{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "two-factor-setup-modal", "dependencies": [ "@inertiajs/react" ], "registryDependencies": [ "@craft/alert-error", "@craft/form-errors", "@craft/use-clipboard", "button", "dialog", "input-otp", "label" ], "files": [ { "path": "registry/new-york-v4/components/two-factor-setup-modal.tsx", "content": "import { Form } from \"@inertiajs/react\";\nimport { REGEXP_ONLY_DIGITS } from \"input-otp\";\nimport { ScanLine } from \"lucide-react\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport AlertError from \"@/components/alert-error\";\nimport InputError from \"@/components/input-error\";\nimport { Button } from \"@/components/ui/button\";\nimport { toastFirstFormError } from \"@/lib/form-errors\";\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n} from \"@/components/ui/dialog\";\nimport { InputOTP, InputOTPGroup, InputOTPSlot } from \"@/components/ui/input-otp\";\nimport { Spinner } from \"@/components/ui/spinner\";\nimport { useAppearance } from \"@/hooks/use-appearance\";\nimport { OTP_MAX_LENGTH } from \"@/hooks/use-two-factor-auth\";\nimport { confirm } from \"@/routes/two-factor\";\n\nfunction GridScanIcon() {\n return (\n
\n
\n
\n {Array.from({ length: 5 }, (_, i) => (\n \n ))}\n
\n
\n {Array.from({ length: 5 }, (_, i) => (\n \n ))}\n
\n \n
\n
\n );\n}\n\nfunction TwoFactorSetupStep({\n qrCodeSvg,\n manualSetupKey,\n recoveryCodesList,\n onConfirmed,\n errors,\n}: {\n qrCodeSvg: string | null;\n manualSetupKey: string | null;\n recoveryCodesList: string[];\n onConfirmed: () => void;\n errors: string[];\n}) {\n const { resolvedAppearance } = useAppearance();\n const [code, setCode] = useState(\"\");\n const pinInputContainerRef = useRef(null);\n\n return (\n <>\n {errors?.length ? (\n \n ) : (\n onConfirmed()}\n onError={toastFirstFormError}\n resetOnError\n resetOnSuccess\n >\n {({\n processing,\n errors: formErrors,\n }: {\n processing: boolean;\n errors?: { confirmTwoFactorAuthentication?: { code?: string } };\n }) => (\n
\n
\n
\n
\n {qrCodeSvg ? (\n \n ) : (\n \n )}\n
\n
\n
\n\n
\n \n Secret key\n \n \n
\n\n
\n

Recovery codes

\n
{recoveryCodesList.length ? recoveryCodesList.join(\"\\n\") : \"Loading recovery codes...\"}
\n
\n\n
\n \n \n \n {Array.from({ length: OTP_MAX_LENGTH }, (_, index) => (\n \n ))}\n \n \n \n
\n\n \n Confirm 2FA\n \n
\n )}\n \n )}\n \n );\n}\n\ntype Props = {\n isOpen: boolean;\n onClose: () => void;\n twoFactorEnabled: boolean;\n qrCodeSvg: string | null;\n manualSetupKey: string | null;\n recoveryCodesList: string[];\n clearSetupData: () => void;\n fetchSetupData: () => Promise;\n errors: string[];\n};\n\nexport default function TwoFactorSetupModal({\n isOpen,\n onClose,\n twoFactorEnabled,\n qrCodeSvg,\n manualSetupKey,\n recoveryCodesList,\n clearSetupData,\n fetchSetupData,\n errors,\n}: Props) {\n const modalConfig = useMemo<{\n title: string;\n description: string;\n }>(() => {\n if (twoFactorEnabled) {\n return {\n title: \"Two-factor authentication enabled\",\n description:\n \"Two-factor authentication is now enabled for your account.\",\n };\n }\n\n return {\n title: \"Set up 2FA\",\n description:\n \"Scan the QR code with your authenticator app, save the recovery codes, then enter the generated code to finish setup.\",\n };\n }, [twoFactorEnabled]);\n\n const resetModalState = useCallback(() => {\n clearSetupData();\n }, [clearSetupData]);\n\n const handleClose = useCallback(() => {\n resetModalState();\n onClose();\n }, [onClose, resetModalState]);\n\n const fetchSetupDataRef = useRef(fetchSetupData);\n\n useEffect(() => {\n fetchSetupDataRef.current = fetchSetupData;\n }, [fetchSetupData]);\n\n useEffect(() => {\n if (isOpen && !qrCodeSvg) {\n fetchSetupDataRef.current();\n }\n }, [isOpen, qrCodeSvg]);\n\n return (\n !open && handleClose()}>\n \n \n \n {modalConfig.title}\n \n {modalConfig.description}\n \n \n\n
\n \n
\n
\n
\n );\n}\n", "type": "registry:component" } ], "type": "registry:component" }