diff --git a/components/form/fields.tsx b/components/form/fields.tsx index 2ee94c632b19c..8c67a4aefd459 100644 --- a/components/form/fields.tsx +++ b/components/form/fields.tsx @@ -1,11 +1,14 @@ import { useId } from "@radix-ui/react-id"; -import { forwardRef, ReactNode } from "react"; -import { FormProvider, SubmitHandler, UseFormReturn } from "react-hook-form"; +import { forwardRef, ReactElement, ReactNode, Ref } from "react"; +import { FieldValues, FormProvider, SubmitHandler, useFormContext, UseFormReturn } from "react-hook-form"; import classNames from "@lib/classNames"; import { getErrorFromUnknown } from "@lib/errors"; +import { useLocale } from "@lib/hooks/useLocale"; import showToast from "@lib/notification"; +import { Alert } from "@components/ui/Alert"; + type InputProps = Omit & { name: string }; export const Input = forwardRef(function Input(props, ref) { return ( @@ -28,78 +31,97 @@ export function Label(props: JSX.IntrinsicElements["label"]) { ); } -export const TextField = forwardRef< - HTMLInputElement, - { - label: ReactNode; - } & React.ComponentProps & { - labelProps?: React.ComponentProps; - } ->(function TextField(props, ref) { - const id = useId(); - const { label, ...passThroughToInput } = props; +type InputFieldProps = { + label?: ReactNode; + addOnLeading?: ReactNode; +} & React.ComponentProps & { + labelProps?: React.ComponentProps; + }; - // TODO: use `useForm()` from RHF and get error state here too! +const InputField = forwardRef(function InputField(props, ref) { + const id = useId(); + const { t } = useLocale(); + const methods = useFormContext(); + const { + label = t(props.name), + labelProps, + placeholder = t(props.name + "_placeholder") !== props.name + "_placeholder" + ? t(props.name + "_placeholder") + : "", + className, + addOnLeading, + ...passThroughToInput + } = props; return (
-
); }); -/** - * Form helper that creates a rect-hook-form Provider and helps with submission handling & default error handling - */ -export function Form( - props: { - /** - * Pass in the return from `react-hook-form`s `useForm()` - */ - form: UseFormReturn; - /** - * Submit handler - you'll get the typed form values back - */ - handleSubmit?: SubmitHandler; - /** - * Optional - Override the default error handling - * By default it shows a toast with the error - */ - handleError?: (err: ReturnType) => void; - } & Omit +export const TextField = forwardRef(function TextField(props, ref) { + return ; +}); + +export const PasswordField = forwardRef(function PasswordField( + props, + ref ) { - const { - form, - handleSubmit, - handleError = (err) => { - showToast(err.message, "error"); - }, - ...passThrough - } = props; + return ; +}); + +export const EmailField = forwardRef(function EmailField(props, ref) { + return ; +}); + +type FormProps = { form: UseFormReturn; handleSubmit: SubmitHandler } & Omit< + JSX.IntrinsicElements["form"], + "onSubmit" +>; + +const PlainForm = (props: FormProps, ref: Ref) => { + const { form, handleSubmit, ...passThrough } = props; return (
{ - try { - await handleSubmit(...args); - } catch (_err) { - const err = getErrorFromUnknown(_err); - handleError(err); - } - }) - : undefined - } + ref={ref} + onSubmit={(event) => { + form + .handleSubmit(handleSubmit)(event) + .catch((err) => { + showToast(`${getErrorFromUnknown(err).message}`, "error"); + }); + }} {...passThrough}> {props.children}
); -} +}; + +export const Form = forwardRef(PlainForm) as ( + p: FormProps & { ref?: Ref } +) => ReactElement; export function FieldsetLegend(props: JSX.IntrinsicElements["legend"]) { return ( diff --git a/components/ui/UsernameInput.tsx b/components/ui/UsernameInput.tsx index f3993889fd3cd..5731cde8ec807 100644 --- a/components/ui/UsernameInput.tsx +++ b/components/ui/UsernameInput.tsx @@ -4,6 +4,9 @@ interface UsernameInputProps extends React.ComponentPropsWithRef<"input"> { label?: string; } +/** + * @deprecated Use to achieve the same effect. + */ const UsernameInput = React.forwardRef((props, ref) => ( // todo, check if username is already taken here?
diff --git a/components/ui/form/Schedule.tsx b/components/ui/form/Schedule.tsx index b3076d44aff32..a07668254aecd 100644 --- a/components/ui/form/Schedule.tsx +++ b/components/ui/form/Schedule.tsx @@ -3,9 +3,10 @@ import dayjs, { Dayjs } from "dayjs"; import React, { useCallback, useState } from "react"; import { Controller, useFieldArray } from "react-hook-form"; +import { defaultDayRange } from "@lib/availability"; import { weekdayNames } from "@lib/core/i18n/weekday"; import { useLocale } from "@lib/hooks/useLocale"; -import { TimeRange, Schedule as ScheduleType } from "@lib/types/schedule"; +import { TimeRange } from "@lib/types/schedule"; import Button from "@components/ui/Button"; import Select from "@components/ui/form/Select"; @@ -30,22 +31,6 @@ const TIMES = (() => { })(); /** End Time Increments For Select */ -// sets the desired time in current date, needs to be current date for proper DST translation -const defaultDayRange: TimeRange = { - start: new Date(new Date().setHours(9, 0, 0, 0)), - end: new Date(new Date().setHours(17, 0, 0, 0)), -}; - -export const DEFAULT_SCHEDULE: ScheduleType = [ - [], - [defaultDayRange], - [defaultDayRange], - [defaultDayRange], - [defaultDayRange], - [defaultDayRange], - [], -]; - type Option = { readonly label: string; readonly value: number; @@ -139,7 +124,7 @@ const ScheduleBlock = ({ name, day, weekday }: ScheduleBlockProps) => { onChange={(e) => (e.target.checked ? replace([defaultDayRange]) : replace([]))} className="inline-block border-gray-300 rounded-sm focus:ring-neutral-500 text-neutral-900" /> - {weekday} + {weekday}
@@ -157,7 +142,7 @@ const ScheduleBlock = ({ name, day, weekday }: ScheduleBlockProps) => { />
))} - {!fields.length && t("no_availability")} + {!fields.length && t("no_availability")}
+
- - - + + ); } -export async function getServerSideProps(ctx) { - if (!ctx.query.token) { +export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { + const token = asStringOrNull(ctx.query.token); + if (!token) { return { notFound: true, }; } const verificationRequest = await prisma.verificationRequest.findUnique({ where: { - token: ctx.query.token, + token, }, }); @@ -175,4 +174,4 @@ export async function getServerSideProps(ctx) { } return { props: { email: verificationRequest.identifier } }; -} +}; diff --git a/pages/availability/index.tsx b/pages/availability/index.tsx index df0bd5111d31e..76e3af0307198 100644 --- a/pages/availability/index.tsx +++ b/pages/availability/index.tsx @@ -2,6 +2,7 @@ import Link from "next/link"; import { useForm } from "react-hook-form"; import { QueryCell } from "@lib/QueryCell"; +import { DEFAULT_SCHEDULE } from "@lib/availability"; import { useLocale } from "@lib/hooks/useLocale"; import showToast from "@lib/notification"; import { inferQueryOutput, trpc } from "@lib/trpc"; @@ -10,7 +11,7 @@ import { Schedule as ScheduleType } from "@lib/types/schedule"; import Shell from "@components/Shell"; import { Form } from "@components/form/fields"; import Button from "@components/ui/Button"; -import Schedule, { DEFAULT_SCHEDULE } from "@components/ui/form/Schedule"; +import Schedule from "@components/ui/form/Schedule"; type FormValues = { schedule: ScheduleType; diff --git a/pages/getting-started.tsx b/pages/getting-started.tsx index ccc723470e762..1f022b56d01b6 100644 --- a/pages/getting-started.tsx +++ b/pages/getting-started.tsx @@ -16,6 +16,7 @@ import { useForm } from "react-hook-form"; import TimezoneSelect from "react-timezone-select"; import { getSession } from "@lib/auth"; +import { DEFAULT_SCHEDULE } from "@lib/availability"; import { useLocale } from "@lib/hooks/useLocale"; import getIntegrations from "@lib/integrations/getIntegrations"; import prisma from "@lib/prisma"; @@ -29,7 +30,7 @@ import { CalendarListContainer } from "@components/integrations/CalendarListCont import { Alert } from "@components/ui/Alert"; import Button from "@components/ui/Button"; import Text from "@components/ui/Text"; -import Schedule, { DEFAULT_SCHEDULE } from "@components/ui/form/Schedule"; +import Schedule from "@components/ui/form/Schedule"; import getCalendarCredentials from "@server/integrations/getCalendarCredentials"; import getConnectedCalendars from "@server/integrations/getConnectedCalendars"; @@ -313,7 +314,7 @@ export default function Onboarding(props: inferSSRProps className="max-w-lg mx-auto text-black bg-white dark:bg-opacity-5 dark:text-white" form={availabilityForm} handleSubmit={async (values) => { diff --git a/public/static/locales/en/common.json b/public/static/locales/en/common.json index 2ff30322782b4..91f4e35d43e36 100644 --- a/public/static/locales/en/common.json +++ b/public/static/locales/en/common.json @@ -245,7 +245,7 @@ "troubleshoot_availability": "Troubleshoot your availability to explore why your times are showing as they are.", "change_available_times": "Change available times", "change_your_available_times": "Change your available times", - "change_start_end": "Set your weekly hours", + "change_start_end": "Change the start and end times of your day", "change_start_end_buffer": "Set the start and end time of your day and a minimum buffer between your meetings.", "current_start_date": "Currently, your day is set to start at", "start_end_changed_successfully": "The start and end times for your day have been changed successfully.", @@ -254,7 +254,10 @@ "dark": "Dark", "automatically_adjust_theme": "Automatically adjust theme based on invitee preferences", "email": "Email", + "email_placeholder": "jdoe@example.com", "full_name": "Full name", + "browse_api_documentation": "Browse our API documentation", + "leverage_our_api": "Leverage our API for full control and customizability.", "create_webhook": "Create Webhook", "booking_cancelled": "Booking Cancelled", "booking_rescheduled": "Booking Rescheduled", @@ -463,7 +466,7 @@ "manage_your_billing_info": "Manage your billing information and cancel your subscription.", "availability": "Availability", "availability_updated_successfully": "Availability updated successfully", - "configure_availability": "Set times when you are available for bookings.", + "configure_availability": "Configure times when you are available for bookings.", "change_weekly_schedule": "Change your weekly schedule", "logo": "Logo", "error": "Error", @@ -525,5 +528,7 @@ "connect_an_additional_calendar": "Connect an additional calendar", "conferencing": "Conferencing", "calendar": "Calendar", - "not_installed": "Not installed" + "not_installed": "Not installed", + "error_password_mismatch": "Passwords don't match.", + "error_required_field": "This field is required." } diff --git a/scripts/seed.ts b/scripts/seed.ts index c47181bd82cf3..7326c9357b7ec 100644 --- a/scripts/seed.ts +++ b/scripts/seed.ts @@ -3,6 +3,7 @@ import dayjs from "dayjs"; import { uuid } from "short-uuid"; import { hashPassword } from "../lib/auth"; +import { DEFAULT_SCHEDULE, getAvailabilityFromSchedule } from "../lib/availability"; const prisma = new PrismaClient(); @@ -26,6 +27,11 @@ async function createUserAndEventType(opts: { password: await hashPassword(opts.user.password), emailVerified: new Date(), completedOnboarding: opts.user.completedOnboarding ?? true, + availability: { + createMany: { + data: getAvailabilityFromSchedule(DEFAULT_SCHEDULE), + }, + }, }; const user = await prisma.user.upsert({ where: { email: opts.user.email },