"use client"; import { authClient } from "@repo/auth/client"; import { useSession } from "@saas/auth/hooks/use-session"; import { useUserAccountsQuery } from "@saas/auth/lib/api"; import { SettingsItem } from "@saas/shared/components/SettingsItem"; import { useMutation } from "@tanstack/react-query"; import { Button } from "@ui/components/button"; import { Card } from "@ui/components/card"; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@ui/components/dialog"; import { FormItem } from "@ui/components/form"; import { Input } from "@ui/components/input"; import { Label } from "@ui/components/label"; import { PasswordInput } from "@ui/components/password-input"; import { ArrowRightIcon, CheckIcon, ShieldCheckIcon, TabletSmartphoneIcon, XIcon, } from "lucide-react"; import { useTranslations } from "next-intl"; import { useEffect, useMemo, useState } from "react"; import QRCode from "react-qr-code"; import { toast } from "sonner"; export function TwoFactorBlock() { const t = useTranslations(); const { user, reloadSession } = useSession(); const [dialogOpen, setDialogOpen] = useState(false); const [dialogView, setDialogView] = useState<"password" | "totp-url">( "password", ); const [totpURI, setTotpURI] = useState(""); const [password, setPassword] = useState(""); const [totpCode, setTotpCode] = useState(""); const { data: accounts } = useUserAccountsQuery(); useEffect(() => { setPassword(""); }, [dialogOpen]); const totpURISecret = useMemo(() => { if (!totpURI) { return null; } const url = new URL(totpURI); return url.searchParams.get("secret") || null; }, [totpURI]); const verifyPassword = async () => { setDialogView("password"); setDialogOpen(true); }; const enableTwoFactorMutation = useMutation({ mutationKey: ["enableTwoFactor"], mutationFn: async () => { const { data, error } = await authClient.twoFactor.enable({ password, }); if (error) { throw error; } setTotpURI(data.totpURI); setDialogView("totp-url"); }, onError: () => { toast.error( t( "settings.account.security.twoFactor.notifications.enable.error.title", ), ); }, }); const disableTwoFactorMutation = useMutation({ mutationKey: ["disableTwoFactor"], mutationFn: async () => { const { data, error } = await authClient.twoFactor.disable({ password, }); if (error) { throw error; } setDialogOpen(false); toast.success( t( "settings.account.security.twoFactor.notifications.disable.success.title", ), ); reloadSession(); }, onError: () => { toast.error( t( "settings.account.security.twoFactor.notifications.enable.error.title", ), ); }, }); const verifyTwoFactorMutation = useMutation({ mutationKey: ["verifyTwoFactor"], mutationFn: async () => { const { error } = await authClient.twoFactor.verifyTotp({ code: totpCode, }); if (error) { throw error; } toast.success( t( "settings.account.security.twoFactor.notifications.verify.success.title", ), ); reloadSession(); setDialogOpen(false); }, }); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (user?.twoFactorEnabled) { disableTwoFactorMutation.mutate(); return; } if (dialogView === "password") { enableTwoFactorMutation.mutate(); return; } verifyTwoFactorMutation.mutate(); }; if (!accounts?.some((account) => account.provider === "credential")) { return null; } return ( {user?.twoFactorEnabled ? (

{t("settings.account.security.twoFactor.enabled")}

) : (
)} {dialogView === "password" ? t( "settings.account.security.twoFactor.dialog.password.title", ) : t( "settings.account.security.twoFactor.dialog.totpUrl.title", )} {dialogView === "password" ? (

{t( "settings.account.security.twoFactor.dialog.password.description", )}

setPassword(value)} />
) : (

{t( "settings.account.security.twoFactor.dialog.totpUrl.description", )}

{totpURISecret && (

{totpURISecret}

)}

setTotpCode(e.target.value) } />
)}
); }