import { json, redirect } from '@remix-run/node'; import { getSignedInUser, sessionStorage } from '../auth.server'; import { getUserByEmail, sendVerificationEmail, verifyEmailVerificationToken } from '../data/zippo.server'; import { z } from 'zod'; import { Form, Link, useLoaderData } from '@remix-run/react'; import { Button, LinkButton } from '../components/Button'; import { useEffect, useState } from 'react'; import { addSeconds, differenceInSeconds } from 'date-fns'; import type { ActionArgs, LoaderArgs, MetaFunction } from '@remix-run/node'; export const meta: MetaFunction = () => { return { title: 'Verify Email | 0x', description: 'Verify your email', }; }; type VerifyEmailType = { retryAt: string; }; const zodEmailTokenModel = z.object({ email: z.string().email('Please enter a valid email'), token: z.string().min(1, 'Please enter a valid token'), }); const zodResendEmailModel = z.object({ email: z.string().email('Please enter a valid email'), }); export async function action({ request }: ActionArgs) { const [user, headers] = await getSignedInUser(request); if (user) { throw redirect('/apps', { headers }); } const data = Object.fromEntries(await request.formData()); const zodResult = zodResendEmailModel.safeParse(data); if (!zodResult.success) { return json({ error: 'Invalid email' }, 400); } const session = await sessionStorage.getSession(request.headers.get('Cookie')); const verifyEmail: VerifyEmailType | undefined = session.get('verifyEmail'); if (!verifyEmail) { return json({ error: 'Error while verifying retry session' }, 400); } const userResult = await getUserByEmail({ email: zodResult.data.email }); if (userResult.result === 'ERROR') { return json({ error: 'Error while verifying email' }, 400); } const retrievedUser = userResult.data; const now = new Date(); const retryIn = new Date(verifyEmail.retryAt) > now ? differenceInSeconds(new Date(verifyEmail.retryAt), now) : 0; if (retryIn > 0) { return json({ error: 'Please wait before retrying' }, 400); } const result = await sendVerificationEmail({ email: zodResult.data.email, userId: retrievedUser.id }); if (result.result === 'ERROR') { return json({ error: 'Error while sending verification email' }, 400); } verifyEmail.retryAt = addSeconds(new Date(), 45).toISOString(); session.set('verifyEmail', verifyEmail); headers.append('Set-Cookie', await sessionStorage.commitSession(session)); return json({ error: null }, { headers }); } export async function loader({ request }: LoaderArgs) { const [user, headers] = await getSignedInUser(request); if (user) { throw redirect('/apps', { headers }); } // if the user doesn't have the email and token query parameters, redirect them to the create account page const url = new URL(request.url); const email = url.searchParams.get('email'); const token = url.searchParams.get('token'); const zodResult = zodEmailTokenModel.safeParse({ email, token }); if (!zodResult.success) { // if we receive invalid query params, we just redirect to login throw redirect('/login', { headers }); } const session = await sessionStorage.getSession(request.headers.get('Cookie')); let verifyEmailSession: VerifyEmailType | undefined = session.get('verifyEmail'); if (!verifyEmailSession) { verifyEmailSession = { retryAt: new Date().toISOString(), }; session.set('verifyEmail', verifyEmailSession); headers.append('Set-Cookie', await sessionStorage.commitSession(session)); } const verifyEmailRetryIn = verifyEmailSession.retryAt; const now = new Date(); const retryIn = verifyEmailRetryIn && new Date(verifyEmailRetryIn) > now ? differenceInSeconds(new Date(verifyEmailRetryIn), now) : 0; const isValid = await verifyEmailVerificationToken({ email: zodResult.data.email, token: zodResult.data.token }); if (isValid) { session.unset('verifyEmail'); headers.set('Set-Cookie', await sessionStorage.commitSession(session)); return json({ error: null, values: { email, retryIn } }, { headers }); } return json({ error: 'Invalid token or email address', values: { email, retryIn } }, { headers }); } function SuccessMessage() { return (

Your email has been verified

Your email has been successfully verified. You can now login to your account.

Back to login
); } function ErrorMessage({ email, retryIn }: { email: string; retryIn: number }) { const [retryCountdown, setRetryCountdown] = useState(retryIn); useEffect(() => { setRetryCountdown(retryIn); }, [retryIn]); useEffect(() => { if (retryCountdown === 0) return; const timeout = setTimeout(() => { const newRetryCountdown = retryCountdown - 1; setRetryCountdown(newRetryCountdown); }, 1000); return () => { clearTimeout(timeout); }; }, [retryCountdown]); return (
Back to login

Verification link expired

Request a new verification email to {email} to verify your account and get started.

{retryCountdown > 0 && ( Resend email ({retryCountdown}s) )}
); } export default function VerifyEmail() { const { error, values } = useLoaderData(); return (
{error ? ( ) : ( )}
); }