import { useRouter } from 'next/router' import Head from 'next/head' import { useEffect, useState, useCallback } 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 } const authorName = getAuthorNameFromPresentationTitle(presentation.title) return (
) } function getAuthorNameFromPresentationTitle(title: string): string { const trimmed = title.replace(/^Présentation de /, '').trim() return trimmed.length > 0 ? trimmed : title } function AuthorProfileImage(params: { bannerUrl?: string }): React.ReactElement | null { if (!params.bannerUrl) { return null } return (
{t('author.profilePicture')}
) } function AuthorHeaderTitle(params: { authorName: string }): React.ReactElement { return (

{params.authorName}

{t('author.profileNote')}

) } function AuthorPresentationSection(params: { title: string; text: string | undefined }): React.ReactElement | null { if (!params.text) { return null } return (

{params.title}

{params.text}

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

{t('author.sponsoring')}

{params.showSponsorButton && ( )}
) } function SponsoringTotals(params: { totalBTC: number; totalSats: number }): React.ReactElement { return (

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

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

) } function SponsoringFormPanel(params: { show: boolean author: AuthorPresentationArticle | null onClose: () => void onSponsor: () => void }): React.ReactElement | null { if (!params.show || !params.author) { return null } return (
{ params.onClose() params.onSponsor() }} onCancel={params.onClose} />
) } 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 = useCallback(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) } }, [hashIdOrPubkey]) useEffect(() => { void reload() }, [hashIdOrPubkey, reload]) return { presentation, series, totalSponsoring, loading, error, reload } } type AuthorPageContentProps = { presentation: AuthorPresentationArticle | null series: Series[] totalSponsoring: number authorPubkey: string loading: boolean error: string | null onSeriesCreated: () => void } function AuthorPageContent({ presentation, series, totalSponsoring, authorPubkey, loading, error, onSeriesCreated, }: AuthorPageContentProps): 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) const hashIdOrPubkey = resolveAuthorHashIdOrPubkey(pubkey) const { presentation, series, totalSponsoring, loading, error, reload } = useAuthorData(hashIdOrPubkey ?? '') const onSeriesCreated = (): void => { void reload() } if (!hashIdOrPubkey) { return
} // Get the actual pubkey from presentation const actualAuthorPubkey = presentation?.pubkey ?? '' return ( <> {t('author.title')} - {t('home.title')}
) } function resolveAuthorHashIdOrPubkey(pubkeyParam: string | string[] | undefined): string | null { if (typeof pubkeyParam !== 'string') { return null } const urlMatch = pubkeyParam.match(/^([a-f0-9]+)_(\d+)_(\d+)$/i) if (urlMatch?.[1]) { return urlMatch[1] } const parsedUrl = parseObjectUrl(`https://zapwall.fr/author/${pubkeyParam}`) if (parsedUrl.objectType === 'author' && parsedUrl.idHash) { return parsedUrl.idHash } return pubkeyParam }