diff --git a/components/AuthorPresentationEditor.tsx b/components/AuthorPresentationEditor.tsx index 9f36eb6..5918f43 100644 --- a/components/AuthorPresentationEditor.tsx +++ b/components/AuthorPresentationEditor.tsx @@ -4,6 +4,8 @@ import { useAuthorPresentation } from '@/hooks/useAuthorPresentation' import { ArticleField } from './ArticleField' import { ArticleFormButtons } from './ArticleFormButtons' import { CreateAccountModal } from './CreateAccountModal' +import { RecoveryStep } from './CreateAccountModalSteps' +import { UnlockAccountModal } from './UnlockAccountModal' import { ImageUploadField } from './ImageUploadField' import { PresentationFormHeader } from './PresentationFormHeader' import { t } from '@/lib/i18n' @@ -254,17 +256,39 @@ function NoAccountActionButtons({ } function NoAccountView() { - const [showCreateModal, setShowCreateModal] = useState(false) - const [modalStep, setModalStep] = useState<'choose' | 'import'>('choose') + const [showImportModal, setShowImportModal] = useState(false) + const [showRecoveryStep, setShowRecoveryStep] = useState(false) + const [showUnlockModal, setShowUnlockModal] = useState(false) + const [recoveryPhrase, setRecoveryPhrase] = useState([]) + const [npub, setNpub] = useState('') + const [generating, setGenerating] = useState(false) + const [error, setError] = useState(null) - const handleOpenModal = (step: 'choose' | 'import') => { - setModalStep(step) - setShowCreateModal(true) + const handleGenerate = async () => { + setGenerating(true) + setError(null) + try { + const { nostrAuthService } = await import('@/lib/nostrAuth') + const result = await nostrAuthService.createAccount() + setRecoveryPhrase(result.recoveryPhrase) + setNpub(result.npub) + setShowRecoveryStep(true) + } catch (e) { + setError(e instanceof Error ? e.message : 'Failed to create account') + } finally { + setGenerating(false) + } } - const handleModalClose = () => { - setShowCreateModal(false) - setModalStep('choose') + const handleRecoveryContinue = () => { + setShowRecoveryStep(false) + setShowUnlockModal(true) + } + + const handleUnlockSuccess = () => { + setShowUnlockModal(false) + setRecoveryPhrase([]) + setNpub('') } return ( @@ -273,15 +297,35 @@ function NoAccountView() {

Créez un compte ou importez votre clé secrète pour commencer

+ {error &&

{error}

} handleOpenModal('choose')} - onImport={() => handleOpenModal('import')} + onGenerate={handleGenerate} + onImport={() => setShowImportModal(true)} /> - {showCreateModal && ( + {generating && ( +

Génération du compte...

+ )} + {showImportModal && ( { + setShowImportModal(false) + setShowUnlockModal(true) + }} + onClose={() => setShowImportModal(false)} + initialStep="import" + /> + )} + {showRecoveryStep && ( + + )} + {showUnlockModal && ( + setShowUnlockModal(false)} /> )} diff --git a/components/ConnectButton.tsx b/components/ConnectButton.tsx index f62fe96..64a1f6c 100644 --- a/components/ConnectButton.tsx +++ b/components/ConnectButton.tsx @@ -1,7 +1,7 @@ import { useState, useEffect } from 'react' import { useNostrAuth } from '@/hooks/useNostrAuth' import { ConnectedUserMenu } from './ConnectedUserMenu' -import { CreateAccountModal } from './CreateAccountModal' +import { RecoveryStep } from './CreateAccountModalSteps' import { UnlockAccountModal } from './UnlockAccountModal' import type { NostrProfile } from '@/types/nostr' @@ -37,12 +37,12 @@ function ConnectForm({ ) } -function useAutoConnect(accountExists: boolean | null, pubkey: string | null, showCreateModal: boolean, showUnlockModal: boolean, connect: () => Promise) { +function useAutoConnect(accountExists: boolean | null, pubkey: string | null, showRecoveryStep: boolean, showUnlockModal: boolean, connect: () => Promise) { useEffect(() => { - if (accountExists === true && !pubkey && !showCreateModal && !showUnlockModal) { + if (accountExists === true && !pubkey && !showRecoveryStep && !showUnlockModal) { void connect() } - }, [accountExists, pubkey, showCreateModal, showUnlockModal, connect]) + }, [accountExists, pubkey, showRecoveryStep, showUnlockModal, connect]) } function ConnectedState({ pubkey, profile, loading, disconnect }: { pubkey: string; profile: NostrProfile | null; loading: boolean; disconnect: () => Promise }) { @@ -72,28 +72,28 @@ function UnlockState({ loading, error, onUnlock, onClose }: { loading: boolean; ) } -function DisconnectedModals({ - showCreateModal, + +function DisconnectedState({ + loading, + error, showUnlockModal, - setShowCreateModal, setShowUnlockModal, + onCreateAccount, }: { - showCreateModal: boolean + loading: boolean + error: string | null showUnlockModal: boolean - setShowCreateModal: (show: boolean) => void setShowUnlockModal: (show: boolean) => void + onCreateAccount: () => void }) { return ( <> - {showCreateModal && ( - { - setShowCreateModal(false) - setShowUnlockModal(true) - }} - onClose={() => setShowCreateModal(false)} - /> - )} + setShowUnlockModal(true)} + loading={loading} + error={error} + /> {showUnlockModal && ( setShowUnlockModal(false)} @@ -104,45 +104,43 @@ function DisconnectedModals({ ) } -function DisconnectedState({ - loading, - error, - showCreateModal, - showUnlockModal, - setShowCreateModal, - setShowUnlockModal, -}: { - loading: boolean - error: string | null - showCreateModal: boolean - showUnlockModal: boolean - setShowCreateModal: (show: boolean) => void - setShowUnlockModal: (show: boolean) => void -}) { - return ( - <> - setShowCreateModal(true)} - onUnlock={() => setShowUnlockModal(true)} - loading={loading} - error={error} - /> - - - ) -} - export function ConnectButton() { const { connected, pubkey, profile, loading, error, connect, disconnect, accountExists, isUnlocked } = useNostrAuth() - const [showCreateModal, setShowCreateModal] = useState(false) + const [showRecoveryStep, setShowRecoveryStep] = useState(false) const [showUnlockModal, setShowUnlockModal] = useState(false) + const [recoveryPhrase, setRecoveryPhrase] = useState([]) + const [npub, setNpub] = useState('') + const [creatingAccount, setCreatingAccount] = useState(false) + const [createError, setCreateError] = useState(null) - useAutoConnect(accountExists, pubkey, showCreateModal, showUnlockModal, connect) + useAutoConnect(accountExists, pubkey, false, showUnlockModal, connect) + + const handleCreateAccount = async () => { + setCreatingAccount(true) + setCreateError(null) + try { + const { nostrAuthService } = await import('@/lib/nostrAuth') + const result = await nostrAuthService.createAccount() + setRecoveryPhrase(result.recoveryPhrase) + setNpub(result.npub) + setShowRecoveryStep(true) + } catch (e) { + setCreateError(e instanceof Error ? e.message : 'Failed to create account') + } finally { + setCreatingAccount(false) + } + } + + const handleRecoveryContinue = () => { + setShowRecoveryStep(false) + setShowUnlockModal(true) + } + + const handleUnlockSuccess = () => { + setShowUnlockModal(false) + setRecoveryPhrase([]) + setNpub('') + } if (connected && pubkey && isUnlocked) { return @@ -160,13 +158,27 @@ export function ConnectButton() { } return ( - + <> + + {showRecoveryStep && ( + + )} + {showUnlockModal && ( + setShowUnlockModal(false)} + /> + )} + ) } diff --git a/components/CreateAccountModalComponents.tsx b/components/CreateAccountModalComponents.tsx index c87575f..b1316ab 100644 --- a/components/CreateAccountModalComponents.tsx +++ b/components/CreateAccountModalComponents.tsx @@ -1,16 +1,16 @@ export function RecoveryWarning() { return ( -
-

⚠️ Important

-

+

+

⚠️ Important

+

Ces 4 mots-clés sont votre seule façon de récupérer votre compte. Ils ne seront jamais affichés à nouveau.

-

+

Ces mots-clés (dictionnaire BIP39) sont utilisés avec PBKDF2 pour chiffrer une clé de chiffrement (KEK) stockée dans l'API Credentials du navigateur. Cette KEK chiffre ensuite votre clé privée stockée dans IndexedDB (système à deux niveaux).

-

+

Notez-les dans un endroit sûr. Sans ces mots-clés, vous perdrez définitivement l'accès à votre compte.

@@ -27,15 +27,15 @@ export function RecoveryPhraseDisplay({ onCopy: () => void }) { return ( -
+
{recoveryPhrase.map((word, index) => (
- {index + 1}. - {word} + {index + 1}. + {word}
))}
@@ -43,7 +43,7 @@ export function RecoveryPhraseDisplay({ onClick={() => { void onCopy() }} - className="w-full py-2 px-4 bg-gray-200 hover:bg-gray-300 rounded-lg text-sm font-medium transition-colors" + className="w-full py-2 px-4 bg-cyber-light border border-neon-cyan/30 hover:border-neon-cyan/50 hover:bg-cyber-dark text-cyber-accent hover:text-neon-cyan rounded-lg text-sm font-medium transition-colors" > {copied ? '✓ Copié!' : 'Copier les mots-clés'} @@ -53,9 +53,9 @@ export function RecoveryPhraseDisplay({ export function PublicKeyDisplay({ npub }: { npub: string }) { return ( -
-

Votre clé publique (npub)

-

{npub}

+
+

Votre clé publique (npub)

+

{npub}

) } @@ -72,7 +72,7 @@ export function ImportKeyForm({ return ( <>
-