import { useEffect, useState, type Dispatch, type SetStateAction } from 'react' import type { Article } from '@/types/nostr' import type { ArticleDraft } from '@/lib/articlePublisherTypes' import { useArticleEditing } from '@/hooks/useArticleEditing' import { UserArticlesView } from './UserArticlesList' import { EditPanel } from './UserArticlesEditPanel' interface UserArticlesProps { articles: Article[] loading: boolean error: string | null onLoadContent: (articleId: string, authorPubkey: string) => Promise
showEmptyMessage?: boolean currentPubkey: string | null onSelectSeries?: ((seriesId: string | undefined) => void) | undefined } export function UserArticles({ articles, loading, error, onLoadContent, showEmptyMessage = true, currentPubkey, onSelectSeries, }: UserArticlesProps): React.ReactElement { const controller = useUserArticlesController({ articles, onLoadContent, currentPubkey }) return ( ) } function useUserArticlesController({ articles, onLoadContent, currentPubkey, }: { articles: Article[] onLoadContent: (articleId: string, authorPubkey: string) => Promise
currentPubkey: string | null }): { localArticles: Article[] unlockedArticles: Set pendingDeleteId: string | null requestDelete: (id: string) => void handleUnlock: (article: Article) => Promise handleDelete: (article: Article) => Promise handleEditSubmit: () => Promise editingDraft: ArticleDraft | null editingArticleId: string | null loading: boolean error: string | null updateDraft: (draft: ArticleDraft) => void startEditing: (article: Article) => Promise cancelEditing: () => void submitEdit: () => Promise deleteArticle: (id: string) => Promise } { const [localArticles, setLocalArticles] = useState(articles) const [unlockedArticles, setUnlockedArticles] = useState>(new Set()) const [pendingDeleteId, setPendingDeleteId] = useState(null) const editingCtx = useArticleEditing(currentPubkey) useEffect(() => setLocalArticles(articles), [articles]) return { localArticles, unlockedArticles, pendingDeleteId, requestDelete: (id: string) => setPendingDeleteId(id), handleUnlock: createHandleUnlock(onLoadContent, setUnlockedArticles), handleDelete: createHandleDelete(editingCtx.deleteArticle, setLocalArticles, setPendingDeleteId), handleEditSubmit: createHandleEditSubmit( editingCtx.submitEdit, editingCtx.editingDraft, currentPubkey, setLocalArticles ), ...editingCtx, } } function createHandleUnlock( onLoadContent: (id: string, pubkey: string) => Promise
, setUnlocked: Dispatch>> ): (article: Article) => Promise { return async (article: Article): Promise => { const full = await onLoadContent(article.id, article.pubkey) if (full?.paid) { setUnlocked((prev) => new Set([...prev, article.id])) } } } function createHandleDelete( deleteArticle: (id: string) => Promise, setLocalArticles: Dispatch>, setPendingDeleteId: Dispatch> ): (article: Article) => Promise { return async (article: Article): Promise => { const ok = await deleteArticle(article.id) if (ok) { setLocalArticles((prev) => prev.filter((a) => a.id !== article.id)) } setPendingDeleteId(null) } } function createHandleEditSubmit( submitEdit: () => Promise, draft: ReturnType['editingDraft'], currentPubkey: string | null, setLocalArticles: Dispatch> ): () => Promise { return async (): Promise => { const result = await submitEdit() if (result && draft) { const updated = buildUpdatedArticle(draft, currentPubkey ?? '', result.articleId) setLocalArticles((prev) => { const filtered = prev.filter((a) => a.id !== result.originalArticleId) return [updated, ...filtered] }) } } } function buildUpdatedArticle( draft: NonNullable['editingDraft']>, pubkey: string, newId: string ): Article { const hash = newId.split('_')[0] ?? '' const index = Number.parseInt(newId.split('_')[1] ?? '0', 10) const version = Number.parseInt(newId.split('_')[2] ?? '0', 10) return { id: newId, hash, version, index, pubkey, title: draft.title, preview: draft.preview, content: '', description: draft.preview, contentDescription: draft.preview, createdAt: Math.floor(Date.now() / 1000), zapAmount: draft.zapAmount, paid: false, thumbnailUrl: draft.bannerUrl ?? '', ...(draft.category ? { category: draft.category } : {}), ...(draft.seriesId ? { seriesId: draft.seriesId } : {}), ...(draft.bannerUrl ? { bannerUrl: draft.bannerUrl } : {}), ...(draft.media ? { media: draft.media } : {}), ...(draft.pages ? { pages: draft.pages } : {}), } } function UserArticlesLayout({ controller, loading, error, showEmptyMessage, currentPubkey, onSelectSeries, }: { controller: ReturnType loading: boolean error: string | null showEmptyMessage: boolean currentPubkey: string | null onSelectSeries?: ((seriesId: string | undefined) => void) | undefined }): React.ReactElement { const { editPanelProps, listProps } = createLayoutProps(controller, { loading, error, showEmptyMessage, currentPubkey, onSelectSeries, }) return (
) } function createLayoutProps( controller: ReturnType, view: { loading: boolean error: string | null showEmptyMessage: boolean currentPubkey: string | null onSelectSeries?: ((seriesId: string | undefined) => void) | undefined } ): { editPanelProps: { draft: ArticleDraft | null editingArticleId: string | null loading: boolean error: string | null onCancel: () => void onDraftChange: (draft: ArticleDraft) => void onSubmit: () => Promise } listProps: { articles: Article[] loading: boolean error: string | null showEmptyMessage: boolean unlockedArticles: Set onUnlock: (article: Article) => void onEdit: (article: Article) => void onDelete: (article: Article) => void editingArticleId: string | null currentPubkey: string | null pendingDeleteId: string | null requestDelete: (articleId: string) => void onSelectSeries?: ((seriesId: string | undefined) => void) | undefined } } { return { editPanelProps: buildEditPanelProps(controller), listProps: buildListProps(controller, view), } } function buildEditPanelProps(controller: ReturnType): { draft: ArticleDraft | null editingArticleId: string | null loading: boolean error: string | null onCancel: () => void onDraftChange: (draft: ArticleDraft) => void onSubmit: () => Promise } { return { draft: controller.editingDraft, editingArticleId: controller.editingArticleId, loading: controller.loading, error: controller.error, onCancel: controller.cancelEditing, onDraftChange: controller.updateDraft, onSubmit: controller.handleEditSubmit, } } function buildListProps( controller: ReturnType, view: { loading: boolean error: string | null showEmptyMessage: boolean currentPubkey: string | null onSelectSeries?: ((seriesId: string | undefined) => void) | undefined } ): { articles: Article[] loading: boolean error: string | null showEmptyMessage: boolean unlockedArticles: Set onUnlock: (article: Article) => void onEdit: (article: Article) => void onDelete: (article: Article) => void editingArticleId: string | null currentPubkey: string | null pendingDeleteId: string | null requestDelete: (articleId: string) => void onSelectSeries?: ((seriesId: string | undefined) => void) | undefined } { return { articles: controller.localArticles, loading: view.loading, error: view.error, showEmptyMessage: view.showEmptyMessage, unlockedArticles: controller.unlockedArticles, onUnlock: (a: Article) => { void controller.handleUnlock(a) }, onEdit: (a: Article) => { void controller.startEditing(a) }, onDelete: (a: Article) => { void controller.handleDelete(a) }, editingArticleId: controller.editingArticleId, currentPubkey: view.currentPubkey, pendingDeleteId: controller.pendingDeleteId, requestDelete: controller.requestDelete, ...(view.onSelectSeries ? { onSelectSeries: view.onSelectSeries } : {}), } }