import { getFormProps, getInputProps, useForm } from "@conform-to/react"; import { conformZodMessage, parseWithZod } from "@conform-to/zod"; import { Form, type MetaFunction, useActionData } from "@remix-run/react"; import { type ActionFunction, json } from "@remix-run/server-runtime"; import { z } from "zod"; import { AvatarCircleIcon } from "~/assets/icons/AvatarCircleIcon"; import { EnvelopeIcon } from "~/assets/icons/EnvelopeIcon"; import { UserProfilePhoto } from "~/components/UserProfilePhoto"; import { MainHorizontallyCenteredContainer, PageBody, PageContainer, } from "~/components/layout/AppLayout"; import { Button } from "~/components/primitives/Buttons"; import { CheckboxWithLabel } from "~/components/primitives/Checkbox"; import { Fieldset } from "~/components/primitives/Fieldset"; import { FormButtons } from "~/components/primitives/FormButtons"; import { FormError } from "~/components/primitives/FormError"; import { Header2 } from "~/components/primitives/Headers"; import { Hint } from "~/components/primitives/Hint"; import { Input } from "~/components/primitives/Input"; import { InputGroup } from "~/components/primitives/InputGroup"; import { Label } from "~/components/primitives/Label"; import { NavBar, PageTitle } from "~/components/primitives/PageHeader"; import { prisma } from "~/db.server"; import { useUser } from "~/hooks/useUser"; import { redirectWithSuccessMessage } from "~/models/message.server"; import { updateUser } from "~/models/user.server"; import { requireUserId } from "~/services/session.server"; import { accountPath } from "~/utils/pathBuilder"; export const meta: MetaFunction = () => { return [ { title: `Your profile | Trigger.dev`, }, ]; }; function createSchema( constraints: { isEmailUnique?: (email: string) => Promise; } = {} ) { return z.object({ name: z .string({ required_error: "You must enter a name" }) .min(2, "Your name must be at least 2 characters long") .max(50), email: z .string() .email() .superRefine((email, ctx) => { if (constraints.isEmailUnique === undefined) { //client-side validation skips this ctx.addIssue({ code: z.ZodIssueCode.custom, message: conformZodMessage.VALIDATION_UNDEFINED, }); } else { // Tell zod this is an async validation by returning the promise return constraints.isEmailUnique(email).then((isUnique) => { if (isUnique) { return; } ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Email is already being used by a different account", }); }); } }), marketingEmails: z.preprocess((value) => value === "on", z.boolean()), }); } export const action: ActionFunction = async ({ request }) => { const userId = await requireUserId(request); const formData = await request.formData(); const formSchema = createSchema({ isEmailUnique: async (email) => { const existingUser = await prisma.user.findFirst({ where: { email, }, }); if (!existingUser) { return true; } if (existingUser.id === userId) { return true; } return false; }, }); const submission = await parseWithZod(formData, { schema: formSchema, async: true }); if (submission.status !== "success") { return json(submission.reply()); } try { const _user = await updateUser({ id: userId, name: submission.value.name, email: submission.value.email, marketingEmails: submission.value.marketingEmails, }); return redirectWithSuccessMessage( accountPath(), request, "Your account profile has been updated." ); } catch (error: any) { return json({ errors: { body: error.message } }, { status: 400 }); } }; export default function Page() { const user = useUser(); const lastSubmission = useActionData(); const [form, { name, email, marketingEmails }] = useForm({ id: "account", // TODO: type this lastResult: lastSubmission as any, onValidate({ formData }) { return parseWithZod(formData, { schema: createSchema() }); }, }); return (
Profile
Your teammates will see this {name.errors} {email.errors} {marketingEmails.errors} Update } />
); }