source: chapterx-frontend/src/pages/writer/CreateChapterPage.tsx@ d300631

main
Last change on this file since d300631 was acf690c, checked in by kikisrbinoska <srbinoskakristina07@…>, 4 months ago

Added fixes for the login,stories management and reading lists

  • Property mode set to 100644
File size: 5.4 KB
Line 
1import React, { useState } from 'react'
2import { useParams, useNavigate } from 'react-router-dom'
3import { ArrowLeft, BookOpen } from 'lucide-react'
4import { useAuthStore } from '../../store/authStore'
5import { useStoryStore } from '../../store/storyStore'
6import { useUIStore } from '../../store/uiStore'
7import { Button } from '../../components/ui/Button'
8import { Chapter } from '../../types'
9
10export const CreateChapterPage: React.FC = () => {
11 const { storyId } = useParams<{ storyId: string }>()
12 const navigate = useNavigate()
13 const { currentUser } = useAuthStore()
14 const { stories, chapters, addChapter } = useStoryStore()
15 const { addToast } = useUIStore()
16 const [form, setForm] = useState({ title: '', content: '' })
17 const [errors, setErrors] = useState<Record<string, string>>({})
18 const [loading, setLoading] = useState(false)
19
20 const story = stories.find(s => s.story_id === Number(storyId))
21 const storyChapters = chapters.filter(c => c.story_id === Number(storyId))
22 const wordCount = form.content.trim().split(/\s+/).filter(Boolean).length
23
24 if (!story) {
25 return (
26 <div className="max-w-4xl mx-auto px-4 py-20 text-center">
27 <h2 className="text-2xl text-white mb-4">Story not found</h2>
28 <Button onClick={() => navigate('/writer')}>Back</Button>
29 </div>
30 )
31 }
32
33 const validate = () => {
34 const e: Record<string, string> = {}
35 if (!form.title.trim()) e.title = 'Chapter title is required'
36 if (!form.content.trim()) e.content = 'Chapter content is required'
37 if (wordCount < 10) e.content = 'Chapter must be at least 10 words'
38 setErrors(e)
39 return Object.keys(e).length === 0
40 }
41
42 const handleSubmit = async (isPublished: boolean) => {
43 if (!validate() || !currentUser) return
44 setLoading(true)
45 await new Promise(r => setTimeout(r, 400))
46 const chapter: Chapter = {
47 chapter_id: Date.now(),
48 story_id: Number(storyId),
49 title: form.title.trim(),
50 content: form.content.trim(),
51 chapter_number: storyChapters.length + 1,
52 word_count: wordCount,
53 view_count: 0,
54 created_at: new Date().toISOString(),
55 updated_at: new Date().toISOString(),
56 is_published: isPublished,
57 }
58 try {
59 await addChapter(chapter)
60 } catch (err: any) {
61 const msg = err?.response?.data?.message || err?.message || 'Failed to save chapter.'
62 addToast(msg, 'error')
63 setLoading(false)
64 return
65 }
66 addToast(isPublished ? 'Chapter published!' : 'Chapter saved as draft!')
67 navigate(isPublished ? `/story/${storyId}` : `/writer/edit-story/${storyId}`)
68 setLoading(false)
69 }
70
71 return (
72 <div className="max-w-3xl mx-auto px-4 py-8">
73 <div className="flex items-center gap-3 mb-8">
74 <button onClick={() => navigate(`/writer/edit-story/${storyId}`)} className="text-slate-400 hover:text-white transition-colors">
75 <ArrowLeft size={20} />
76 </button>
77 <div>
78 <p className="text-slate-400 text-sm">{story.title}</p>
79 <h1 className="font-serif text-2xl font-bold text-white">New Chapter</h1>
80 <p className="text-slate-500 text-xs mt-0.5">Chapter {storyChapters.length + 1}</p>
81 </div>
82 </div>
83
84 <div className="space-y-6">
85 <div>
86 <label className="block text-sm font-medium text-slate-300 mb-1.5">Chapter Title *</label>
87 <input
88 value={form.title}
89 onChange={e => { setForm(f => ({ ...f, title: e.target.value })); setErrors(e => ({ ...e, title: '' })) }}
90 placeholder="The Awakening..."
91 className={`w-full px-4 py-3 bg-slate-800 border rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500 font-serif text-lg ${errors.title ? 'border-rose-500' : 'border-slate-700'}`}
92 />
93 {errors.title && <p className="text-rose-400 text-xs mt-1">{errors.title}</p>}
94 </div>
95
96 <div>
97 <div className="flex items-center justify-between mb-1.5">
98 <label className="text-sm font-medium text-slate-300">Chapter Content *</label>
99 <div className="flex items-center gap-2 text-xs text-slate-500">
100 <BookOpen size={12} />
101 <span className={wordCount > 100 ? 'text-emerald-400' : wordCount > 50 ? 'text-amber-400' : 'text-slate-500'}>
102 {wordCount.toLocaleString()} words
103 </span>
104 </div>
105 </div>
106 <textarea
107 value={form.content}
108 onChange={e => { setForm(f => ({ ...f, content: e.target.value })); setErrors(e => ({ ...e, content: '' })) }}
109 placeholder="Begin your chapter here..."
110 rows={20}
111 className={`w-full px-4 py-3 bg-slate-800 border rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500 resize-y font-serif text-base leading-relaxed ${errors.content ? 'border-rose-500' : 'border-slate-700'}`}
112 />
113 {errors.content && <p className="text-rose-400 text-xs mt-1">{errors.content}</p>}
114 </div>
115
116 <div className="flex gap-3 pt-4 border-t border-slate-700">
117 <Button variant="secondary" className="flex-1" onClick={() => handleSubmit(false)} loading={loading}>
118 Save as Draft
119 </Button>
120 <Button className="flex-1" onClick={() => handleSubmit(true)} loading={loading}>
121 Publish Chapter
122 </Button>
123 </div>
124 </div>
125 </div>
126 )
127}
Note: See TracBrowser for help on using the repository browser.