diff --git a/src/components/auth/LoginArea.tsx b/src/components/auth/LoginArea.tsx index 40d2f53..31d604e 100644 --- a/src/components/auth/LoginArea.tsx +++ b/src/components/auth/LoginArea.tsx @@ -5,7 +5,7 @@ import { useState } from 'react'; import { User } from 'lucide-react'; import { Button } from '@/components/ui/button.tsx'; import LoginDialog from './LoginDialog'; -import SignupDialog from './SignupDialog'; + import { useLoggedInAccounts } from '@/hooks/useLoggedInAccounts'; import { AccountSwitcher } from './AccountSwitcher'; import { cn } from '@/lib/utils'; @@ -17,11 +17,9 @@ export interface LoginAreaProps { export function LoginArea({ className }: LoginAreaProps) { const { currentUser } = useLoggedInAccounts(); const [loginDialogOpen, setLoginDialogOpen] = useState(false); - const [signupDialogOpen, setSignupDialogOpen] = useState(false); const handleLogin = () => { setLoginDialogOpen(false); - setSignupDialogOpen(false); }; return ( @@ -39,16 +37,12 @@ export function LoginArea({ className }: LoginAreaProps) { )} setLoginDialogOpen(false)} + isOpen={loginDialogOpen} + onClose={() => setLoginDialogOpen(false)} onLogin={handleLogin} - onSignup={() => setSignupDialogOpen(true)} /> - setSignupDialogOpen(false)} - /> + ); } \ No newline at end of file diff --git a/src/components/auth/LoginDialog.tsx b/src/components/auth/LoginDialog.tsx index 7c437e7..6e6dace 100644 --- a/src/components/auth/LoginDialog.tsx +++ b/src/components/auth/LoginDialog.tsx @@ -14,18 +14,19 @@ import { } from '@/components/ui/dialog.tsx'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs.tsx'; import { useLoginActions } from '@/hooks/useLoginActions'; +import SignupDialog from './SignupDialog'; interface LoginDialogProps { isOpen: boolean; onClose: () => void; onLogin: () => void; - onSignup?: () => void; } -const LoginDialog: React.FC = ({ isOpen, onClose, onLogin, onSignup }) => { +const LoginDialog: React.FC = ({ isOpen, onClose, onLogin }) => { const [isLoading, setIsLoading] = useState(false); const [nsec, setNsec] = useState(''); const [bunkerUri, setBunkerUri] = useState(''); + const [isSignupOpen, setIsSignupOpen] = useState(false); const fileInputRef = useRef(null); const login = useLoginActions(); @@ -89,117 +90,115 @@ const LoginDialog: React.FC = ({ isOpen, onClose, onLogin, onS const handleSignupClick = () => { onClose(); - if (onSignup) { - onSignup(); - } + setIsSignupOpen(true); }; return ( - - - - Log in - - Access your account securely with your preferred method - - + <> + + + + Log in + + Access your account securely with your preferred method + + -
- - - Extension - Nsec - Bunker - +
+ + + Extension + Nsec + Bunker + - -
- -

- Login with one click using the browser extension -

- -
-
- - -
-
- - setNsec(e.target.value)} - className='rounded-lg border-gray-300 dark:border-gray-700 focus-visible:ring-primary' - placeholder='nsec1...' - /> -
- -
-

Or upload a key file

- + +
+ +

+ Login with one click using the browser extension +

+
+ + +
+
+ + setNsec(e.target.value)} + className='rounded-lg border-gray-300 dark:border-gray-700 focus-visible:ring-primary' + placeholder='nsec1...' + /> +
+ +
+

Or upload a key file

