181 lines
5.0 KiB
TypeScript
181 lines
5.0 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 (
|
|
<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>
|
|
)
|
|
}
|
|
|
|
if (error || !series) {
|
|
return (
|
|
<main className="min-h-screen bg-gray-50">
|
|
<div className="w-full px-4 py-8">
|
|
<p className="text-sm text-red-600">{error ?? 'Série introuvable'}</p>
|
|
<button
|
|
onClick={() => {
|
|
void router.back()
|
|
}}
|
|
className="mt-4 text-blue-600 hover:text-blue-700 text-sm"
|
|
>
|
|
{t('common.back')}
|
|
</button>
|
|
</div>
|
|
</main>
|
|
)
|
|
}
|
|
|
|
if (!isAuthor) {
|
|
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={() => {
|
|
void router.push(`/series/${seriesId}`)
|
|
}}
|
|
className="mt-4 text-blue-600 hover:text-blue-700 text-sm"
|
|
>
|
|
{t('common.back')}
|
|
</button>
|
|
</div>
|
|
</main>
|
|
)
|
|
}
|
|
|
|
const handlePublishSuccess = (): void => {
|
|
setTimeout(() => {
|
|
void router.push(`/series/${seriesId}`)
|
|
}, 2000)
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<PublishHeader series={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={() => {
|
|
void router.push(`/series/${seriesId}`)
|
|
}}
|
|
className="text-blue-600 hover:text-blue-700 text-sm font-medium mb-4"
|
|
>
|
|
{t('common.back')}
|
|
</button>
|
|
<SeriesHeader series={series} />
|
|
<ArticleEditor
|
|
onPublishSuccess={handlePublishSuccess}
|
|
onCancel={() => {
|
|
void router.push(`/series/${seriesId}`)
|
|
}}
|
|
seriesOptions={[{ id: series.id, title: series.title }]}
|
|
onSelectSeries={() => {
|
|
// Series is already selected and cannot be changed
|
|
}}
|
|
defaultSeriesId={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 }
|
|
}
|