Refactor SignupDialog to match Soapbox design with improved UX

- Simplify SignupDialog UI to clean Soapbox-style layout with key emoji
- Fix button width to match text size instead of full width
- Implement proper key generation flow using existing generateSecretKey code
- Replace toast message with direct login modal opening for 'I already have a key'
- Remove duplicate NostrExtensionIndicator (LoginDialog already handles extensions)
- Update LoginArea to handle modal transitions between signup and login
- Maintain 3-step flow: key selection → generation → confirmation
- Reuse existing MKStack functionality instead of reinventing wheel
This commit is contained in:
P. Reis 2025-09-22 14:31:39 -03:00
parent 05c0ec92f7
commit a0bd6729ae
3 changed files with 89 additions and 84 deletions

View File

@ -58,6 +58,10 @@ export function LoginArea({ className }: LoginAreaProps) {
isOpen={signupDialogOpen} isOpen={signupDialogOpen}
onClose={() => setSignupDialogOpen(false)} onClose={() => setSignupDialogOpen(false)}
onLogin={handleLogin} onLogin={handleLogin}
onOpenLogin={() => {
setSignupDialogOpen(false);
setLoginDialogOpen(true);
}}
/> />
</div> </div>
); );

View File

@ -1,50 +0,0 @@
import React from 'react';
import { useLoginActions } from '@/hooks/useLoginActions';
interface NostrExtensionIndicatorProps {
onLogin?: () => void;
onClose?: () => void;
}
const NostrExtensionIndicator: React.FC<NostrExtensionIndicatorProps> = ({ onLogin, onClose }) => {
const login = useLoginActions();
const handleExtensionLogin = async () => {
try {
await login.extension();
onLogin?.();
onClose?.();
} catch (error) {
console.error('Extension login failed:', error);
}
};
function renderBody(): React.ReactNode {
if ('nostr' in window) {
return (
<>
<button
type='button'
className='underline text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300'
onClick={handleExtensionLogin}
>
Sign in
</button>
{' '}with browser extension.
</>
);
} else {
return 'Browser extension not found.';
}
}
return (
<div className='flex items-center rounded-lg bg-gray-100 p-3 dark:bg-gray-800'>
<p className='text-xs text-gray-600 dark:text-gray-400'>
{renderBody()}
</p>
</div>
);
};
export default NostrExtensionIndicator;

View File

@ -2,13 +2,12 @@
// It is important that all functionality in this file is preserved, and should only be modified if explicitly requested. // It is important that all functionality in this file is preserved, and should only be modified if explicitly requested.
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { toast } from '@/hooks/useToast'; import { toast } from '@/hooks/useToast';
import { useLoginActions } from '@/hooks/useLoginActions';
import { generateSecretKey, nip19 } from 'nostr-tools';
import NostrExtensionIndicator from './NostrExtensionIndicator';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
interface SignupDialogProps { interface SignupDialogProps {
@ -16,36 +15,76 @@ interface SignupDialogProps {
onClose: () => void; onClose: () => void;
onComplete?: () => void; onComplete?: () => void;
onLogin?: () => void; onLogin?: () => void;
onOpenLogin?: () => void;
} }
const SignupDialog: React.FC<SignupDialogProps> = ({ isOpen, onClose, onLogin }) => { const SignupDialog: React.FC<SignupDialogProps> = ({ isOpen, onClose, onLogin, onOpenLogin }) => {
const [step, setStep] = useState<'key' | 'keygen'>('key'); const [step, setStep] = useState<'key' | 'keygen' | 'download'>('key');
const [nsec, setNsec] = useState('');
const login = useLoginActions();
const handleGenerateKey = () => { const handleGenerateKey = () => {
setStep('keygen'); setStep('keygen');
// Add a dramatic pause for the key generation effect
setTimeout(() => {
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.',
});
} catch {
toast({
title: 'Error',
description: 'Failed to generate key. Please try again.',
variant: 'destructive',
});
setStep('key');
}
}, 2000);
}; };
const handleHasKey = () => { const handleHasKey = () => {
onClose(); onClose();
// This would trigger the login dialog to open with the key-add step if (onOpenLogin) {
// For now, we'll just show a toast message onOpenLogin();
toast({ }
title: 'Use Login Instead',
description: 'Please use the login dialog to enter your existing key.',
});
}; };
const handleExtensionLogin = () => { const handleUseKey = () => {
if (onLogin) { try {
onLogin(); login.nsec(nsec);
if (onLogin) {
onLogin();
}
onClose();
toast({
title: 'Welcome!',
description: 'Your account is ready.',
});
} catch {
toast({
title: 'Login Failed',
description: 'Failed to login with the generated key. Please try again.',
variant: 'destructive',
});
} }
onClose();
}; };
// Reset state when dialog opens // Reset state when dialog opens
useEffect(() => { useEffect(() => {
if (isOpen) { if (isOpen) {
setStep('key'); setStep('key');
setNsec('');
} }
}, [isOpen]); }, [isOpen]);
@ -73,7 +112,7 @@ const SignupDialog: React.FC<SignupDialogProps> = ({ isOpen, onClose, onLogin })
<div className='flex flex-col items-center space-y-3 w-full'> <div className='flex flex-col items-center space-y-3 w-full'>
<Button <Button
className='w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-3 px-6 rounded-lg' className='bg-blue-600 hover:bg-blue-700 text-white font-medium py-3 px-6 rounded-lg'
size='lg' size='lg'
onClick={handleGenerateKey} onClick={handleGenerateKey}
> >
@ -95,13 +134,41 @@ const SignupDialog: React.FC<SignupDialogProps> = ({ isOpen, onClose, onLogin })
<div className='text-center space-y-4'> <div className='text-center space-y-4'>
<div className='space-y-2'> <div className='space-y-2'>
<p className='text-lg font-semibold'> <p className='text-lg font-semibold'>
Key Generation Coming Soon Generating your secret key...
</p> </p>
<p className='text-sm text-muted-foreground'> <p className='text-sm text-muted-foreground'>
Key generation feature is being developed. For now, please use an existing key or browser extension. Creating your secure key
</p> </p>
</div> </div>
<div className='flex justify-center'>
<div className='w-8 h-8 border-4 border-blue-600 border-t-transparent rounded-full animate-spin'></div>
</div>
</div>
)}
{step === 'download' && (
<div className='text-center space-y-4'>
<div className='space-y-2'>
<p className='text-lg font-semibold'>
Your secret key has been generated!
</p>
<p className='text-sm text-muted-foreground'>
Your key is ready to use.
</p>
</div>
<div className='text-6xl'>
</div>
<Button
className='w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-3 px-6 rounded-lg'
onClick={handleUseKey}
>
Continue with generated key
</Button>
<Button <Button
variant='outline' variant='outline'
onClick={() => setStep('key')} onClick={() => setStep('key')}
@ -112,23 +179,7 @@ const SignupDialog: React.FC<SignupDialogProps> = ({ isOpen, onClose, onLogin })
</div> </div>
)} )}
<div className='space-y-4'>
<div className='relative'>
<div className='absolute inset-0 flex items-center'>
<div className='w-full border-t border-gray-300 dark:border-gray-600'></div>
</div>
<div className='relative flex justify-center text-sm'>
<span className='px-3 bg-background text-muted-foreground'>
or
</span>
</div>
</div>
<NostrExtensionIndicator
onLogin={handleExtensionLogin}
onClose={onClose}
/>
</div>
</div> </div>
</DialogContent> </DialogContent>
</Dialog> </Dialog>