import { useRouter } from 'next/router' import Head from 'next/head' import { useEffect, useState } from 'react' import { fetchAuthorByHashId } from '@/lib/authorQueries' import { getSeriesByAuthor } from '@/lib/seriesQueries' import { getAuthorSponsoring } from '@/lib/sponsoring' import { nostrService } from '@/lib/nostr' import type { AuthorPresentationArticle, Series } from '@/types/nostr' import { PageHeader } from '@/components/PageHeader' import { Footer } from '@/components/Footer' import { t } from '@/lib/i18n' import Link from 'next/link' import { SeriesCard } from '@/components/SeriesCard' import Image from 'next/image' import { CreateSeriesModal } from '@/components/CreateSeriesModal' import { useNostrAuth } from '@/hooks/useNostrAuth' import { parseObjectUrl } from '@/lib/urlGenerator' import { SponsoringForm } from '@/components/SponsoringForm' function AuthorPageHeader({ presentation }: { presentation: AuthorPresentationArticle | null }): React.ReactElement | null { if (!presentation) { return null } // Extract author name from title (format: "Présentation de ") const authorName = presentation.title.replace(/^Présentation de /, '').trim() || presentation.title return (
{presentation.bannerUrl && (
{t('author.profilePicture')}
)}

{authorName}

{t('author.profileNote')}

{presentation.description && (

{t('presentation.field.presentation')}

{presentation.description}

)} {presentation.contentDescription && (

{t('presentation.field.contentDescription')}

{presentation.contentDescription}

)}
) } function SponsoringSummary({ totalSponsoring, author, onSponsor }: { totalSponsoring: number; author: AuthorPresentationArticle | null; onSponsor: () => void }): React.ReactElement { const totalBTC = totalSponsoring / 100_000_000 const [showForm, setShowForm] = useState(false) return (

{t('author.sponsoring')}

{author && ( )}

{t('author.sponsoring.total', { amount: totalBTC.toFixed(6) })}

{t('author.sponsoring.sats', { amount: totalSponsoring.toLocaleString() })}

{showForm && author && (
{ setShowForm(false) onSponsor() }} onCancel={() => { setShowForm(false) }} />
)}
) } function SeriesList({ series, authorPubkey, onSeriesCreated }: { series: Series[]; authorPubkey: string; onSeriesCreated: () => void }): React.ReactElement { const { pubkey, isUnlocked } = useNostrAuth() const [showCreateModal, setShowCreateModal] = useState(false) const isAuthor = pubkey === authorPubkey && isUnlocked return (

{t('series.title')}

{isAuthor && ( )}
{series.length === 0 ? (

{t('series.empty')}

) : (
{series.map((s) => ( {}} /> ))}
)} setShowCreateModal(false)} onSuccess={onSeriesCreated} authorPubkey={authorPubkey} />
) } async function loadAuthorData(hashId: string): Promise<{ pres: AuthorPresentationArticle | null; seriesList: Series[]; sponsoring: number }> { const pool = nostrService.getPool() if (!pool) { throw new Error('Pool not initialized') } const pres = await fetchAuthorByHashId(pool, hashId) if (!pres) { return { pres: null, seriesList: [], sponsoring: 0 } } const [seriesList, sponsoring] = await Promise.all([ getSeriesByAuthor(pres.pubkey), getAuthorSponsoring(pres.pubkey), ]) return { pres, seriesList, sponsoring } } function useAuthorData(hashIdOrPubkey: string): { presentation: AuthorPresentationArticle | null series: Series[] totalSponsoring: number loading: boolean error: string | null reload: () => Promise } { const [presentation, setPresentation] = useState(null) const [series, setSeries] = useState([]) const [totalSponsoring, setTotalSponsoring] = useState(0) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const reload = async (): Promise => { if (!hashIdOrPubkey) { return } setLoading(true) setError(null) try { const { pres, seriesList, sponsoring } = await loadAuthorData(hashIdOrPubkey) setPresentation(pres) setSeries(seriesList) setTotalSponsoring(sponsoring) } catch (e) { setError(e instanceof Error ? e.message : 'Erreur lors du chargement') } finally { setLoading(false) } } useEffect(() => { void reload() }, [hashIdOrPubkey]) return { presentation, series, totalSponsoring, loading, error, reload } } function AuthorPageContent({ presentation, series, totalSponsoring, authorPubkey, loading, error, onSeriesCreated, }: { presentation: AuthorPresentationArticle | null series: Series[] totalSponsoring: number authorPubkey: string loading: boolean error: string | null onSeriesCreated: () => void }): React.ReactElement { if (loading) { return

{t('common.loading')}

} if (error) { return

{error}

} if (presentation) { return ( <> ) } return (

{t('author.notFound')}

) } export default function AuthorPage(): React.ReactElement { const router = useRouter() const { pubkey } = router.query // Parse the URL parameter - it can be either: // 1. Old format: /author/ (for backward compatibility) // 2. New format: /author/__ (standard format) let hashIdOrPubkey: string | null = null if (typeof pubkey === 'string') { // Try to parse as new format first (hash_index_version) const urlMatch = pubkey.match(/^([a-f0-9]+)_(\d+)_(\d+)$/i) if (urlMatch && urlMatch[1]) { // Extract hash ID from the format hash_index_version hashIdOrPubkey = urlMatch[1] } else { // Try to parse as full URL format const parsedUrl = parseObjectUrl(`https://zapwall.fr/author/${pubkey}`) if (parsedUrl.objectType === 'author' && parsedUrl.idHash) { hashIdOrPubkey = parsedUrl.idHash } else { // Legacy: treat as pubkey for backward compatibility (64 hex chars) hashIdOrPubkey = pubkey } } } const { presentation, series, totalSponsoring, loading, error, reload } = useAuthorData(hashIdOrPubkey || '') if (!hashIdOrPubkey) { return <> } // Get the actual pubkey from presentation const actualAuthorPubkey = presentation?.pubkey || '' return ( <> {t('author.title')} - {t('home.title')}
) }