TypeScript 97.6%
SQL 1.4%
JavaScript 0.5%
1"use client";23import { useState } from "react";4import { Copy, Check, KeyRound, TriangleAlert } from "lucide-react";5import { Button } from "@/components/ui";6import { cn } from "@/lib/utils";78const NOTICE = "Save this recovery code. Spinza does not collect your email address. If you lose your password and recovery code, your account cannot be recovered.";910/** Prominent recovery-code display with copy button and a save-confirmation gate. */11export function RecoveryCodePanel({ code, title = "Your recovery code", notice = NOTICE, continueLabel = "Continue", onContinue, loading, className, hideHeader }: { code: string; title?: string; notice?: string; continueLabel?: string; onContinue: () => void; loading?: boolean; className?: string; hideHeader?: boolean }) {12 const [copied, setCopied] = useState(false);13 const [saved, setSaved] = useState(false);1415 const copy = async () => {16 try {17 await navigator.clipboard.writeText(code);18 setCopied(true);19 setTimeout(() => setCopied(false), 1800);20 } catch {21 // Clipboard unavailable (insecure context): user can still select the code.22 }23 };2425 return (26 <div className={cn("space-y-5", className)}>27 {hideHeader ? null : (28 <div className="flex items-center gap-3">29 <span className="grid h-11 w-11 shrink-0 place-items-center rounded-md bg-accent text-bg">30 <KeyRound className="h-5 w-5" />31 </span>32 <div>33 <h1 className="text-xl font-semibold tracking-tight">{title}</h1>34 <p className="text-[13px] text-fg-3">This code is the only way to reset your password.</p>35 </div>36 </div>37 )}3839 <div className="metal rounded-lg p-4">40 <div className="eyebrow mb-2">Recovery code</div>41 <div className="flex items-center justify-between gap-3">42 <code className="min-w-0 select-all whitespace-nowrap font-mono text-[17px] font-semibold tracking-[0.03em] text-accent-2 sm:text-2xl sm:tracking-[0.06em]" aria-label={`Recovery code ${code}`}>43 {code}44 </code>45 <Button type="button" variant="secondary" size="icon" onClick={copy} aria-label="Copy recovery code" className="shrink-0">46 {copied ? <Check className="h-4 w-4 text-success" /> : <Copy className="h-4 w-4" />}47 </Button>48 </div>49 </div>5051 <div className="flex gap-3 rounded-md border border-accent/30 bg-accent-soft p-4 text-[13px] leading-relaxed text-fg-2" role="note">52 <TriangleAlert className="mt-0.5 h-4 w-4 shrink-0 text-accent" />53 <p>{notice}</p>54 </div>5556 <label className="flex cursor-pointer items-start gap-3 rounded-md border border-line p-3.5 tap hover:bg-surface">57 <input type="checkbox" checked={saved} onChange={(e) => setSaved(e.target.checked)} className="mt-0.5 h-5 w-5 shrink-0 accent-[#c9a961]" />58 <span className="text-[15px] font-medium">I saved my recovery code</span>59 </label>6061 <Button size="lg" variant="accent" className="w-full" disabled={!saved} loading={loading} onClick={onContinue}>62 {continueLabel}63 </Button>64 </div>65 );66}67