'use client'; import { useState, useEffect } from 'react'; import { useParams, useRouter } from 'next/navigation'; import AppLayout from '../../../../components/layout/AppLayout'; import { Input, Textarea } from '../../../../components/ui/Input'; import Button from '../../../../components/ui/Button'; import TagInput from '../../../../components/stories/TagInput'; import TagSuggestions from '../../../../components/tags/TagSuggestions'; import RichTextEditor from '../../../../components/stories/RichTextEditor'; import ImageUpload from '../../../../components/ui/ImageUpload'; import AuthorSelector from '../../../../components/stories/AuthorSelector'; import SeriesSelector from '../../../../components/stories/SeriesSelector'; import LoadingSpinner from '../../../../components/ui/LoadingSpinner'; import { storyApi } from '../../../../lib/api'; import { Story } from '../../../../types/api'; export default function EditStoryPage() { const params = useParams(); const router = useRouter(); const storyId = params.id as string; const [story, setStory] = useState(null); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [resetingPosition, setResetingPosition] = useState(false); const [errors, setErrors] = useState>({}); const [formData, setFormData] = useState({ title: '', summary: '', authorName: '', authorId: undefined as string | undefined, contentHtml: '', sourceUrl: '', tags: [] as string[], seriesName: '', seriesId: undefined as string | undefined, volume: '', }); const [coverImage, setCoverImage] = useState(null); useEffect(() => { const loadStory = async () => { try { setLoading(true); const storyData = await storyApi.getStory(storyId); setStory(storyData); // Initialize form with story data setFormData({ title: storyData.title, summary: storyData.summary || '', authorName: storyData.authorName, authorId: storyData.authorId, contentHtml: storyData.contentHtml, sourceUrl: storyData.sourceUrl || '', tags: storyData.tags?.map(tag => tag.name) || [], seriesName: storyData.seriesName || '', seriesId: storyData.seriesId, volume: storyData.volume?.toString() || '', }); } catch (error) { console.error('Failed to load story:', error); router.push('/library'); } finally { setLoading(false); } }; if (storyId) { loadStory(); } }, [storyId, router]); const handleInputChange = (field: string) => ( e: React.ChangeEvent ) => { setFormData(prev => ({ ...prev, [field]: e.target.value })); // Clear error when user starts typing if (errors[field]) { setErrors(prev => ({ ...prev, [field]: '' })); } }; const handleContentChange = (html: string) => { setFormData(prev => ({ ...prev, contentHtml: html })); if (errors.contentHtml) { setErrors(prev => ({ ...prev, contentHtml: '' })); } }; const handleTagsChange = (tags: string[]) => { setFormData(prev => ({ ...prev, tags })); }; const handleAddSuggestedTag = (tagName: string) => { if (!formData.tags.includes(tagName.toLowerCase())) { setFormData(prev => ({ ...prev, tags: [...prev.tags, tagName.toLowerCase()] })); } }; const handleAuthorChange = (authorName: string, authorId?: string) => { setFormData(prev => ({ ...prev, authorName, authorId: authorId // This will be undefined if creating new author, which clears the existing ID })); // Clear error when user changes author if (errors.authorName) { setErrors(prev => ({ ...prev, authorName: '' })); } }; const handleSeriesChange = (seriesName: string, seriesId?: string) => { setFormData(prev => ({ ...prev, seriesName, seriesId: seriesId // This will be undefined if creating new series, which clears the existing ID })); // Clear error when user changes series if (errors.seriesName) { setErrors(prev => ({ ...prev, seriesName: '' })); } }; const validateForm = () => { const newErrors: Record = {}; if (!formData.title.trim()) { newErrors.title = 'Title is required'; } if (!formData.authorName.trim()) { newErrors.authorName = 'Author name is required'; } if (!formData.contentHtml.trim()) { newErrors.contentHtml = 'Story content is required'; } if (formData.seriesName && !formData.volume) { newErrors.volume = 'Volume number is required when series is specified'; } if (formData.volume && !formData.seriesName.trim()) { newErrors.seriesName = 'Series name is required when volume is specified'; } setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!validateForm() || !story) { return; } setSaving(true); try { // Update the story with JSON data const updateData = { title: formData.title, summary: formData.summary || undefined, contentHtml: formData.contentHtml, sourceUrl: formData.sourceUrl || undefined, volume: formData.seriesName && formData.volume ? parseInt(formData.volume) : undefined, // Send seriesId if we have it (existing series), otherwise send seriesName (new/changed series) ...(formData.seriesId ? { seriesId: formData.seriesId } : { seriesName: formData.seriesName }), // Send authorId if we have it (existing author), otherwise send authorName (new/changed author) ...(formData.authorId ? { authorId: formData.authorId } : { authorName: formData.authorName }), tagNames: formData.tags, }; const updatedStory = await storyApi.updateStory(storyId, updateData); // If there's a new cover image, upload it separately if (coverImage) { await storyApi.uploadCover(storyId, coverImage); } router.push(`/stories/${storyId}`); } catch (error: any) { console.error('Failed to update story:', error); const errorMessage = error.response?.data?.message || 'Failed to update story'; setErrors({ submit: errorMessage }); } finally { setSaving(false); } }; const handleResetReadingPosition = async () => { if (!story || !confirm('Are you sure you want to reset the reading position to the beginning? This will remove your current place in the story.')) { return; } try { setResetingPosition(true); await storyApi.updateReadingProgress(storyId, 0); setStory(prev => prev ? { ...prev, readingPosition: 0 } : null); // Show success feedback setErrors({ resetSuccess: 'Reading position reset! The story will start from the beginning next time you read it.' }); // Clear success message after 4 seconds setTimeout(() => { setErrors(prev => { const { resetSuccess, ...rest } = prev; return rest; }); }, 4000); } catch (error) { console.error('Failed to reset reading position:', error); setErrors({ submit: 'Failed to reset reading position' }); } finally { setResetingPosition(false); } }; const handleDelete = async () => { if (!story || !confirm('Are you sure you want to delete this story? This action cannot be undone.')) { return; } try { setSaving(true); await storyApi.deleteStory(storyId); router.push('/library'); } catch (error) { console.error('Failed to delete story:', error); setErrors({ submit: 'Failed to delete story' }); } finally { setSaving(false); } }; if (loading) { return (
); } if (!story) { return (

Story Not Found

); } return (

Edit Story

Make changes to "{story.title}"

{/* Title */} {/* Author Selector */} {/* Summary */}