story-research-zapwall/hooks/useArticleEditing.ts
2026-01-10 09:41:57 +01:00

154 lines
4.8 KiB
TypeScript

import { useState } from 'react'
import type { ArticleDraft } from '@/lib/articlePublisher'
import type { ArticleUpdateResult } from '@/lib/articleMutations'
import { publishArticleUpdate, deleteArticleEvent, getStoredContent } from '@/lib/articleMutations'
import { nostrService } from '@/lib/nostr'
import type { Article } from '@/types/nostr'
interface EditState {
draft: ArticleDraft | null
articleId: string | null
}
type UseArticleEditingResult = {
editingDraft: ArticleDraft | null
editingArticleId: string | null
loading: boolean
error: string | null
startEditing: (article: Article) => Promise<void>
cancelEditing: () => void
submitEdit: () => Promise<ArticleUpdateResult | null>
deleteArticle: (articleId: string) => Promise<boolean>
updateDraft: (draft: ArticleDraft | null) => void
}
export function useArticleEditing(authorPubkey: string | null): UseArticleEditingResult {
const [state, setState] = useState<EditState>({ draft: null, articleId: null })
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const updateDraft = (draft: ArticleDraft | null): void => setState((prev) => ({ ...prev, draft }))
const startEditing = (article: Article): Promise<void> =>
startEditingArticle({ authorPubkey, article, setState, setLoading, setError })
const cancelEditing = (): void => resetEditingState({ setState, setError })
const submitEdit = (): Promise<ArticleUpdateResult | null> =>
submitArticleEdit({ authorPubkey, state, setState, setLoading, setError })
const deleteArticle = (articleId: string): Promise<boolean> =>
deleteArticleById({ authorPubkey, articleId, setLoading, setError })
return {
editingDraft: state.draft,
editingArticleId: state.articleId,
loading,
error,
startEditing,
cancelEditing,
submitEdit,
deleteArticle,
updateDraft,
}
}
function resetEditingState(params: {
setState: (value: EditState) => void
setError: (value: string | null) => void
}): void {
params.setState({ draft: null, articleId: null })
params.setError(null)
}
async function startEditingArticle(params: {
authorPubkey: string | null
article: Article
setState: (value: EditState) => void
setLoading: (value: boolean) => void
setError: (value: string | null) => void
}): Promise<void> {
if (!params.authorPubkey) {
params.setError('Connect your Nostr wallet to edit')
return
}
params.setLoading(true)
params.setError(null)
try {
const stored = await getStoredContent(params.article.id)
if (!stored) {
params.setError('Private content not available locally. Please republish from original device.')
return
}
params.setState({ articleId: params.article.id, draft: buildDraftForEdit(params.article, stored.content) })
} catch (e) {
params.setError(e instanceof Error ? e.message : 'Failed to load draft')
} finally {
params.setLoading(false)
}
}
function buildDraftForEdit(article: Article, content: string): ArticleDraft {
return {
title: article.title,
preview: article.preview,
content,
zapAmount: article.zapAmount,
...(article.category === 'science-fiction' || article.category === 'scientific-research' ? { category: article.category } : {}),
}
}
async function submitArticleEdit(params: {
authorPubkey: string | null
state: EditState
setState: (value: EditState) => void
setLoading: (value: boolean) => void
setError: (value: string | null) => void
}): Promise<ArticleUpdateResult | null> {
if (!params.authorPubkey || !params.state.articleId || !params.state.draft) {
params.setError('Missing data for update')
return null
}
params.setLoading(true)
params.setError(null)
try {
const privateKey = nostrService.getPrivateKey() ?? undefined
const result = await publishArticleUpdate(params.state.articleId, params.state.draft, params.authorPubkey, privateKey)
if (!result.success) {
params.setError(result.error ?? 'Update failed')
return null
}
return result
} catch (e) {
params.setError(e instanceof Error ? e.message : 'Update failed')
return null
} finally {
params.setLoading(false)
params.setState({ draft: null, articleId: null })
}
}
async function deleteArticleById(params: {
authorPubkey: string | null
articleId: string
setLoading: (value: boolean) => void
setError: (value: string | null) => void
}): Promise<boolean> {
if (!params.authorPubkey) {
params.setError('Connect your Nostr wallet to delete')
return false
}
params.setLoading(true)
params.setError(null)
try {
const privateKey = nostrService.getPrivateKey() ?? undefined
await deleteArticleEvent(params.articleId, params.authorPubkey, privateKey)
return true
} catch (e) {
params.setError(e instanceof Error ? e.message : 'Delete failed')
return false
} finally {
params.setLoading(false)
}
}