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

View file

@ -3,7 +3,7 @@
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import { Link } from 'react-router-dom'; 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 { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea'; import { Textarea } from '@/components/ui/textarea';
@ -13,7 +13,7 @@ import { toast } from '@/hooks/useToast';
import { useLoginActions } from '@/hooks/useLoginActions'; import { useLoginActions } from '@/hooks/useLoginActions';
import { useNostrPublish } from '@/hooks/useNostrPublish'; import { useNostrPublish } from '@/hooks/useNostrPublish';
import { useUploadFile } from '@/hooks/useUploadFile'; import { useUploadFile } from '@/hooks/useUploadFile';
import { generateSecretKey, getPublicKey, nip19 } from 'nostr-tools'; import { generateSecretKey, nip19 } from 'nostr-tools';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
interface SignupDialogProps { interface SignupDialogProps {
@ -27,13 +27,10 @@ const sanitizeFilename = (filename: string) => {
} }
const SignupDialog: React.FC<SignupDialogProps> = ({ isOpen, onClose, onComplete }) => { const SignupDialog: React.FC<SignupDialogProps> = ({ isOpen, onClose, onComplete }) => {
const [step, setStep] = useState<'welcome' | 'generate' | 'download' | 'profile' | 'done'>('welcome'); const [step, setStep] = useState<'profile' | 'done'>('profile');
const [isLoading, setIsLoading] = useState(false);
const [nsec, setNsec] = useState(''); const [nsec, setNsec] = useState('');
const [showSparkles, setShowSparkles] = useState(false);
const [keySecured, setKeySecured] = useState<'none' | 'downloaded'>('none'); const [keySecured, setKeySecured] = useState<'none' | 'downloaded'>('none');
const [showNsec, setShowNsec] = useState(false); const [showNsec, setShowNsec] = useState(false);
const [showNpub, setShowNpub] = useState(false);
const [profileData, setProfileData] = useState({ const [profileData, setProfileData] = useState({
name: '', name: '',
about: '', about: '',
@ -44,36 +41,24 @@ const SignupDialog: React.FC<SignupDialogProps> = ({ isOpen, onClose, onComplete
const { mutateAsync: uploadFile, isPending: isUploading } = useUploadFile(); const { mutateAsync: uploadFile, isPending: isUploading } = useUploadFile();
const avatarFileInputRef = useRef<HTMLInputElement>(null); const avatarFileInputRef = useRef<HTMLInputElement>(null);
// Generate a proper nsec key using nostr-tools // Silently generate a key and log the user in immediately.
const generateKey = () => { // No forced download, no crypto vocabulary — the account "just works".
setIsLoading(true); // Backup is deferred to the BackupReminder (shown a couple seconds after login).
setShowSparkles(true); const createAccountSilently = () => {
// Add a dramatic pause for the key generation effect
setTimeout(() => {
try { try {
// Generate a new secret key
const sk = generateSecretKey(); const sk = generateSecretKey();
const newNsec = nip19.nsecEncode(sk);
// Convert to nsec format setNsec(newNsec);
setNsec(nip19.nsecEncode(sk)); login.nsec(newNsec);
setStep('download'); return true;
toast({
title: 'Your Secret Key is Ready!',
description: 'A new secret key has been generated for you.',
});
} catch { } catch {
toast({ toast({
title: 'Error', title: 'Something went wrong',
description: 'Failed to generate key. Please try again.', description: "Couldn't create your account. Please try again.",
variant: 'destructive', variant: 'destructive',
}); });
} finally { return false;
setIsLoading(false);
setShowSparkles(false);
} }
}, 2000);
}; };
const downloadKey = () => { 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 handleAvatarUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]; const file = e.target.files?.[0];
if (!file) return; if (!file) return;
@ -227,24 +199,9 @@ const SignupDialog: React.FC<SignupDialogProps> = ({ isOpen, onClose, onComplete
}; };
const getTitle = () => { 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 ( if (step === 'profile') return (
<span className="flex items-center justify-center gap-2"> <span className="flex items-center justify-center gap-2">
Create Your Profile Set Up Your Profile
</span> </span>
); );
return ( 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(() => { useEffect(() => {
if (isOpen) { if (isOpen) {
setStep('welcome'); setStep('profile');
setIsLoading(false);
setNsec(''); setNsec('');
setShowSparkles(false);
setKeySecured('none'); setKeySecured('none');
setShowNsec(false); setShowNsec(false);
setShowNpub(false);
setProfileData({ name: '', about: '', picture: '' }); setProfileData({ name: '', about: '', picture: '' });
createAccountSilently();
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isOpen]); }, [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 ( return (
<Dialog open={isOpen} onOpenChange={onClose}> <Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent <DialogContent
@ -289,350 +236,6 @@ const SignupDialog: React.FC<SignupDialogProps> = ({ isOpen, onClose, onComplete
</DialogTitle> </DialogTitle>
</DialogHeader> </DialogHeader>
<div className='px-4 sm:px-6 pt-2 pb-4 space-y-4 overflow-y-auto flex-1'> <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 */} {/* Profile Step - Optional profile setup */}
{step === 'profile' && ( {step === 'profile' && (
<div className='text-center space-y-4'> <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'> <div className='relative z-10 space-y-2'>
<p className='text-base font-semibold'> <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>
<p className='text-sm text-muted-foreground'> <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> </p>
</div> </div>
</div> </div>
@ -759,12 +362,12 @@ const SignupDialog: React.FC<SignupDialogProps> = ({ isOpen, onClose, onComplete
{isPublishing ? ( {isPublishing ? (
<> <>
<div className='w-4 h-4 mr-2 border-2 border-current border-t-transparent rounded-full animate-spin' /> <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' /> <User className='w-4 h-4 mr-2' />
Create Profile & Finish Save & Start Skating
</> </>
)} )}
</Button> </Button>
@ -788,50 +391,91 @@ const SignupDialog: React.FC<SignupDialogProps> = ({ isOpen, onClose, onComplete
</div> </div>
)} )}
{/* Done Step - Account created with signer recommendation */} {/* Done Step - Account created, gentle optional backup */}
{step === 'done' && ( {step === 'done' && (
<div className='py-4 space-y-4'> <div className='py-4 space-y-4'>
<Card className='bg-gradient-to-br from-[#ccff00]/10 to-[#00ff00]/10 border-2 border-[#ccff00]/50'> <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'> <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]'> <h3 className='text-xl font-black uppercase text-[#ccff00]'>
Account Created! You're all set!
</h3> </h3>
<p className='text-gray-300 text-sm'> <p className='text-gray-300 text-sm'>
Your Nostr account is ready to go! Your account is ready. Go find some spots.
</p> </p>
<CheckCircle className='h-10 w-10 mx-auto text-[#00ff00]' /> <CheckCircle className='h-10 w-10 mx-auto text-[#00ff00]' />
</CardContent> </CardContent>
</Card> </Card>
{/* Next Steps - Signer Recommendation */} {/* Optional backup — no pressure, no jargon */}
<Card className='bg-blue-950/30 border-2 border-blue-500/30'> <Card className='bg-amber-950/20 border-2 border-amber-500/30'>
<CardContent className='p-5 space-y-3'> <CardContent className='p-5 space-y-3'>
<div className='flex items-center gap-3'> <div className='flex items-center gap-3'>
<div className='p-2 rounded-lg bg-blue-500/20'> <div className='p-2 rounded-lg bg-amber-500/20'>
<Shield className='h-5 w-5 text-blue-400' /> <Shield className='h-5 w-5 text-amber-400' />
</div> </div>
<div> <div>
<h4 className='font-bold text-blue-400'>Recommended: Set Up a Signer App</h4> <h4 className='font-bold text-amber-400'>Save a backup (optional)</h4>
<p className='text-xs text-gray-400'>Keep your keys extra secure</p> <p className='text-xs text-gray-400'>So you never lose access to your account</p>
</div> </div>
</div> </div>
<p className='text-sm text-gray-300'> <p className='text-sm text-gray-300'>
For better security, use a dedicated signer app instead of pasting your secret key everywhere. Your account lives on your device. Save a recovery code somewhere safe and
Signer apps keep your keys safe while letting you use any Nostr app. you'll be able to log back in on any device. You can also do this later from Settings.
</p> </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'> {!showNsec ? (
<ExternalLink className='h-4 w-4 mr-2' /> <Button
Learn About Signer Apps variant='outline'
</Button> className='w-full border-amber-500/50 text-amber-400 hover:bg-amber-500/10'
</Link> onClick={() => setShowNsec(true)}
<button
onClick={handleFinishComplete}
className='w-full text-xs text-gray-500 hover:text-gray-400 py-2'
> >
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> </button>
</Link>
</CardContent> </CardContent>
</Card> </Card>
</div> </div>