UX Move 1: kill the signup wall — one-tap silent account, no forced key download

- "Sign Up" now goes straight into a one-tap flow: a key is generated silently
  and the user is logged in immediately, landing on optional profile setup.
- Removed the method-choice dialog (mnemonic vs nsec) and the generate/download
  steps from the default path — no crypto vocabulary, no gates.
- Removed the forced key-download gate (Continue was disabled until download).
  Backup is now optional and deferred (BackupReminder already nudges post-login;
  the "done" step offers a gentle, jargon-free recovery-code reveal).
- Softened copy throughout: "secret key" -> "recovery code / account backup",
  "your identity on Nostr" -> plain reassurance, CTA -> "Save & Start Skating".
- Advanced signer-app path preserved under a subtle link.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
shakespeare.diy 2026-08-27 11:03:26 +07:00
parent 33cfe692ed
commit 54e6b4a809
2 changed files with 97 additions and 453 deletions

View file

@ -40,7 +40,7 @@ export function LoginArea({ className, compact }: LoginAreaProps) {
// Compact mode for mobile headers - icon only
<div className="flex gap-1 justify-center">
<Button
onClick={() => setSignupMethodDialogOpen(true)}
onClick={() => setNsecSignupDialogOpen(true)}
size="icon"
className='h-9 w-9 rounded-full bg-[#ccff00] text-black hover:bg-[#ccff00]/90'
title="Sign Up"
@ -61,7 +61,7 @@ export function LoginArea({ className, compact }: LoginAreaProps) {
// Default full-size buttons
<div className="flex gap-3 justify-center w-full">
<Button
onClick={() => setSignupMethodDialogOpen(true)}
onClick={() => setNsecSignupDialogOpen(true)}
className='flex items-center gap-2 px-5 py-2.5 rounded-full bg-[#ccff00] text-black hover:bg-[#ccff00]/90 font-bold transition-all flex-1 sm:flex-none'
>
<UserPlus className='w-4 h-4' />
@ -82,7 +82,7 @@ export function LoginArea({ className, compact }: LoginAreaProps) {
isOpen={loginDialogOpen}
onClose={() => setLoginDialogOpen(false)}
onLogin={handleLogin}
onSignup={() => setSignupMethodDialogOpen(true)}
onSignup={() => setNsecSignupDialogOpen(true)}
/>
<SignupMethodDialog

View file

@ -3,7 +3,7 @@
import React, { useState, useEffect, useRef } from 'react';
import { Link } from 'react-router-dom';
import { Download, Key, UserPlus, FileText, Shield, User, Sparkles, LogIn, CheckCircle, Upload, Globe, Eye, EyeOff, ExternalLink } from 'lucide-react';
import { Download, Key, Shield, User, Sparkles, LogIn, CheckCircle, Upload } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
@ -13,7 +13,7 @@ import { toast } from '@/hooks/useToast';
import { useLoginActions } from '@/hooks/useLoginActions';
import { useNostrPublish } from '@/hooks/useNostrPublish';
import { useUploadFile } from '@/hooks/useUploadFile';
import { generateSecretKey, getPublicKey, nip19 } from 'nostr-tools';
import { generateSecretKey, nip19 } from 'nostr-tools';
import { cn } from '@/lib/utils';
interface SignupDialogProps {
@ -27,13 +27,10 @@ const sanitizeFilename = (filename: string) => {
}
const SignupDialog: React.FC<SignupDialogProps> = ({ isOpen, onClose, onComplete }) => {
const [step, setStep] = useState<'welcome' | 'generate' | 'download' | 'profile' | 'done'>('welcome');
const [isLoading, setIsLoading] = useState(false);
const [step, setStep] = useState<'profile' | 'done'>('profile');
const [nsec, setNsec] = useState('');
const [showSparkles, setShowSparkles] = useState(false);
const [keySecured, setKeySecured] = useState<'none' | 'downloaded'>('none');
const [showNsec, setShowNsec] = useState(false);
const [showNpub, setShowNpub] = useState(false);
const [profileData, setProfileData] = useState({
name: '',
about: '',
@ -44,36 +41,24 @@ const SignupDialog: React.FC<SignupDialogProps> = ({ isOpen, onClose, onComplete
const { mutateAsync: uploadFile, isPending: isUploading } = useUploadFile();
const avatarFileInputRef = useRef<HTMLInputElement>(null);
// Generate a proper nsec key using nostr-tools
const generateKey = () => {
setIsLoading(true);
setShowSparkles(true);
// Add a dramatic pause for the key generation effect
setTimeout(() => {
// Silently generate a key and log the user in immediately.
// No forced download, no crypto vocabulary — the account "just works".
// Backup is deferred to the BackupReminder (shown a couple seconds after login).
const createAccountSilently = () => {
try {
// Generate a new secret key
const sk = generateSecretKey();
// Convert to nsec format
setNsec(nip19.nsecEncode(sk));
setStep('download');
toast({
title: 'Your Secret Key is Ready!',
description: 'A new secret key has been generated for you.',
});
const newNsec = nip19.nsecEncode(sk);
setNsec(newNsec);
login.nsec(newNsec);
return true;
} catch {
toast({
title: 'Error',
description: 'Failed to generate key. Please try again.',
title: 'Something went wrong',
description: "Couldn't create your account. Please try again.",
variant: 'destructive',
});
} finally {
setIsLoading(false);
setShowSparkles(false);
return false;
}
}, 2000);
};
const downloadKey = () => {
@ -115,19 +100,6 @@ const SignupDialog: React.FC<SignupDialogProps> = ({ isOpen, onClose, onComplete
const finishKeySetup = () => {
try {
login.nsec(nsec);
setStep('profile');
} catch {
toast({
title: 'Login Failed',
description: 'Failed to login with the generated key. Please try again.',
variant: 'destructive',
});
}
};
const handleAvatarUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
@ -227,24 +199,9 @@ const SignupDialog: React.FC<SignupDialogProps> = ({ isOpen, onClose, onComplete
};
const getTitle = () => {
if (step === 'welcome') return (
<span className="flex items-center justify-center gap-2">
Create Your Account
</span>
);
if (step === 'generate') return (
<span className="flex items-center justify-center gap-2">
Generating Your Key
</span>
);
if (step === 'download') return (
<span className="flex items-center justify-center gap-2">
Secret Key
</span>
);
if (step === 'profile') return (
<span className="flex items-center justify-center gap-2">
Create Your Profile
Set Up Your Profile
</span>
);
return (
@ -254,30 +211,20 @@ const SignupDialog: React.FC<SignupDialogProps> = ({ isOpen, onClose, onComplete
);
};
// Reset state when dialog opens
// Reset state when dialog opens, and silently create + log in the account
// so the skater lands straight on profile setup — no crypto steps, no gates.
useEffect(() => {
if (isOpen) {
setStep('welcome');
setIsLoading(false);
setStep('profile');
setNsec('');
setShowSparkles(false);
setKeySecured('none');
setShowNsec(false);
setShowNpub(false);
setProfileData({ name: '', about: '', picture: '' });
createAccountSilently();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isOpen]);
// Add sparkle animation effect
useEffect(() => {
if (showSparkles) {
const interval = setInterval(() => {
// This will trigger re-renders for sparkle animation
}, 100);
return () => clearInterval(interval);
}
}, [showSparkles]);
return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent
@ -289,350 +236,6 @@ const SignupDialog: React.FC<SignupDialogProps> = ({ isOpen, onClose, onComplete
</DialogTitle>
</DialogHeader>
<div className='px-4 sm:px-6 pt-2 pb-4 space-y-4 overflow-y-auto flex-1'>
{/* Welcome Step - New engaging introduction */}
{step === 'welcome' && (
<div className='text-center space-y-4'>
{/* Hero illustration */}
<div className='relative p-6 rounded-2xl bg-gradient-to-br from-blue-50 to-indigo-100 dark:from-blue-950/50 dark:to-indigo-950/50'>
<div className='flex justify-center items-center space-x-4 mb-3'>
<div className='relative'>
<UserPlus className='w-12 h-12 text-blue-600' />
<Sparkles className='w-4 h-4 text-yellow-500 absolute -top-1 -right-1 animate-pulse' />
</div>
<Globe className='w-16 h-16 text-blue-700 animate-spin-slow' />
<div className='relative'>
<FileText className='w-12 h-12 text-blue-600' />
<Sparkles className='w-4 h-4 text-yellow-500 absolute -top-1 -left-1 animate-pulse' style={{animationDelay: '0.3s'}} />
</div>
</div>
{/* Benefits */}
<div className='grid grid-cols-1 gap-2 text-sm'>
<div className='flex items-center justify-center gap-2 text-blue-700 dark:text-blue-300'>
<Shield className='w-4 h-4' />
Community-run, skater-owned
</div>
<div className='flex items-center justify-center gap-2 text-blue-700 dark:text-blue-300'>
<User className='w-4 h-4' />
You control your account
</div>
<div className='flex items-center justify-center gap-2 text-blue-700 dark:text-blue-300'>
<Globe className='w-4 h-4' />
Connect with skaters worldwide
</div>
</div>
</div>
<div className='space-y-3'>
<Button
className='w-full rounded-full py-6 text-lg font-bold uppercase bg-[#ccff00] text-black hover:bg-[#ccff00]/90 transform transition-all duration-200 hover:scale-105 shadow-[0_0_20px_rgba(204,255,0,0.5)]'
onClick={() => setStep('generate')}
>
<LogIn className='w-5 h-5 mr-2' />
Get Started
</Button>
<a href="/onboarding" onClick={onClose}>
<Button
variant="outline"
className='w-full rounded-full py-3 border-2 border-[#ff0040]/50 hover:bg-[#ff0040]/10 text-[#ff0040] font-bold uppercase'
>
<Sparkles className='w-4 h-4 mr-2' />
Need Help? View Guide
</Button>
</a>
</div>
</div>
)}
{/* Generate Step - Enhanced with animations */}
{step === 'generate' && (
<div className='text-center space-y-4'>
<div className='relative p-6 rounded-2xl bg-gradient-to-br from-blue-50 to-purple-100 dark:from-blue-950/50 dark:to-purple-950/50 overflow-hidden'>
{/* Animated background elements */}
{showSparkles && (
<div className='absolute inset-0'>
{[...Array(12)].map((_, i) => (
<Sparkles
key={i}
className={`absolute w-4 h-4 text-yellow-400 animate-ping`}
style={{
left: `${Math.random() * 80 + 10}%`,
top: `${Math.random() * 80 + 10}%`,
animationDelay: `${Math.random() * 2}s`
}}
/>
))}
</div>
)}
<div className='relative z-10'>
{isLoading ? (
<div className='space-y-3'>
<div className='relative'>
<Key className='w-20 h-20 text-primary mx-auto animate-pulse' />
<div className='absolute inset-0 flex items-center justify-center'>
<div className='w-24 h-24 border-4 border-yellow-400 border-t-transparent rounded-full animate-spin'></div>
</div>
</div>
<div className='space-y-2'>
<p className='text-lg font-semibold text-primary flex items-center justify-center gap-2'>
<Sparkles className='w-5 h-5' />
Generating your secret key...
</p>
<p className='text-sm text-muted-foreground'>
Creating your secure key
</p>
</div>
</div>
) : (
<div className='space-y-3'>
<Key className='w-20 h-20 text-primary mx-auto' />
<div className='space-y-2'>
<p className='text-lg font-semibold'>
Ready to generate your secret key?
</p>
<p className='text-sm text-muted-foreground px-5'>
This key will be your password to access applications within the Nostr network.
</p>
</div>
</div>
)}
</div>
</div>
{!isLoading && (
<Button
className='w-full rounded-full py-6 text-lg font-semibold bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-700 hover:to-blue-700 transform transition-all duration-200 hover:scale-105 shadow-lg'
onClick={generateKey}
disabled={isLoading}
>
<Sparkles className='w-5 h-5 mr-2' />
Generate My Secret Key
</Button>
)}
</div>
)}
{/* Download Step - Whimsical and magical */}
{step === 'download' && (
<div className='text-center space-y-4'>
{/* Key reveal */}
<div className='relative p-6 rounded-2xl bg-gradient-to-br from-blue-50 to-indigo-100 dark:from-blue-950/50 dark:to-indigo-950/50 overflow-hidden'>
{/* Sparkles */}
<div className='absolute inset-0 pointer-events-none'>
<Sparkles className='absolute top-3 left-4 w-3 h-3 text-yellow-400 animate-pulse' style={{animationDelay: '0s'}} />
<Sparkles className='absolute top-6 right-6 w-3 h-3 text-yellow-500 animate-pulse' style={{animationDelay: '0.5s'}} />
<Sparkles className='absolute bottom-4 left-6 w-3 h-3 text-yellow-400 animate-pulse' style={{animationDelay: '1s'}} />
<Sparkles className='absolute bottom-3 right-4 w-3 h-3 text-yellow-500 animate-pulse' style={{animationDelay: '1.5s'}} />
</div>
<div className='relative z-10 flex justify-center items-center mb-3'>
<div className='relative'>
<div className='w-16 h-16 bg-gradient-to-br from-blue-200 to-indigo-300 rounded-full flex items-center justify-center shadow-lg animate-pulse'>
<Key className='w-8 h-8 text-indigo-800' />
</div>
<div className='absolute -top-1 -right-1 w-5 h-5 bg-blue-500 rounded-full flex items-center justify-center animate-bounce'>
<Sparkles className='w-3 h-3 text-white' />
</div>
</div>
</div>
<div className='relative z-10 space-y-2'>
<p className='text-base font-semibold'>
Your secret key has been generated!
</p>
{/* Warning */}
<div className='relative mx-auto max-w-sm'>
<div className='p-3 bg-gradient-to-r from-amber-100 via-yellow-50 to-amber-100 dark:from-amber-950/40 dark:via-yellow-950/20 dark:to-amber-950/40 rounded-lg border-2 border-amber-300 dark:border-amber-700 shadow-md'>
<div className='flex items-center gap-2 mb-1'>
<FileText className='w-3 h-3 text-amber-700' />
<span className='text-xs font-bold text-amber-800 dark:text-amber-200'>
Important Warning
</span>
</div>
<p className='text-xs text-red-700 dark:text-amber-300 italic'>
This key is your primary and only means of accessing your account. Store it safely and securely.
</p>
</div>
</div>
</div>
</div>
{/* Your Keys - nsec and npub with copy buttons and privacy controls */}
<Card className="bg-black/50 border-[#ff0040]/30">
<CardContent className="p-4 space-y-3">
{/* nsec (secret key) */}
<div className="space-y-2">
<div className="flex items-center justify-between">
<label className="text-xs font-bold uppercase text-[#ff0040]">🔐 Secret Key (nsec)</label>
<div className="flex gap-1">
<Button
variant="ghost"
size="sm"
className="h-auto py-1 px-2 text-xs hover:bg-[#ff0040]/10 text-[#ff0040] font-bold"
onClick={() => setShowNsec(!showNsec)}
title={showNsec ? "Hide secret key" : "Show secret key"}
>
{showNsec ? <EyeOff className="h-3 w-3" /> : <Eye className="h-3 w-3" />}
</Button>
<Button
variant="ghost"
size="sm"
className="h-auto py-1 px-2 text-xs hover:bg-[#ff0040]/10 text-[#ff0040] font-bold"
onClick={() => {
navigator.clipboard.writeText(nsec);
toast({ title: 'Copied!', description: 'Secret key copied to clipboard' });
}}
>
Copy
</Button>
</div>
</div>
<div className="p-2 bg-black rounded border border-[#ff0040]/20 font-mono text-xs break-all text-white">
{showNsec ? nsec : '••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••'}
</div>
<p className="text-xs text-yellow-400">⚠️ This is your PASSWORD - keep it secret and safe!</p>
</div>
{/* npub (public key) */}
<div className="space-y-2">
<div className="flex items-center justify-between">
<label className="text-xs font-bold uppercase text-[#ccff00]">👤 Public Key (npub)</label>
<div className="flex gap-1">
<Button
variant="ghost"
size="sm"
className="h-auto py-1 px-2 text-xs hover:bg-[#ccff00]/10 text-[#ccff00] font-bold"
onClick={() => setShowNpub(!showNpub)}
title={showNpub ? "Hide public key" : "Show public key"}
>
{showNpub ? <EyeOff className="h-3 w-3" /> : <Eye className="h-3 w-3" />}
</Button>
<Button
variant="ghost"
size="sm"
className="h-auto py-1 px-2 text-xs hover:bg-[#ccff00]/10 text-[#ccff00] font-bold"
onClick={() => {
try {
const { data } = nip19.decode(nsec);
const pk = getPublicKey(data as Uint8Array);
const npub = nip19.npubEncode(pk);
navigator.clipboard.writeText(npub);
toast({ title: 'Copied!', description: 'Public key copied to clipboard' });
} catch (error) {
toast({ title: 'Error', description: 'Failed to copy public key', variant: 'destructive' });
}
}}
>
Copy
</Button>
</div>
</div>
<div className="p-2 bg-black rounded border border-[#ccff00]/20 font-mono text-xs break-all text-white">
{showNpub ? (() => {
try {
const { data } = nip19.decode(nsec);
const pk = getPublicKey(data as Uint8Array);
return nip19.npubEncode(pk);
} catch {
return 'Error generating npub';
}
})() : '••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••'}
</div>
<p className="text-xs text-gray-400">✓ This is your USERNAME - safe to share publicly</p>
</div>
<div className="pt-2 text-xs text-gray-300 border-t border-gray-700">
<p className="mb-1"><strong>What's the difference?</strong></p>
<ul className="space-y-1 ml-4 list-disc">
<li><strong className="text-[#ff0040]">nsec</strong> = Your password. Never share this!</li>
<li><strong className="text-[#ccff00]">npub</strong> = Your username. Share this with friends!</li>
</ul>
</div>
</CardContent>
</Card>
{/* Security options */}
<div className='space-y-3'>
<p className="text-sm text-center text-gray-300 font-bold">Now download your secret key to keep it safe:</p>
<div className='grid grid-cols-1 gap-2'>
{/* Download Option */}
<Card className={`cursor-pointer transition-all duration-200 border-2 ${
keySecured === 'downloaded'
? 'ring-2 ring-green-500 bg-green-950/20 border-green-500/50'
: 'hover:bg-[#ff0040]/5 hover:border-[#ff0040]/20 border-[#ff0040]/20'
}`}>
<CardContent className='p-3'>
<Button
variant="ghost"
className='w-full h-auto p-0 justify-start hover:bg-transparent'
onClick={downloadKey}
>
<div className='flex items-center gap-3 w-full'>
<div className={`p-1.5 rounded-lg border-2 ${
keySecured === 'downloaded'
? 'bg-green-900 border-green-500'
: 'bg-[#ff0040]/10 border-[#ff0040]/30'
}`}>
{keySecured === 'downloaded' ? (
<CheckCircle className='w-4 h-4 text-green-400' />
) : (
<Download className='w-4 h-4 text-[#ff0040]' />
)}
</div>
<div className='flex-1 text-left'>
<div className='font-bold text-sm uppercase text-white'>
Download as File
</div>
<div className='text-xs text-gray-400'>
Save as nostr-nsec-key.txt file
</div>
</div>
{keySecured === 'downloaded' && (
<div className='text-xs font-bold text-green-400 uppercase'>
✓ Downloaded
</div>
)}
</div>
</Button>
</CardContent>
</Card>
</div>
{/* Continue button */}
<Button
className={`w-full rounded-full py-6 text-base font-bold uppercase transform transition-all duration-200 shadow-lg ${
keySecured === 'downloaded'
? 'bg-[#ccff00] text-black hover:bg-[#ccff00]/90 border-2 border-[#ccff00] shadow-[0_0_20px_rgba(204,255,0,0.5)] hover:scale-105'
: 'bg-gray-700 text-gray-400 cursor-not-allowed border-2 border-gray-600'
}`}
onClick={finishKeySetup}
disabled={keySecured !== 'downloaded'}
>
<LogIn className='w-5 h-5 mr-2 flex-shrink-0' />
<span className="text-center leading-tight">
{keySecured === 'none' ? (
<>
Download Your Key First
</>
) : (
<>
My Key is Safe - Continue
</>
)}
</span>
</Button>
</div>
</div>
)}
{/* Profile Step - Optional profile setup */}
{step === 'profile' && (
<div className='text-center space-y-4'>
@ -658,11 +261,11 @@ const SignupDialog: React.FC<SignupDialogProps> = ({ isOpen, onClose, onComplete
<div className='relative z-10 space-y-2'>
<p className='text-base font-semibold'>
Almost there! Let's set up your profile
You're in! 🛹 Add a name so skaters know who you are
</p>
<p className='text-sm text-muted-foreground'>
Your profile is your identity on Nostr.
This is optional — you can skip it and fill it in later.
</p>
</div>
</div>
@ -759,12 +362,12 @@ const SignupDialog: React.FC<SignupDialogProps> = ({ isOpen, onClose, onComplete
{isPublishing ? (
<>
<div className='w-4 h-4 mr-2 border-2 border-current border-t-transparent rounded-full animate-spin' />
Creating Profile...
Saving...
</>
) : (
<>
<User className='w-4 h-4 mr-2' />
Create Profile & Finish
Save & Start Skating
</>
)}
</Button>
@ -788,50 +391,91 @@ const SignupDialog: React.FC<SignupDialogProps> = ({ isOpen, onClose, onComplete
</div>
)}
{/* Done Step - Account created with signer recommendation */}
{/* Done Step - Account created, gentle optional backup */}
{step === 'done' && (
<div className='py-4 space-y-4'>
<Card className='bg-gradient-to-br from-[#ccff00]/10 to-[#00ff00]/10 border-2 border-[#ccff00]/50'>
<CardContent className='p-6 text-center space-y-3'>
<div className='text-5xl mb-2'>✅</div>
<div className='text-5xl mb-2'>🛹</div>
<h3 className='text-xl font-black uppercase text-[#ccff00]'>
Account Created!
You're all set!
</h3>
<p className='text-gray-300 text-sm'>
Your Nostr account is ready to go!
Your account is ready. Go find some spots.
</p>
<CheckCircle className='h-10 w-10 mx-auto text-[#00ff00]' />
</CardContent>
</Card>
{/* Next Steps - Signer Recommendation */}
<Card className='bg-blue-950/30 border-2 border-blue-500/30'>
{/* Optional backup — no pressure, no jargon */}
<Card className='bg-amber-950/20 border-2 border-amber-500/30'>
<CardContent className='p-5 space-y-3'>
<div className='flex items-center gap-3'>
<div className='p-2 rounded-lg bg-blue-500/20'>
<Shield className='h-5 w-5 text-blue-400' />
<div className='p-2 rounded-lg bg-amber-500/20'>
<Shield className='h-5 w-5 text-amber-400' />
</div>
<div>
<h4 className='font-bold text-blue-400'>Recommended: Set Up a Signer App</h4>
<p className='text-xs text-gray-400'>Keep your keys extra secure</p>
<h4 className='font-bold text-amber-400'>Save a backup (optional)</h4>
<p className='text-xs text-gray-400'>So you never lose access to your account</p>
</div>
</div>
<p className='text-sm text-gray-300'>
For better security, use a dedicated signer app instead of pasting your secret key everywhere.
Signer apps keep your keys safe while letting you use any Nostr app.
Your account lives on your device. Save a recovery code somewhere safe and
you'll be able to log back in on any device. You can also do this later from Settings.
</p>
<Link to='/signers' onClick={handleFinishComplete}>
<Button className='w-full bg-blue-500/20 text-blue-400 hover:bg-blue-500/30 border border-blue-500/50'>
<ExternalLink className='h-4 w-4 mr-2' />
Learn About Signer Apps
</Button>
</Link>
<button
onClick={handleFinishComplete}
className='w-full text-xs text-gray-500 hover:text-gray-400 py-2'
{!showNsec ? (
<Button
variant='outline'
className='w-full border-amber-500/50 text-amber-400 hover:bg-amber-500/10'
onClick={() => setShowNsec(true)}
>
Skip for now
<Key className='h-4 w-4 mr-2' />
Show my recovery code
</Button>
) : (
<div className='space-y-2'>
<div className='p-2 bg-black rounded border border-amber-500/20 font-mono text-xs break-all text-white'>
{nsec}
</div>
<div className='grid grid-cols-2 gap-2'>
<Button
variant='outline'
size='sm'
className='border-amber-500/40 text-amber-300 hover:bg-amber-500/10'
onClick={() => {
navigator.clipboard.writeText(nsec);
toast({ title: 'Copied!', description: 'Recovery code copied. Keep it somewhere safe.' });
}}
>
Copy
</Button>
<Button
variant='outline'
size='sm'
className='border-amber-500/40 text-amber-300 hover:bg-amber-500/10'
onClick={downloadKey}
>
<Download className='h-3.5 w-3.5 mr-1.5' />
{keySecured === 'downloaded' ? 'Saved ✓' : 'Download'}
</Button>
</div>
<p className='text-xs text-amber-300/80'>⚠️ Anyone with this code can access your account. Never share it.</p>
</div>
)}
<Button
className='w-full rounded-full py-3 font-bold uppercase bg-[#ccff00] text-black hover:bg-[#ccff00]/90'
onClick={handleFinishComplete}
>
<LogIn className='h-4 w-4 mr-2' />
Start Skating
</Button>
<Link to='/signers' onClick={handleFinishComplete} className='block'>
<button className='w-full text-xs text-gray-500 hover:text-gray-400 py-1'>
Advanced: use a signer app instead
</button>
</Link>
</CardContent>
</Card>
</div>