"use client"; import { useState } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { AlertCircle, CheckCircle2 } from "lucide-react"; import { createApiClient } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { AxiosResponse } from "axios"; import { RequestTotpSecretBody, RequestTotpSecretResponse, VerifyTotpBody, VerifyTotpResponse } from "@server/routers/auth"; import { z } from "zod"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@app/components/ui/form"; import { Credenza, CredenzaBody, CredenzaClose, CredenzaContent, CredenzaDescription, CredenzaFooter, CredenzaHeader, CredenzaTitle } from "@app/components/Credenza"; import { toast } from "@app/hooks/useToast"; import { formatAxiosError } from "@app/lib/api"; import CopyTextBox from "@app/components/CopyTextBox"; import { QRCodeCanvas, QRCodeSVG } from "qrcode.react"; import { useUserContext } from "@app/hooks/useUserContext"; import { useTranslations } from "next-intl"; type Enable2FaProps = { open: boolean; setOpen: (val: boolean) => void; }; export default function Enable2FaForm({ open, setOpen }: Enable2FaProps) { const [step, setStep] = useState(1); const [secretKey, setSecretKey] = useState(""); const [secretUri, setSecretUri] = useState(""); const [verificationCode, setVerificationCode] = useState(""); const [error, setError] = useState(""); const [success, setSuccess] = useState(false); const [loading, setLoading] = useState(false); const [backupCodes, setBackupCodes] = useState([]); const { user, updateUser } = useUserContext(); const api = createApiClient(useEnvContext()); const t = useTranslations(); const enableSchema = z.object({ password: z.string().min(1, { message: t('passwordRequired') }) }); const confirmSchema = z.object({ code: z.string().length(6, { message: t('pincodeInvalid') }) }); const enableForm = useForm>({ resolver: zodResolver(enableSchema), defaultValues: { password: "" } }); const confirmForm = useForm>({ resolver: zodResolver(confirmSchema), defaultValues: { code: "" } }); const request2fa = async (values: z.infer) => { setLoading(true); const res = await api .post>( `/auth/2fa/request`, { password: values.password } as RequestTotpSecretBody ) .catch((e) => { toast({ title: t('otpErrorEnable'), description: formatAxiosError( e, t('otpErrorEnableDescription') ), variant: "destructive" }); }); if (res && res.data.data.secret) { setSecretKey(res.data.data.secret); setSecretUri(res.data.data.uri); setStep(2); } setLoading(false); }; const confirm2fa = async (values: z.infer) => { setLoading(true); const res = await api .post>(`/auth/2fa/enable`, { code: values.code } as VerifyTotpBody) .catch((e) => { toast({ title: t('otpErrorEnable'), description: formatAxiosError( e, t('otpErrorEnableDescription') ), variant: "destructive" }); }); if (res && res.data.data.valid) { setBackupCodes(res.data.data.backupCodes || []); updateUser({ twoFactorEnabled: true }); setStep(3); } setLoading(false); }; const handleVerify = () => { if (verificationCode.length !== 6) { setError(t('otpSetupCheckCode')); return; } if (verificationCode === "123456") { setSuccess(true); setStep(3); } else { setError(t('otpSetupCheckCodeRetry')); } }; function reset() { setLoading(false); setStep(1); setSecretKey(""); setSecretUri(""); setVerificationCode(""); setError(""); setSuccess(false); setBackupCodes([]); enableForm.reset(); confirmForm.reset(); } return ( { setOpen(val); reset(); }} > {t('otpSetup')} {t('otpSetupDescription')} {step === 1 && (
( {t('password')} )} />
)} {step === 2 && (

{t('otpSetupScanQr')}

( {t('otpSetupSecretCode')} )} />
)} {step === 3 && (

{t('otpSetupSuccess')}

{t('otpSetupSuccessStoreBackupCodes')}

)}
{(step === 1 || step === 2) && ( )}
); }