From 6345b44eebc25940e131827fb5db1b7072d4934b Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Tue, 6 Jan 2026 16:20:08 +0000 Subject: [PATCH] Changes --- src/components/security/AddPasskeyWizard.tsx | 184 +++++++++++++++++++ src/pages/user/SecurityPage.tsx | 18 +- 2 files changed, 198 insertions(+), 4 deletions(-) create mode 100644 src/components/security/AddPasskeyWizard.tsx diff --git a/src/components/security/AddPasskeyWizard.tsx b/src/components/security/AddPasskeyWizard.tsx new file mode 100644 index 0000000..0ba6fd0 --- /dev/null +++ b/src/components/security/AddPasskeyWizard.tsx @@ -0,0 +1,184 @@ +import { useState } from "react"; +import { Fingerprint, Loader2, CheckCircle, AlertCircle } from "lucide-react"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; + +interface AddPasskeyWizardProps { + open: boolean; + onOpenChange: (open: boolean) => void; + onSuccess?: (passkey: { id: string; name: string }) => void; +} + +type WizardStep = "name" | "registering" | "success" | "error"; + +export function AddPasskeyWizard({ open, onOpenChange, onSuccess }: AddPasskeyWizardProps) { + const [step, setStep] = useState("name"); + const [passkeyName, setPasskeyName] = useState(""); + const [error, setError] = useState(null); + + const handleStartRegistration = async () => { + if (!passkeyName.trim()) return; + + setStep("registering"); + setError(null); + + try { + // Simulate WebAuthn registration flow + // In production, this would call the backend to get challenge options, + // then call navigator.credentials.create() + await new Promise((resolve) => setTimeout(resolve, 2000)); + + // Simulate success + setStep("success"); + + // Notify parent after a short delay + setTimeout(() => { + onSuccess?.({ id: crypto.randomUUID(), name: passkeyName.trim() }); + }, 1500); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to register passkey"); + setStep("error"); + } + }; + + const handleClose = () => { + onOpenChange(false); + // Reset state after dialog closes + setTimeout(() => { + setStep("name"); + setPasskeyName(""); + setError(null); + }, 200); + }; + + const handleRetry = () => { + setStep("name"); + setError(null); + }; + + return ( + + + + + + Add Passkey + + + {step === "name" && "Register a passkey for passwordless sign-in"} + {step === "registering" && "Follow your browser's prompts to register"} + {step === "success" && "Passkey registered successfully"} + {step === "error" && "Registration failed"} + + + +
+ {step === "name" && ( +
+
+ + setPasskeyName(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter" && passkeyName.trim()) { + handleStartRegistration(); + } + }} + /> +

+ Give this passkey a name to help you identify it later +

+
+ +
+

What happens next

+
    +
  • • Your browser will prompt you to authenticate
  • +
  • • Use Touch ID, Face ID, or your security key
  • +
  • • The passkey will be stored on this device
  • +
+
+ +
+ + +
+
+ )} + + {step === "registering" && ( +
+
+ +
+
+

Waiting for authentication

+

+ Follow the prompts from your browser or device +

+
+ +
+ )} + + {step === "success" && ( +
+
+ +
+
+

Passkey added

+

+ "{passkeyName}" is now registered and ready to use +

+
+ +
+ )} + + {step === "error" && ( +
+
+ +
+
+

Registration failed

+

+ {error || "Unable to register passkey. Please try again."} +

+
+
+ + +
+
+ )} +
+
+
+ ); +} diff --git a/src/pages/user/SecurityPage.tsx b/src/pages/user/SecurityPage.tsx index 7347fae..218d0bb 100644 --- a/src/pages/user/SecurityPage.tsx +++ b/src/pages/user/SecurityPage.tsx @@ -1,14 +1,15 @@ import { useState } from "react"; -import { Lock, Fingerprint, Smartphone, Shield, Plus, AlertTriangle, CheckCircle } from "lucide-react"; +import { Lock, Fingerprint, Smartphone, Shield, Plus, CheckCircle } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; -import { Switch } from "@/components/ui/switch"; +import { AddPasskeyWizard } from "@/components/security/AddPasskeyWizard"; export default function SecurityPage() { const [showPasswordForm, setShowPasswordForm] = useState(false); + const [showAddPasskey, setShowAddPasskey] = useState(false); // Mock security data const security = { @@ -145,11 +146,11 @@ export default function SecurityPage() { Required )} - + Use biometrics or security keys for passwordless login - @@ -180,6 +181,15 @@ export default function SecurityPage() { + + { + console.log("Passkey added:", passkey); + setShowAddPasskey(false); + }} + /> ); }