import { useState, useCallback, useEffect, type FormEvent } from 'react' import { useRouter } from 'next/router' import { useNostrAuth } from '@/hooks/useNostrAuth' import { useAuthorPresentation } from '@/hooks/useAuthorPresentation' import { ArticleField } from './ArticleField' import { CreateAccountModal } from './CreateAccountModal' import { RecoveryStep } from './CreateAccountModalSteps' import { UnlockAccountModal } from './UnlockAccountModal' import { ImageUploadField } from './ImageUploadField' import { PresentationFormHeader } from './PresentationFormHeader' import { extractPresentationData } from '@/lib/presentationParsing' import type { Article } from '@/types/nostr' import { t } from '@/lib/i18n' import { userConfirm } from '@/lib/userConfirm' interface AuthorPresentationDraft { authorName: string presentation: string contentDescription: string mainnetAddress: string pictureUrl?: string } const ADDRESS_PATTERN = /^(1|3|bc1)[a-zA-Z0-9]{25,62}$/ function SuccessNotice({ pubkey }: { pubkey: string | null }): React.ReactElement { return (

{t('presentation.success')}

{t('presentation.successMessage')}

{pubkey && (
{t('presentation.manageSeries')}
)}
) } function ValidationError({ message }: { message: string | null }): React.ReactElement | null { if (!message) { return null } return (

{message}

) } function PresentationField({ draft, onChange, }: { draft: AuthorPresentationDraft onChange: (next: AuthorPresentationDraft) => void }): React.ReactElement { return ( onChange({ ...draft, presentation: value as string })} required type="textarea" rows={6} placeholder={t('presentation.field.presentation.placeholder')} helpText={t('presentation.field.presentation.help')} /> ) } function ContentDescriptionField({ draft, onChange, }: { draft: AuthorPresentationDraft onChange: (next: AuthorPresentationDraft) => void }): React.ReactElement { return ( onChange({ ...draft, contentDescription: value as string })} required type="textarea" rows={6} placeholder={t('presentation.field.contentDescription.placeholder')} helpText={t('presentation.field.contentDescription.help')} /> ) } function MainnetAddressField({ draft, onChange, }: { draft: AuthorPresentationDraft onChange: (next: AuthorPresentationDraft) => void }): React.ReactElement { return ( onChange({ ...draft, mainnetAddress: value as string })} required type="text" placeholder={t('presentation.field.mainnetAddress.placeholder')} helpText={t('presentation.field.mainnetAddress.help')} /> ) } function AuthorNameField({ draft, onChange, }: { draft: AuthorPresentationDraft onChange: (next: AuthorPresentationDraft) => void }): React.ReactElement { return ( onChange({ ...draft, authorName: value as string })} required type="text" placeholder={t('presentation.field.authorName.placeholder')} helpText={t('presentation.field.authorName.help')} /> ) } function PictureField({ draft, onChange, }: { draft: AuthorPresentationDraft onChange: (next: AuthorPresentationDraft) => void }): React.ReactElement { return ( onChange({ ...draft, pictureUrl: url })} /> ) } const PresentationFields = ({ draft, onChange, }: { draft: AuthorPresentationDraft onChange: (next: AuthorPresentationDraft) => void }): React.ReactElement => (
) function DeleteButton({ onDelete, deleting }: { onDelete: () => void; deleting: boolean }): React.ReactElement { return ( ) } function PresentationForm({ draft, setDraft, validationError, error, loading, handleSubmit, deleting, handleDelete, hasExistingPresentation, }: { draft: AuthorPresentationDraft setDraft: (next: AuthorPresentationDraft) => void validationError: string | null error: string | null loading: boolean handleSubmit: (e: FormEvent) => Promise deleting: boolean handleDelete: () => void hasExistingPresentation: boolean }): React.ReactElement { return (
) => { void handleSubmit(e) }} className="border border-neon-cyan/20 rounded-lg p-6 bg-cyber-dark space-y-4" >
{hasExistingPresentation && ( { void handleDelete() }} deleting={deleting} /> )}
) } function useAuthorPresentationState(pubkey: string | null, existingAuthorName?: string, existingPresentation?: Article | null): { draft: AuthorPresentationDraft setDraft: (next: AuthorPresentationDraft) => void validationError: string | null error: string | null loading: boolean handleSubmit: (e: FormEvent) => Promise deleting: boolean handleDelete: () => Promise success: boolean } { const { loading, error, success, publishPresentation, deletePresentation } = useAuthorPresentation(pubkey) const router = useRouter() const [draft, setDraft] = useState(() => { if (existingPresentation) { const { presentation, contentDescription } = extractPresentationData(existingPresentation) const authorName = existingPresentation.title.replace(/^Présentation de /, '') ?? existingAuthorName ?? '' return { authorName, presentation, contentDescription, mainnetAddress: existingPresentation.mainnetAddress ?? '', ...(existingPresentation.bannerUrl ? { pictureUrl: existingPresentation.bannerUrl } : {}), } } return { authorName: existingAuthorName ?? '', presentation: '', contentDescription: '', mainnetAddress: '', } }) const [validationError, setValidationError] = useState(null) const [deleting, setDeleting] = useState(false) // Update authorName when profile changes useEffect(() => { if (existingAuthorName && existingAuthorName !== draft.authorName && !existingPresentation) { setDraft((prev) => ({ ...prev, authorName: existingAuthorName })) } }, [existingAuthorName, existingPresentation]) const handleSubmit = useCallback( async (e: FormEvent) => { e.preventDefault() const address = draft.mainnetAddress.trim() if (!ADDRESS_PATTERN.test(address)) { setValidationError(t('presentation.validation.invalidAddress')) return } if (!draft.authorName.trim()) { setValidationError(t('presentation.validation.authorNameRequired')) return } setValidationError(null) await publishPresentation(draft) }, [draft, publishPresentation] ) const handleDelete = useCallback(async () => { if (!existingPresentation?.id) { return } if (!userConfirm(t('presentation.delete.confirm'))) { return } setDeleting(true) setValidationError(null) try { await deletePresentation(existingPresentation.id) await router.push('/') } catch (e) { setValidationError(e instanceof Error ? e.message : t('presentation.delete.error')) } finally { setDeleting(false) } }, [existingPresentation, deletePresentation, router]) return { loading, error, success, draft, setDraft, validationError, handleSubmit, deleting, handleDelete } } function NoAccountActionButtons({ onGenerate, onImport, }: { onGenerate: () => void onImport: () => void }): React.ReactElement { return (
) } function NoAccountView(): React.ReactElement { 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 handleGenerate = async (): Promise => { 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 : t('account.create.error.failed')) } finally { setGenerating(false) } } const handleRecoveryContinue = (): void => { setShowRecoveryStep(false) setShowUnlockModal(true) } const handleUnlockSuccess = (): void => { setShowUnlockModal(false) setRecoveryPhrase([]) setNpub('') } return (

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

{error &&

{error}

} { void handleGenerate() }} onImport={() => setShowImportModal(true)} /> {generating && (

Génération du compte...

)} {showImportModal && ( { setShowImportModal(false) setShowUnlockModal(true) }} onClose={() => setShowImportModal(false)} initialStep="import" /> )} {showRecoveryStep && ( )} {showUnlockModal && ( setShowUnlockModal(false)} /> )}
) } function AuthorPresentationFormView({ pubkey, profile, }: { pubkey: string | null profile: { name?: string; pubkey: string } | null }): React.ReactElement { const { checkPresentationExists } = useAuthorPresentation(pubkey) const [existingPresentation, setExistingPresentation] = useState
(null) const [loadingPresentation, setLoadingPresentation] = useState(true) useEffect(() => { const load = async (): Promise => { if (!pubkey) { setLoadingPresentation(false) return } try { const presentation = await checkPresentationExists() setExistingPresentation(presentation) } catch (e) { console.error('Error loading presentation:', e) } finally { setLoadingPresentation(false) } } void load() }, [pubkey, checkPresentationExists]) const state = useAuthorPresentationState(pubkey, profile?.name, existingPresentation) if (!pubkey) { return } if (loadingPresentation) { return (

{t('common.loading')}

) } if (state.success) { return } return ( { void state.handleDelete() }} hasExistingPresentation={existingPresentation !== null && existingPresentation !== undefined} /> ) } function useAutoLoadPubkey(accountExists: boolean | null, pubkey: string | null, connect: () => Promise): void { useEffect(() => { if (accountExists === true && !pubkey) { void connect() } }, [accountExists, pubkey, connect]) } export function AuthorPresentationEditor(): React.ReactElement { const { pubkey, profile, accountExists, connect } = useNostrAuth() useAutoLoadPubkey(accountExists, pubkey ?? null, connect) return }