175 lines
5.5 KiB
TypeScript
175 lines
5.5 KiB
TypeScript
import Head from 'next/head'
|
|
import { useRouter } from 'next/router'
|
|
import { useEffect, useState } from 'react'
|
|
import { ArticleEditor } from '@/components/ArticleEditor'
|
|
import { useNostrAuth } from '@/hooks/useNostrAuth'
|
|
import { getSeriesById } from '@/lib/seriesQueries'
|
|
import type { Series } from '@/types/nostr'
|
|
import { t } from '@/lib/i18n'
|
|
import Image from 'next/image'
|
|
|
|
function PublishHeader({ series }: { series: Series }): React.ReactElement {
|
|
return (
|
|
<Head>
|
|
<title>{t('series.publish.title', { series: series.title })} - zapwall.fr</title>
|
|
<meta name="description" content={t('series.publish.description')} />
|
|
</Head>
|
|
)
|
|
}
|
|
|
|
function SeriesHeader({ series }: { series: Series }): React.ReactElement {
|
|
return (
|
|
<div className="space-y-3 mb-6">
|
|
{series.coverUrl && (
|
|
<div className="relative w-full h-32">
|
|
<Image
|
|
src={series.coverUrl}
|
|
alt={series.title}
|
|
fill
|
|
sizes="(max-width: 768px) 100vw, 50vw"
|
|
className="object-cover rounded"
|
|
/>
|
|
</div>
|
|
)}
|
|
<h1 className="text-2xl font-bold">{series.title}</h1>
|
|
<p className="text-sm text-gray-600">{t('series.publish.subtitle')}</p>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default function SeriesPublishPage(): React.ReactElement | null {
|
|
const router = useRouter()
|
|
const { id } = router.query
|
|
const seriesId = typeof id === 'string' ? id : ''
|
|
const { pubkey } = useNostrAuth()
|
|
const { series, loading, error, isAuthor } = useSeriesPublishPageData(seriesId, pubkey ?? null)
|
|
|
|
if (!seriesId) {
|
|
return null
|
|
}
|
|
|
|
if (loading) {
|
|
return <SeriesPublishLoading />
|
|
}
|
|
if (error || !series) {
|
|
return <SeriesPublishError error={error} onBack={() => { void router.back() }} />
|
|
}
|
|
if (!isAuthor) {
|
|
return <SeriesPublishNotAuthor onBack={() => { void router.push(`/series/${seriesId}`) }} />
|
|
}
|
|
return <SeriesPublishEditorView series={series} seriesId={seriesId} onBack={() => { void router.push(`/series/${seriesId}`) }} onAfterPublish={() => { void router.push(`/series/${seriesId}`) }} />
|
|
}
|
|
|
|
function SeriesPublishLoading(): React.ReactElement {
|
|
return (
|
|
<main className="min-h-screen bg-gray-50">
|
|
<div className="w-full px-4 py-8">
|
|
<p className="text-sm text-gray-600">{t('common.loading')}</p>
|
|
</div>
|
|
</main>
|
|
)
|
|
}
|
|
|
|
function SeriesPublishError(params: { error: string | null; onBack: () => void }): React.ReactElement {
|
|
return (
|
|
<main className="min-h-screen bg-gray-50">
|
|
<div className="w-full px-4 py-8">
|
|
<p className="text-sm text-red-600">{params.error ?? 'Série introuvable'}</p>
|
|
<button onClick={params.onBack} className="mt-4 text-blue-600 hover:text-blue-700 text-sm">
|
|
{t('common.back')}
|
|
</button>
|
|
</div>
|
|
</main>
|
|
)
|
|
}
|
|
|
|
function SeriesPublishNotAuthor(params: { onBack: () => void }): React.ReactElement {
|
|
return (
|
|
<main className="min-h-screen bg-gray-50">
|
|
<div className="w-full px-4 py-8">
|
|
<p className="text-sm text-red-600">{t('series.publish.error.notAuthor')}</p>
|
|
<button onClick={params.onBack} className="mt-4 text-blue-600 hover:text-blue-700 text-sm">
|
|
{t('common.back')}
|
|
</button>
|
|
</div>
|
|
</main>
|
|
)
|
|
}
|
|
|
|
function SeriesPublishEditorView(params: { series: Series; seriesId: string; onBack: () => void; onAfterPublish: () => void }): React.ReactElement {
|
|
const handlePublishSuccess = (): void => {
|
|
setTimeout(() => {
|
|
params.onAfterPublish()
|
|
}, 2000)
|
|
}
|
|
return (
|
|
<>
|
|
<PublishHeader series={params.series} />
|
|
<main className="min-h-screen bg-gray-50">
|
|
<header className="bg-white shadow-sm">
|
|
<div className="w-full px-4 py-4 flex justify-between items-center">
|
|
<h1 className="text-2xl font-bold text-gray-900">zapwall4Science</h1>
|
|
</div>
|
|
</header>
|
|
<div className="w-full px-4 py-8">
|
|
<button onClick={params.onBack} className="text-blue-600 hover:text-blue-700 text-sm font-medium mb-4">
|
|
{t('common.back')}
|
|
</button>
|
|
<SeriesHeader series={params.series} />
|
|
<ArticleEditor
|
|
onPublishSuccess={handlePublishSuccess}
|
|
onCancel={params.onBack}
|
|
seriesOptions={[{ id: params.series.id, title: params.series.title }]}
|
|
onSelectSeries={() => {
|
|
// Series is already selected and cannot be changed
|
|
}}
|
|
defaultSeriesId={params.series.id}
|
|
/>
|
|
</div>
|
|
</main>
|
|
</>
|
|
)
|
|
}
|
|
|
|
function useSeriesPublishPageData(
|
|
seriesId: string,
|
|
userPubkey: string | null
|
|
): {
|
|
series: Series | null
|
|
loading: boolean
|
|
error: string | null
|
|
isAuthor: boolean
|
|
} {
|
|
const [series, setSeries] = useState<Series | null>(null)
|
|
const [loading, setLoading] = useState(true)
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
useEffect(() => {
|
|
if (!seriesId) {
|
|
return
|
|
}
|
|
const load = async (): Promise<void> => {
|
|
setLoading(true)
|
|
setError(null)
|
|
try {
|
|
const s = await getSeriesById(seriesId)
|
|
if (!s) {
|
|
setError('Série introuvable')
|
|
setLoading(false)
|
|
return
|
|
}
|
|
setSeries(s)
|
|
} catch (e) {
|
|
setError(e instanceof Error ? e.message : 'Erreur lors du chargement de la série')
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
void load()
|
|
}, [seriesId])
|
|
|
|
const isAuthor = series !== null && userPubkey !== null && series.pubkey === userPubkey
|
|
|
|
return { series, loading, error, isAuthor }
|
|
}
|