{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "passkey-register", "dependencies": [ "@laravel/passkeys", "@inertiajs/react" ], "registryDependencies": [ "@craft/action-confirmation-provider", "@craft/use-error-toast", "button", "input", "label" ], "files": [ { "path": "registry/new-york-v4/components/passkey-register.tsx", "content": "import { Passkeys } from \"@laravel/passkeys\";\nimport { useState } from \"react\";\nimport InputError from \"@/components/input-error\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport { useErrorToast } from \"@/hooks/use-error-toast\";\nimport { useActionConfirmation } from \"@/components/action-confirmation-provider\";\n\ntype Props = {\n onSuccess: () => void;\n};\n\nconst isActionConfirmationCancelled = (error: unknown): boolean => {\n return error instanceof Error && error.name === \"ActionConfirmationCancelledError\";\n};\n\nexport default function PasskeyRegistration({ onSuccess }: Props) {\n const { confirmSensitiveAction } = useActionConfirmation();\n const [name, setName] = useState(() => {\n const ua = navigator.userAgent;\n\n const browser = [\n { pattern: /Edg|Edge/, name: \"Edge\" },\n { pattern: /OPR|Opera|OPiOS/, name: \"Opera\" },\n { pattern: /Firefox|FxiOS/, name: \"Firefox\" },\n { pattern: /Chrome|CriOS/, name: \"Chrome\" },\n { pattern: /Safari/, name: \"Safari\" },\n ].find(({ pattern }) => pattern.test(ua))?.name;\n\n const os = [\n { pattern: /iPhone/, name: \"iPhone\" },\n { pattern: /iPad|Macintosh(?=.*Mobile)/, name: \"iPad\" },\n { pattern: /Android/, name: \"Android\" },\n { pattern: /Mac/, name: \"Mac\" },\n { pattern: /Windows/, name: \"Windows\" },\n ].find(({ pattern }) => pattern.test(ua))?.name;\n\n return [browser, os].filter(Boolean).join(\" on \") || \"\";\n });\n\n const [showForm, setShowForm] = useState(false);\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState(null);\n const isSupported = Passkeys.isSupported();\n\n useErrorToast(error);\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n\n if (!name.trim()) {\n return;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n await confirmSensitiveAction(() => Passkeys.register({ name }));\n setName(\"\");\n setShowForm(false);\n onSuccess();\n } catch (error) {\n if (!isActionConfirmationCancelled(error)) {\n setError(error instanceof Error ? error.message : \"Unable to register passkey.\");\n }\n } finally {\n setIsLoading(false);\n }\n };\n\n const handleCancel = () => {\n setShowForm(false);\n setName(\"\");\n setError(null);\n };\n\n if (!isSupported) {\n return (\n
\n Passkeys are not supported in this browser.\n
\n );\n }\n\n if (!showForm) {\n return (\n \n );\n }\n\n return (\n \n
\n \n setName(e.target.value)}\n placeholder=\"e.g., MacBook Pro, iPhone\"\n className=\"mt-1 block w-full border-foreground/20\"\n autoFocus\n />\n

\n A name helps you identify this passkey later.\n

\n
\n\n {error && }\n\n
\n \n \n
\n \n );\n}\n", "type": "registry:component" } ], "type": "registry:component" }