import { zodResolver } from "@hookform/resolvers/zod"; import { href, redirect } from "react-router"; import { getValidatedFormData } from "remix-hook-form"; import { toast } from "sonner"; import { z } from "zod"; import { VerifyForm } from "~/blocks/auth.verify-form"; import { createErrorResponse } from "~/lib/actions.errors"; import { createAuthVerificationStorage } from "~/lib/sessions.server"; import { firestormConfig } from "~/pkg/config"; import type { Route } from "./+types/auth.verify-otp"; const AuthVerifyOtpSchema = z.object({ email: z.string().email(), otp: z.string().min(6).max(6), }); const authVerifyOtpResolver = zodResolver(AuthVerifyOtpSchema); const toastTitles = { DEFAULT: { loading: "Processing...", error: "An unknown error occurred", success: "Operation completed successfully", }, POST: { loading: "Verifying OTP...", error: "Error Verifying OTP", success: "OTP Verified Successfully", }, } as const; export async function loader({ request, context }: Route.LoaderArgs) { const { auth, cloudflare: { env }, } = context; const sessionData = await auth.api.getSession(request); if (sessionData?.user) return redirect(href("/onboard")); const authVerificationStorage = createAuthVerificationStorage(env); const session = await authVerificationStorage.getSession( request.headers.get("Cookie"), ); const email = session.get("email"); if (!email) return redirect(href("/auth/continue")); return { email, NODE_ENV: env.NODE_ENV }; } export async function action({ request, context }: Route.ActionArgs) { const { auth, cloudflare: { env }, } = context; switch (request.method) { case "POST": { const { errors, data: formData } = await getValidatedFormData< z.infer >(request, authVerifyOtpResolver); if (errors) return createErrorResponse( "Validation failed", 422, "VALIDATION_FAILED", ); const { data: response, error } = await auth.api .signInEmailOTP({ headers: request.headers, body: { email: formData.email, otp: formData.otp, }, returnHeaders: true, }) .then((res) => ({ data: res, error: null, })) .catch((error) => ({ data: null, error, })); if (error || !response) { console.error("Error running signInEmailOTP", error); return createErrorResponse( error.body.message, error.statusCode, error.body.code, ); } const data = response.response; if (!data.user || !data.token) return createErrorResponse( "Failed to authenticate user", 500, "INTERNAL_SERVER_ERROR", ); const authVerificationStorage = createAuthVerificationStorage(env); const session = await authVerificationStorage.getSession( request.headers.get("Cookie"), ); const redirectTo = session.get("redirectTo") ?? href("/onboard"); const headers = new Headers(); headers.append( "Set-Cookie", await authVerificationStorage.destroySession(session), ); for (const cookie of response.headers.getSetCookie()) { headers.append("Set-Cookie", cookie); } return redirect(redirectTo, { headers, }); } default: return createErrorResponse( "Method not allowed", 405, "METHOD_NOT_ALLOWED", ); } } export async function clientAction({ request, serverAction, }: Route.ClientActionArgs) { const method = request.method as keyof typeof toastTitles; const toastTitle = toastTitles[method] || toastTitles.DEFAULT; switch (request.method) { case "POST": { const emailOtpPromise = serverAction(); toast.promise(emailOtpPromise, { loading: toastTitle.loading, }); const result = await emailOtpPromise; if (result.error) { toast.error(toastTitle.error, { description: result.error.message, }); return result; } toast.success(toastTitle.success); return result; } default: { const error = createErrorResponse( `Unsupported method: ${request.method}`, 405, "METHOD_NOT_ALLOWED", ); toast.error(toastTitle.error, { description: error.data.error.message, }); return error; } } } export function meta(_: Route.MetaArgs) { const appName = firestormConfig.appName; return [ { title: `Verify Account | ${appName}` }, { property: "og:title", content: `Verify Account | ${appName}`, }, { name: "description", content: "Verify your identity with the secure one-time password sent to your email address.", }, { property: "og:description", content: "Verify your identity with the secure one-time password sent to your email address.", }, ]; } export default function VerifyOTP(_: Route.LoaderArgs) { return (
); }