+ + +
+ + +
+
+ + +
+ + setBunkerUri(e.target.value)} + className='rounded-lg border-gray-300 dark:border-gray-700 focus-visible:ring-primary' + placeholder='bunker://' + /> + {bunkerUri && !bunkerUri.startsWith('bunker://') && ( +

URI must start with bunker://

+ )} +
-
- + + - -
- - setBunkerUri(e.target.value)} - className='rounded-lg border-gray-300 dark:border-gray-700 focus-visible:ring-primary' - placeholder='bunker://' - /> - {bunkerUri && !bunkerUri.startsWith('bunker://') && ( -

URI must start with bunker://

- )} -
- - -
- - - {onSignup && (
- )} - {onSignup && (
- )} -
- -
+ +
+
+ setIsSignupOpen(false)} /> + ); }; diff --git a/src/components/auth/SignupDialog.tsx b/src/components/auth/SignupDialog.tsx index 28ee399..655f8f5 100644 --- a/src/components/auth/SignupDialog.tsx +++ b/src/components/auth/SignupDialog.tsx @@ -1,22 +1,13 @@ -// 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, Key, Copy, Upload } from 'lucide-react'; +import React, { useState, useEffect } from 'react'; +import { Download, KeyRound, Lock, CheckCircle, Copy, ArrowRight } 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 { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog'; +import { Card, CardContent } from '@/components/ui/card'; +import { useToast } 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 { @@ -25,19 +16,17 @@ interface SignupDialogProps { } const SignupDialog: React.FC = ({ isOpen, onClose }) => { - const [step, setStep] = useState<'generate' | 'download' | 'profile' | 'done'>('generate'); + const [step, setStep] = useState<'welcome' | 'generate' | 'download' | 'profile' | 'done'>('welcome'); 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 { toast } = useToast(); const login = useLoginActions(); const { mutateAsync: publishEvent, isPending: isPublishing } = useNostrPublish(); - const { mutateAsync: uploadFile, isPending: isUploading } = useUploadFile(); - const avatarFileInputRef = useRef(null); const generateKey = () => { setIsLoading(true); @@ -47,8 +36,8 @@ const SignupDialog: React.FC = ({ isOpen, onClose }) => { setNsec(nip19.nsecEncode(sk)); setStep('download'); toast({ - title: 'Key Generated', - description: 'Your new key is ready.', + title: 'New key generated', + description: 'Keep it safe.', }); } catch { toast({ @@ -59,25 +48,27 @@ const SignupDialog: React.FC = ({ isOpen, onClose }) => { } finally { setIsLoading(false); } - }, 500); + }, 1000); }; const downloadKey = () => { try { const blob = new Blob([nsec], { type: 'text/plain; charset=utf-8' }); const url = globalThis.URL.createObjectURL(blob); + const filename = 'secret-key.txt'; const a = document.createElement('a'); a.href = url; - a.download = 'nsec.txt'; + a.download = filename; 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.', + title: 'Key Secured', + description: 'Your key has been safely stored.', }); } catch { toast({ @@ -110,56 +101,12 @@ const SignupDialog: React.FC = ({ isOpen, onClose }) => { } }; - 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)) { + if (!skipProfile && (profileData.name || profileData.about)) { 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, @@ -167,8 +114,8 @@ const SignupDialog: React.FC = ({ isOpen, onClose }) => { }); toast({ - title: 'Profile Updated', - description: 'Your profile has been published.', + title: 'Profile Created', + description: 'Your profile has been created.', }); } @@ -179,7 +126,7 @@ const SignupDialog: React.FC = ({ isOpen, onClose }) => { title: 'Welcome!', description: 'You are now logged in.', }); - }, 1500); + }, 2000); } catch { toast({ @@ -187,7 +134,7 @@ const SignupDialog: React.FC = ({ isOpen, onClose }) => { description: 'Your account was created but profile setup failed. You can update it later.', variant: 'destructive', }); - + setStep('done'); setTimeout(() => { onClose(); @@ -195,180 +142,255 @@ const SignupDialog: React.FC = ({ isOpen, onClose }) => { title: 'Welcome!', description: 'You are now logged in.', }); - }, 1500); + }, 2000); } }; - 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'; + if (step === 'welcome') return 'Welcome'; + if (step === 'generate') return 'Generate Your Key'; + if (step === 'download') return 'Secure Your Key'; + if (step === 'profile') return 'Create 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."; + if (step === 'welcome') return 'Ready to get started?'; + if (step === 'generate') return 'A new key is being generated for you.'; + if (step === 'download') return "This key is your password. Keep it safe."; + if (step === 'profile') return 'This is how others will see you.'; + return 'You are now logged in.'; }; + useEffect(() => { + if (isOpen) { + setStep('welcome'); + setIsLoading(false); + setNsec(''); + setKeySecured('none'); + setProfileData({ name: '', about: '' }); + } + }, [isOpen]); + return ( - - - {getTitle()} - - {getDescription()} - + + + {getTitle()} + {getDescription()} - -
- {step === 'generate' && ( -
-
- -
-

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

+ {step === 'welcome' && ( +
+

+ Your key is your identity. It's how you log in and how others see you.

)} + {step === 'generate' && ( +
+ {isLoading ? ( +
+
+ +
+

+ Generating your key... +

+
+ ) : ( +
+ +

+ Ready to generate your key? +

+
+ )} + {!isLoading && ( + + )} +
+ )} + {step === 'download' && ( -
-
- {nsec} +
+
+
+ + + Your Secret Key + +
+
+ {nsec} +
-
-

Important:

-
    -
  • Store this key somewhere safe.
  • -
  • If you lose it, you lose your account.
  • -
  • Never share it with anyone.
  • -
-
+
+

+ Choose how to secure your key: +

-
- - + + + + + + + + +
+ +
- -
)} {step === 'profile' && (
-
+
- + setProfileData(prev => ({ ...prev, name: e.target.value }))} - placeholder='e.g. Satoshi Nakamoto' + placeholder='Your name' + disabled={isPublishing} />
- +