Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
itsmegood committed Feb 1, 2024
1 parent d40ce1e commit 0758042
Show file tree
Hide file tree
Showing 20 changed files with 444 additions and 422 deletions.
34 changes: 18 additions & 16 deletions app/routes/_auth+/forgot-password.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { conform, useForm } from '@conform-to/react'
import { getFieldsetConstraint, parse } from '@conform-to/zod'
import { getFormProps, getInputProps, useForm } from '@conform-to/react'
import { getZodConstraint, parseWithZod } from '@conform-to/zod'
import * as E from '@react-email/components'
import {
json,
Expand All @@ -26,7 +26,7 @@ const ForgotPasswordSchema = z.object({
export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData()
checkHoneypot(formData)
const submission = await parse(formData, {
const submission = await parseWithZod(formData, {
schema: ForgotPasswordSchema.superRefine(async (data, ctx) => {
const user = await prisma.user.findFirst({
where: {
Expand All @@ -48,11 +48,11 @@ export async function action({ request }: ActionFunctionArgs) {
}),
async: true,
})
if (submission.intent !== 'submit') {
return json({ status: 'idle', submission } as const)
}
if (!submission.value) {
return json({ status: 'error', submission } as const, { status: 400 })
if (submission.status !== 'success') {
return json(
{ result: submission.reply() },
{ status: submission.status === 'error' ? 400 : 200 },
)
}
const { usernameOrEmail } = submission.value

Expand All @@ -79,8 +79,10 @@ export async function action({ request }: ActionFunctionArgs) {
if (response.status === 'success') {
return redirect(redirectTo.toString())
} else {
submission.error[''] = [response.error.message]
return json({ status: 'error', submission } as const, { status: 500 })
return json(
{ result: submission.reply({ formErrors: [response.error.message] }) },
{ status: 500 },
)
}
}

Expand Down Expand Up @@ -120,10 +122,10 @@ export default function ForgotPasswordRoute() {

const [form, fields] = useForm({
id: 'forgot-password-form',
constraint: getFieldsetConstraint(ForgotPasswordSchema),
lastSubmission: forgotPassword.data?.submission,
constraint: getZodConstraint(ForgotPasswordSchema),
lastResult: forgotPassword.data?.result,
onValidate({ formData }) {
return parse(formData, { schema: ForgotPasswordSchema })
return parseWithZod(formData, { schema: ForgotPasswordSchema })
},
shouldRevalidate: 'onBlur',
})
Expand All @@ -138,7 +140,7 @@ export default function ForgotPasswordRoute() {
</p>
</div>
<div className="mx-auto mt-16 min-w-full max-w-sm sm:min-w-[368px]">
<forgotPassword.Form method="POST" {...form.props}>
<forgotPassword.Form method="POST" {...getFormProps(form)}>
<HoneypotInputs />
<div>
<Field
Expand All @@ -148,7 +150,7 @@ export default function ForgotPasswordRoute() {
}}
inputProps={{
autoFocus: true,
...conform.input(fields.usernameOrEmail),
...getInputProps(fields.usernameOrEmail, { type: 'text' }),
}}
errors={fields.usernameOrEmail.errors}
/>
Expand All @@ -161,7 +163,7 @@ export default function ForgotPasswordRoute() {
status={
forgotPassword.state === 'submitting'
? 'pending'
: forgotPassword.data?.status ?? 'idle'
: form.status ?? 'idle'
}
type="submit"
disabled={forgotPassword.state !== 'idle'}
Expand Down
45 changes: 22 additions & 23 deletions app/routes/_auth+/login.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { conform, useForm } from '@conform-to/react'
import { getFieldsetConstraint, parse } from '@conform-to/zod'
import { useForm, getFormProps, getInputProps } from '@conform-to/react'
import { getZodConstraint, parseWithZod } from '@conform-to/zod'
import { invariant } from '@epic-web/invariant'
import {
json,
Expand Down Expand Up @@ -110,7 +110,10 @@ export async function handleVerification({
request,
submission,
}: VerifyFunctionArgs) {
invariant(submission.value, 'Submission should have a value by this point')
invariant(
submission.status === 'success',
'Submission should be successful by now',
)
const authSession = await authSessionStorage.getSession(
request.headers.get('cookie'),
)
Expand Down Expand Up @@ -196,10 +199,10 @@ export async function action({ request }: ActionFunctionArgs) {
await requireAnonymous(request)
const formData = await request.formData()
checkHoneypot(formData)
const submission = await parse(formData, {
const submission = await parseWithZod(formData, {
schema: intent =>
LoginFormSchema.transform(async (data, ctx) => {
if (intent !== 'submit') return { ...data, session: null }
if (intent !== null) return { ...data, session: null }

const session = await login(data)
if (!session) {
Expand All @@ -214,16 +217,12 @@ export async function action({ request }: ActionFunctionArgs) {
}),
async: true,
})
// get the password off the payload that's sent back
delete submission.payload.password

if (submission.intent !== 'submit') {
// @ts-expect-error - conform should probably have support for doing this
delete submission.value?.password
return json({ status: 'idle', submission } as const)
}
if (!submission.value?.session) {
return json({ status: 'error', submission } as const, { status: 400 })
if (submission.status !== 'success' || !submission.value.session) {
return json(
{ result: submission.reply({ hideFields: ['password'] }) },
{ status: submission.status === 'error' ? 400 : 200 },
)
}

const { session, remember, redirectTo } = submission.value
Expand All @@ -244,11 +243,11 @@ export default function LoginPage() {

const [form, fields] = useForm({
id: 'login-form',
constraint: getFieldsetConstraint(LoginFormSchema),
constraint: getZodConstraint(LoginFormSchema),
defaultValue: { redirectTo },
lastSubmission: actionData?.submission,
lastResult: actionData?.result,
onValidate({ formData }) {
return parse(formData, { schema: LoginFormSchema })
return parseWithZod(formData, { schema: LoginFormSchema })
},
shouldRevalidate: 'onBlur',
})
Expand All @@ -266,12 +265,12 @@ export default function LoginPage() {

<div>
<div className="mx-auto w-full max-w-md px-8">
<Form method="POST" {...form.props}>
<Form method="POST" {...getFormProps(form)}>
<HoneypotInputs />
<Field
labelProps={{ children: 'Username' }}
inputProps={{
...conform.input(fields.username),
...getInputProps(fields.username, { type: 'text' }),
autoFocus: true,
className: 'lowercase',
autoComplete: 'username',
Expand All @@ -282,7 +281,7 @@ export default function LoginPage() {
<Field
labelProps={{ children: 'Password' }}
inputProps={{
...conform.input(fields.password, {
...getInputProps(fields.password, {
type: 'password',
}),
autoComplete: 'current-password',
Expand All @@ -296,7 +295,7 @@ export default function LoginPage() {
htmlFor: fields.remember.id,
children: 'Remember me',
}}
buttonProps={conform.input(fields.remember, {
buttonProps={getInputProps(fields.remember, {
type: 'checkbox',
})}
errors={fields.remember.errors}
Expand All @@ -312,14 +311,14 @@ export default function LoginPage() {
</div>

<input
{...conform.input(fields.redirectTo, { type: 'hidden' })}
{...getInputProps(fields.redirectTo, { type: 'hidden' })}
/>
<ErrorList errors={form.errors} id={form.errorId} />

<div className="flex items-center justify-between gap-6 pt-3">
<StatusButton
className="w-full"
status={isPending ? 'pending' : actionData?.status ?? 'idle'}
status={isPending ? 'pending' : form.status ?? 'idle'}
type="submit"
disabled={isPending}
>
Expand Down
47 changes: 25 additions & 22 deletions app/routes/_auth+/onboarding.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { conform, useForm } from '@conform-to/react'
import { getFieldsetConstraint, parse } from '@conform-to/zod'
import { getFormProps, getInputProps, useForm } from '@conform-to/react'
import { getZodConstraint, parseWithZod } from '@conform-to/zod'
import { invariant } from '@epic-web/invariant'
import {
json,
Expand Down Expand Up @@ -69,7 +69,7 @@ export async function action({ request }: ActionFunctionArgs) {
const email = await requireOnboardingEmail(request)
const formData = await request.formData()
checkHoneypot(formData)
const submission = await parse(formData, {
const submission = await parseWithZod(formData, {
schema: intent =>
SignupFormSchema.superRefine(async (data, ctx) => {
const existingUser = await prisma.user.findUnique({
Expand All @@ -85,19 +85,19 @@ export async function action({ request }: ActionFunctionArgs) {
return
}
}).transform(async data => {
if (intent !== 'submit') return { ...data, session: null }
if (intent !== null) return { ...data, session: null }

const session = await signup({ ...data, email })
return { ...data, session }
}),
async: true,
})

if (submission.intent !== 'submit') {
return json({ status: 'idle', submission } as const)
}
if (!submission.value?.session) {
return json({ status: 'error', submission } as const, { status: 400 })
if (submission.status !== 'success' || !submission.value.session) {
return json(
{ result: submission.reply() },
{ status: submission.status === 'error' ? 400 : 200 },
)
}

const { session, remember, redirectTo } = submission.value
Expand Down Expand Up @@ -127,7 +127,10 @@ export async function action({ request }: ActionFunctionArgs) {
}

export async function handleVerification({ submission }: VerifyFunctionArgs) {
invariant(submission.value, 'submission.value should be defined by now')
invariant(
submission.status === 'success',
'Submission should be successful by now',
)
const verifySession = await verifySessionStorage.getSession()
verifySession.set(onboardingEmailSessionKey, submission.value.target)
return redirect('/onboarding', {
Expand All @@ -150,11 +153,11 @@ export default function SignupRoute() {

const [form, fields] = useForm({
id: 'onboarding-form',
constraint: getFieldsetConstraint(SignupFormSchema),
constraint: getZodConstraint(SignupFormSchema),
defaultValue: { redirectTo },
lastSubmission: actionData?.submission,
lastResult: actionData?.result,
onValidate({ formData }) {
return parse(formData, { schema: SignupFormSchema })
return parseWithZod(formData, { schema: SignupFormSchema })
},
shouldRevalidate: 'onBlur',
})
Expand All @@ -172,13 +175,13 @@ export default function SignupRoute() {
<Form
method="POST"
className="mx-auto min-w-full max-w-sm sm:min-w-[368px]"
{...form.props}
{...getFormProps(form)}
>
<HoneypotInputs />
<Field
labelProps={{ htmlFor: fields.username.id, children: 'Username' }}
inputProps={{
...conform.input(fields.username),
...getInputProps(fields.username, { type: 'text' }),
autoComplete: 'username',
className: 'lowercase',
}}
Expand All @@ -187,15 +190,15 @@ export default function SignupRoute() {
<Field
labelProps={{ htmlFor: fields.name.id, children: 'Name' }}
inputProps={{
...conform.input(fields.name),
...getInputProps(fields.name, { type: 'text' }),
autoComplete: 'name',
}}
errors={fields.name.errors}
/>
<Field
labelProps={{ htmlFor: fields.password.id, children: 'Password' }}
inputProps={{
...conform.input(fields.password, { type: 'password' }),
...getInputProps(fields.password, { type: 'password' }),
autoComplete: 'new-password',
}}
errors={fields.password.errors}
Expand All @@ -207,7 +210,7 @@ export default function SignupRoute() {
children: 'Confirm Password',
}}
inputProps={{
...conform.input(fields.confirmPassword, { type: 'password' }),
...getInputProps(fields.confirmPassword, { type: 'password' }),
autoComplete: 'new-password',
}}
errors={fields.confirmPassword.errors}
Expand All @@ -219,7 +222,7 @@ export default function SignupRoute() {
children:
'Do you agree to our Terms of Service and Privacy Policy?',
}}
buttonProps={conform.input(
buttonProps={getInputProps(
fields.agreeToTermsOfServiceAndPrivacyPolicy,
{ type: 'checkbox' },
)}
Expand All @@ -230,17 +233,17 @@ export default function SignupRoute() {
htmlFor: fields.remember.id,
children: 'Remember me',
}}
buttonProps={conform.input(fields.remember, { type: 'checkbox' })}
buttonProps={getInputProps(fields.remember, { type: 'checkbox' })}
errors={fields.remember.errors}
/>

<input {...conform.input(fields.redirectTo, { type: 'hidden' })} />
<input {...getInputProps(fields.redirectTo, { type: 'hidden' })} />
<ErrorList errors={form.errors} id={form.errorId} />

<div className="flex items-center justify-between gap-6">
<StatusButton
className="w-full"
status={isPending ? 'pending' : actionData?.status ?? 'idle'}
status={isPending ? 'pending' : form.status ?? 'idle'}
type="submit"
disabled={isPending}
>
Expand Down
Loading

0 comments on commit 0758042

Please sign in to comment.