import React, { useState, useEffect, useRef } from 'react'; import { Download, Key, Copy, Upload } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from '@/components/ui/dialog.tsx'; import { toast } from '@/hooks/useToast'; import { useLoginActions } from '@/hooks/useLoginActions'; import { useNostrPublish } from '@/hooks/useNostrPublish'; import { useUploadFile } from '@/hooks/useUploadFile'; import { generateSecretKey, nip19 } from 'nostr-tools'; interface SignupDialogProps { isOpen: boolean; onClose: () => void; } const SignupDialog: React.FC = ({ isOpen, onClose }) => { const [step, setStep] = useState<'generate' | 'download' | 'profile' | 'done'>('generate'); const [isLoading, setIsLoading] = useState(false); const [nsec, setNsec] = useState(''); const [keySecured, setKeySecured] = useState<'none' | 'copied' | 'downloaded'>('none'); const [profileData, setProfileData] = useState({ name: '', about: '', picture: '' }); const login = useLoginActions(); const { mutateAsync: publishEvent, isPending: isPublishing } = useNostrPublish(); const { mutateAsync: uploadFile, isPending: isUploading } = useUploadFile(); const avatarFileInputRef = useRef(null); const generateKey = () => { setIsLoading(true); setTimeout(() => { try { const sk = generateSecretKey(); setNsec(nip19.nsecEncode(sk)); setStep('download'); toast({ title: 'Key Generated', description: 'Your new key is ready.', }); } catch { toast({ title: 'Error', description: 'Failed to generate key. Please try again.', variant: 'destructive', }); } finally { setIsLoading(false); } }, 500); }; const downloadKey = () => { try { const blob = new Blob([nsec], { type: 'text/plain; charset=utf-8' }); const url = globalThis.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'nsec.txt'; a.style.display = 'none'; document.body.appendChild(a); a.click(); globalThis.URL.revokeObjectURL(url); document.body.removeChild(a); setKeySecured('downloaded'); toast({ title: 'Key Downloaded', description: 'Your key has been saved to a file.', }); } catch { toast({ title: 'Download failed', description: 'Could not download the key file. Please copy it manually.', variant: 'destructive', }); } }; const copyKey = () => { navigator.clipboard.writeText(nsec); setKeySecured('copied'); toast({ title: 'Copied to clipboard', description: 'Your key has been copied.', }); }; 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) => { const file = e.target.files?.[0]; if (!file) return; e.target.value = ''; if (!file.type.startsWith('image/')) { toast({ title: 'Invalid file type', description: 'Please select an image file.', variant: 'destructive', }); return; } if (file.size > 5 * 1024 * 1024) { toast({ title: 'File too large', description: 'Image must be smaller than 5MB.', variant: 'destructive', }); return; } try { const tags = await uploadFile(file); const url = tags[0]?.[1]; if (url) { setProfileData(prev => ({ ...prev, picture: url })); toast({ title: 'Avatar uploaded!', description: 'Your new avatar is ready.', }); } } catch { toast({ title: 'Upload failed', description: 'Failed to upload avatar. Please try again.', variant: 'destructive', }); } }; const finishSignup = async (skipProfile = false) => { try { if (!skipProfile && (profileData.name || profileData.about || profileData.picture)) { const metadata: Record = {}; if (profileData.name) metadata.name = profileData.name; if (profileData.about) metadata.about = profileData.about; if (profileData.picture) metadata.picture = profileData.picture; await publishEvent({ kind: 0, content: JSON.stringify(metadata), }); toast({ title: 'Profile Updated', description: 'Your profile has been published.', }); } setStep('done'); setTimeout(() => { onClose(); toast({ title: 'Welcome!', description: 'You are now logged in.', }); }, 1500); } catch { toast({ title: 'Profile Setup Failed', description: 'Your account was created but profile setup failed. You can update it later.', variant: 'destructive', }); setStep('done'); setTimeout(() => { onClose(); toast({ title: 'Welcome!', description: 'You are now logged in.', }); }, 1500); } }; useEffect(() => { if (isOpen) { setStep('generate'); setIsLoading(false); setNsec(''); setKeySecured('none'); setProfileData({ name: '', about: '', picture: '' }); } }, [isOpen]); const getTitle = () => { if (step === 'generate') return 'Create Your Account'; if (step === 'download') return 'Save Your Key'; if (step === 'profile') return 'Set Up Your Profile'; return 'Welcome!'; }; const getDescription = () => { if (step === 'generate') return "We'll generate a secure, private key for your new account."; if (step === 'download') return "This key is your password. It's the only way to access your account."; if (step === 'profile') return "Customize your profile. This is how others will see you."; return "You're all set up and ready to go."; }; return ( {getTitle()} {getDescription()}
{step === 'generate' && (

Click the button below to generate a unique and secure key for your account.

)} {step === 'download' && (
{nsec}

Important:

  • Store this key somewhere safe.
  • If you lose it, you lose your account.
  • Never share it with anyone.
)} {step === 'profile' && (
setProfileData(prev => ({ ...prev, name: e.target.value }))} placeholder='e.g. Satoshi Nakamoto' />