/** * TopicForm - Complete topic creation/edit form. * Title, category, tags, markdown editor with preview, cross-post options. * Client-side validation matching API Zod schemas. * @see specs/prd-web.md Section 4 (Editor Components) */ 'use client' import { useState, useCallback } from 'react' import type { CreateTopicInput, CategoryTreeNode } from '@/lib/api/types' import { cn } from '@/lib/utils' import { TopicMetaFields } from '@/components/topic-meta-fields' import { TopicContentEditor } from '@/components/topic-content-editor' import { TopicCrossPostSection } from '@/components/topic-cross-post-section' import { CrossPostAuthDialog } from '@/components/crosspost-auth-dialog' import { validateTopicForm } from '@/components/topic-form-validation' import type { TopicFormValues, FormErrors } from '@/components/topic-form-validation' import { useAuth } from '@/hooks/use-auth' interface TopicFormProps { onSubmit: (values: CreateTopicInput) => void | Promise initialValues?: Partial mode?: 'create' | 'edit' categories?: CategoryTreeNode[] submitting?: boolean className?: string } const CATEGORIES_FALLBACK: CategoryTreeNode[] = [ { id: 'fallback-general', slug: 'general', name: 'General Discussion', description: null, parentId: null, sortOrder: 0, communityDid: '', maturityRating: 'safe', createdAt: '', updatedAt: '', children: [], }, ] export function TopicForm({ onSubmit, initialValues, mode = 'create', categories = CATEGORIES_FALLBACK, submitting = false, className, }: TopicFormProps) { const [title, setTitle] = useState(initialValues?.title ?? '') const [content, setContent] = useState(initialValues?.content ?? '') const [category, setCategory] = useState(initialValues?.category ?? '') const [tagInput, setTagInput] = useState(initialValues?.tags?.join(', ') ?? '') const [crossPostBluesky, setCrossPostBluesky] = useState(initialValues?.crossPostBluesky ?? true) const [crossPostFrontpage, setCrossPostFrontpage] = useState( initialValues?.crossPostFrontpage ?? false ) const [errors, setErrors] = useState({}) const [showCrossPostAuthDialog, setShowCrossPostAuthDialog] = useState(false) const { crossPostScopesGranted, requestCrossPostAuth } = useAuth() const handleSubmit = useCallback( (e: React.FormEvent) => { e.preventDefault() const values: TopicFormValues = { title, content, category, tags: tagInput .split(',') .map((t) => t.trim()) .filter(Boolean), crossPostBluesky, crossPostFrontpage, } const validationErrors = validateTopicForm(values) setErrors(validationErrors) if (Object.keys(validationErrors).length > 0) return onSubmit({ title: values.title.trim(), content: values.content.trim(), category: values.category, tags: values.tags?.length ? values.tags : undefined, crossPostBluesky: values.crossPostBluesky, crossPostFrontpage: values.crossPostFrontpage, }) }, [title, content, category, tagInput, crossPostBluesky, crossPostFrontpage, onSubmit] ) return (
{mode === 'create' && ( setShowCrossPostAuthDialog(true)} /> )} { setShowCrossPostAuthDialog(false) void requestCrossPostAuth() }} onCancel={() => setShowCrossPostAuthDialog(false)} />
) }