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

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

Added frontend and imporoved AI Suggestions service

  • Property mode set to 100644
File size: 5.1 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 addChapter(chapter)
59 addToast(isPublished ? 'Chapter published!' : 'Chapter saved as draft!')
60 navigate(`/writer/edit-story/${storyId}`)
61 setLoading(false)
62 }
63
64 return (
65 <div className="max-w-3xl mx-auto px-4 py-8">
66 <div className="flex items-center gap-3 mb-8">
67 <button onClick={() => navigate(`/writer/edit-story/${storyId}`)} className="text-slate-400 hover:text-white transition-colors">
68 <ArrowLeft size={20} />
69 </button>
70 <div>
71 <p className="text-slate-400 text-sm">{story.title}</p>
72 <h1 className="font-serif text-2xl font-bold text-white">New Chapter</h1>
73 <p className="text-slate-500 text-xs mt-0.5">Chapter {storyChapters.length + 1}</p>
74 </div>
75 </div>
76
77 <div className="space-y-6">
78 <div>
79 <label className="block text-sm font-medium text-slate-300 mb-1.5">Chapter Title *</label>
80 <input
81 value={form.title}
82 onChange={e => { setForm(f => ({ ...f, title: e.target.value })); setErrors(e => ({ ...e, title: '' })) }}
83 placeholder="The Awakening..."
84 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'}`}
85 />
86 {errors.title && <p className="text-rose-400 text-xs mt-1">{errors.title}</p>}
87 </div>
88
89 <div>
90 <div className="flex items-center justify-between mb-1.5">
91 <label className="text-sm font-medium text-slate-300">Chapter Content *</label>
92 <div className="flex items-center gap-2 text-xs text-slate-500">
93 <BookOpen size={12} />
94 <span className={wordCount > 100 ? 'text-emerald-400' : wordCount > 50 ? 'text-amber-400' : 'text-slate-500'}>
95 {wordCount.toLocaleString()} words
96 </span>
97 </div>
98 </div>
99 <textarea
100 value={form.content}
101 onChange={e => { setForm(f => ({ ...f, content: e.target.value })); setErrors(e => ({ ...e, content: '' })) }}
102 placeholder="Begin your chapter here..."
103 rows={20}
104 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'}`}
105 />
106 {errors.content && <p className="text-rose-400 text-xs mt-1">{errors.content}</p>}
107 </div>
108
109 <div className="flex gap-3 pt-4 border-t border-slate-700">
110 <Button variant="secondary" className="flex-1" onClick={() => handleSubmit(false)} loading={loading}>
111 Save as Draft
112 </Button>
113 <Button className="flex-1" onClick={() => handleSubmit(true)} loading={loading}>
114 Publish Chapter
115 </Button>
116 </div>
117 </div>
118 </div>
119 )
120}
Note: See TracBrowser for help on using the repository browser.