Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed #1015 - Teams user registration is broken #1090

Merged
merged 7 commits into from Nov 11, 2021
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
132 changes: 77 additions & 55 deletions 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<JSX.IntrinsicElements["input"], "name"> & { name: string };
export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(props, ref) {
return (
Expand All @@ -28,78 +31,97 @@ export function Label(props: JSX.IntrinsicElements["label"]) {
);
}

export const TextField = forwardRef<
HTMLInputElement,
{
label: ReactNode;
} & React.ComponentProps<typeof Input> & {
labelProps?: React.ComponentProps<typeof Label>;
}
>(function TextField(props, ref) {
const id = useId();
const { label, ...passThroughToInput } = props;
type InputFieldProps = {
label?: ReactNode;
addOnLeading?: ReactNode;
} & React.ComponentProps<typeof Input> & {
labelProps?: React.ComponentProps<typeof Label>;
};

// TODO: use `useForm()` from RHF and get error state here too!
const InputField = forwardRef<HTMLInputElement, InputFieldProps>(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 (
<div>
<Label htmlFor={id} {...props.labelProps}>
<Label htmlFor={id} {...labelProps}>
{label}
</Label>
<Input id={id} {...passThroughToInput} ref={ref} />
{addOnLeading ? (
<div className="flex mt-1 rounded-md shadow-sm">
{addOnLeading}
<Input
id={id}
placeholder={placeholder}
className={classNames(className, "mt-0")}
{...passThroughToInput}
ref={ref}
/>
</div>
) : (
<Input id={id} placeholder={placeholder} className={className} {...passThroughToInput} ref={ref} />
)}
{methods?.formState?.errors[props.name] && (
<Alert className="mt-1" severity="error" message={methods.formState.errors[props.name].message} />
)}
</div>
);
});

/**
* Form helper that creates a rect-hook-form Provider and helps with submission handling & default error handling
*/
export function Form<TFieldValues>(
props: {
/**
* Pass in the return from `react-hook-form`s `useForm()`
*/
form: UseFormReturn<TFieldValues>;
/**
* Submit handler - you'll get the typed form values back
*/
handleSubmit?: SubmitHandler<TFieldValues>;
/**
* Optional - Override the default error handling
* By default it shows a toast with the error
*/
handleError?: (err: ReturnType<typeof getErrorFromUnknown>) => void;
} & Omit<JSX.IntrinsicElements["form"], "ref">
export const TextField = forwardRef<HTMLInputElement, InputFieldProps>(function TextField(props, ref) {
return <InputField ref={ref} {...props} />;
});

export const PasswordField = forwardRef<HTMLInputElement, InputFieldProps>(function PasswordField(
props,
ref
) {
const {
form,
handleSubmit,
handleError = (err) => {
showToast(err.message, "error");
},
...passThrough
} = props;
return <InputField type="password" placeholder="•••••••••••••" ref={ref} {...props} />;
});

export const EmailField = forwardRef<HTMLInputElement, InputFieldProps>(function EmailField(props, ref) {
return <InputField type="email" inputMode="email" ref={ref} {...props} />;
});

type FormProps<T> = { form: UseFormReturn<T>; handleSubmit: SubmitHandler<T> } & Omit<
JSX.IntrinsicElements["form"],
"onSubmit"
>;

const PlainForm = <T extends FieldValues>(props: FormProps<T>, ref: Ref<HTMLFormElement>) => {
const { form, handleSubmit, ...passThrough } = props;

return (
<FormProvider {...form}>
<form
onSubmit={
handleSubmit
? form.handleSubmit(async (...args) => {
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}
</form>
</FormProvider>
);
}
};

export const Form = forwardRef(PlainForm) as <T extends FieldValues>(
p: FormProps<T> & { ref?: Ref<HTMLFormElement> }
) => ReactElement;
Comment on lines +96 to +124
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Preserve form value types from generics


export function FieldsetLegend(props: JSX.IntrinsicElements["legend"]) {
return (
Expand Down
3 changes: 3 additions & 0 deletions components/ui/UsernameInput.tsx
Expand Up @@ -4,6 +4,9 @@ interface UsernameInputProps extends React.ComponentPropsWithRef<"input"> {
label?: string;
}

/**
* @deprecated Use <TextField addOnLeading={}> to achieve the same effect.
*/
const UsernameInput = React.forwardRef<HTMLInputElement, UsernameInputProps>((props, ref) => (
// todo, check if username is already taken here?
<div>
Expand Down
7 changes: 7 additions & 0 deletions pages/api/auth/signup.ts
Expand Up @@ -39,6 +39,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
email: userEmail,
},
],
AND: [
{
emailVerified: {
not: null,
},
},
],
},
});

Expand Down
40 changes: 21 additions & 19 deletions pages/api/schedule/index.ts
Expand Up @@ -6,7 +6,7 @@ import prisma from "@lib/prisma";
import { TimeRange } from "@lib/types/schedule";

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const session = await getSession({ req: req });
const session = await getSession({ req });
const userId = session?.user?.id;
if (!userId) {
res.status(401).json({ message: "Not authenticated" });
Expand All @@ -17,7 +17,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return res.status(400).json({ message: "Bad Request." });
}

const availability = req.body.schedule.reduce(
const availability: Availability[] = req.body.schedule.reduce(
(availability: Availability[], times: TimeRange[], day: number) => {
const addNewTime = (time: TimeRange) =>
({
Expand Down Expand Up @@ -48,27 +48,29 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)

if (req.method === "POST") {
try {
await prisma.availability.deleteMany({
await prisma.user.update({
where: {
userId,
id: userId,
},
});
await Promise.all(
availability.map((schedule: Availability) =>
prisma.availability.create({
data: {
days: schedule.days,
startTime: schedule.startTime,
endTime: schedule.endTime,
user: {
connect: {
id: userId,
},
data: {
availability: {
/* We delete user availabilty */
deleteMany: {
userId: {
equals: userId,
},
},
})
)
);
/* So we can replace it */
createMany: {
data: availability.map((schedule) => ({
days: schedule.days,
startTime: schedule.startTime,
endTime: schedule.endTime,
})),
},
},
},
});
Comment on lines +23 to +45
Copy link
Member

@zomars zomars Nov 11, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made this query safer and more legible (IMO) Tested and working 👍🏽

return res.status(200).json({
message: "created",
});
Expand Down