// NOTE: This file is stable and usually should not be modified. // It is important that all functionality in this file is preserved, and should only be modified if explicitly requested. import React, { useState, useEffect, useRef } from 'react'; import { Download, Eye, EyeOff, Loader2 } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; 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 { downloadTextFile } from '@/lib/downloadFile'; import { ProfileCard } from '@/components/ProfileCard'; import { ImageCropDialog } from '@/components/ImageCropDialog'; import type { NostrMetadata } from '@nostrify/nostrify'; interface SignupDialogProps { isOpen: boolean; onClose: () => void; } const SignupDialog: React.FC = ({ isOpen, onClose }) => { const [step, setStep] = useState<'generate' | 'download' | 'profile'>('generate'); const [nsec, setNsec] = useState(''); const [showKey, setShowKey] = useState(false); const [profileData, setProfileData] = useState>({ name: '', about: '', picture: '', banner: '', }); const [cropState, setCropState] = useState<{ imageSrc: string; aspect: number; field: 'picture' | 'banner' } | null>(null); const pickInputRef = useRef(null); const pendingField = useRef<'picture' | 'banner'>('picture'); const login = useLoginActions(); const { mutateAsync: publishEvent, isPending: isPublishing } = useNostrPublish(); const { mutateAsync: uploadFile, isPending: isUploading } = useUploadFile(); // Generate a proper nsec key using nostr-tools const generateKey = () => { const sk = generateSecretKey(); setNsec(nip19.nsecEncode(sk)); setStep('download'); }; const downloadKey = async () => { try { const decoded = nip19.decode(nsec); if (decoded.type !== 'nsec') { throw new Error('Invalid nsec key'); } const pubkey = getPublicKey(decoded.data); const npub = nip19.npubEncode(pubkey); const filename = `nostr-${location.hostname.replaceAll(/\./g, '-')}-${npub.slice(5, 9)}.nsec.txt`; await downloadTextFile(filename, nsec); // Continue to profile step login.nsec(nsec); setStep('profile'); } catch { toast({ title: 'Download failed', description: 'Could not download the key file. Please copy it manually.', variant: 'destructive', }); } }; const handlePickImage = (field: 'picture' | 'banner') => { pendingField.current = field; pickInputRef.current?.click(); }; const handleFileChosen = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; e.target.value = ''; const field = pendingField.current; setCropState({ imageSrc: URL.createObjectURL(file), aspect: field === 'picture' ? 1 : 3, field }); }; const handleCropConfirm = async (blob: Blob) => { if (!cropState) return; const { field, imageSrc } = cropState; URL.revokeObjectURL(imageSrc); setCropState(null); try { const file = new File([blob], `${field}.jpg`, { type: 'image/jpeg' }); const [[, url]] = await uploadFile(file); setProfileData(prev => ({ ...prev, [field]: url })); } catch { toast({ title: 'Upload failed', description: 'Please try again.', variant: 'destructive' }); } }; const handleCropCancel = () => { if (cropState) URL.revokeObjectURL(cropState.imageSrc); setCropState(null); }; const finishSignup = async (skipProfile = false) => { try { if (!skipProfile && (profileData.name || profileData.about || profileData.picture)) { await publishEvent({ kind: 0, content: JSON.stringify(profileData), }); } } catch { toast({ title: 'Profile Setup Failed', description: 'Your account was created but profile setup failed. You can update it later.', variant: 'destructive', }); } finally { onClose(); } }; const getTitle = () => { if (step === 'generate') return 'Sign up'; if (step === 'download') return 'Secret Key'; if (step === 'profile') return 'Create Your Profile'; }; // Reset state when dialog opens useEffect(() => { if (isOpen) { setStep('generate'); setNsec(''); setShowKey(false); setProfileData({ name: '', about: '', picture: '' }); } }, [isOpen]); return ( {getTitle()}
{/* Generate Step */} {step === 'generate' && (
🔑
)} {/* Download Step */} {step === 'download' && (
🔑
Important Warning

This key is your primary and only means of accessing your account. Store it safely and securely. Please download your key to continue.

)} {/* Profile Step */} {step === 'profile' && (
{cropState && ( )}
setProfileData(prev => ({ ...prev, ...patch }))} onPickImage={handlePickImage} />
{isUploading && (
Uploading image…
)}
)}
); }; export default SignupDialog;