source: chapterx-frontend/src/pages/story/ChapterReadPage.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: 6.8 KB
Line 
1import React, { useEffect, useState } from 'react'
2import { useParams, useNavigate } from 'react-router-dom'
3import { ArrowLeft, ArrowRight, BookOpen, Eye, ChevronLeft, ChevronRight } from 'lucide-react'
4import { useStoryStore } from '../../store/storyStore'
5import { Button } from '../../components/ui/Button'
6
7export const ChapterReadPage: React.FC = () => {
8 const { storyId, chapterId } = useParams<{ storyId: string; chapterId: string }>()
9 const navigate = useNavigate()
10 const { stories, chapters, incrementViewCount } = useStoryStore()
11 const [scrollProgress, setScrollProgress] = useState(0)
12 const [viewed, setViewed] = useState(false)
13
14 const story = stories.find(s => s.story_id === Number(storyId))
15 const chapter = chapters.find(c => c.chapter_id === Number(chapterId))
16 const storyChapters = chapters
17 .filter(c => c.story_id === Number(storyId) && c.is_published)
18 .sort((a, b) => a.chapter_number - b.chapter_number)
19
20 const currentIndex = storyChapters.findIndex(c => c.chapter_id === Number(chapterId))
21 const prevChapter = currentIndex > 0 ? storyChapters[currentIndex - 1] : null
22 const nextChapter = currentIndex < storyChapters.length - 1 ? storyChapters[currentIndex + 1] : null
23
24 // Track scroll progress
25 useEffect(() => {
26 const handler = () => {
27 const el = document.documentElement
28 const progress = (el.scrollTop / (el.scrollHeight - el.clientHeight)) * 100
29 setScrollProgress(Math.min(100, progress))
30 // Increment view count when 30% read
31 if (progress > 30 && !viewed) {
32 setViewed(true)
33 if (chapter) incrementViewCount(chapter.chapter_id)
34 }
35 }
36 window.addEventListener('scroll', handler)
37 return () => window.removeEventListener('scroll', handler)
38 }, [chapter, viewed, incrementViewCount])
39
40 if (!story || !chapter) {
41 return (
42 <div className="max-w-4xl mx-auto px-4 py-20 text-center">
43 <h2 className="text-2xl text-white mb-4">Chapter not found</h2>
44 <Button onClick={() => navigate('/browse')}>Browse Stories</Button>
45 </div>
46 )
47 }
48
49 return (
50 <div className="min-h-screen">
51 {/* Progress bar */}
52 <div className="fixed top-0 left-0 right-0 z-50 h-1 bg-slate-800">
53 <div
54 className="h-full bg-gradient-to-r from-indigo-500 to-violet-500 transition-all duration-100"
55 style={{ width: `${scrollProgress}%` }}
56 />
57 </div>
58
59 {/* Top nav */}
60 <div className="sticky top-1 z-40 glass border-b border-white/5">
61 <div className="max-w-3xl mx-auto px-4 py-3 flex items-center justify-between">
62 <button
63 onClick={() => navigate(`/story/${story.story_id}`)}
64 className="flex items-center gap-2 text-slate-400 hover:text-white text-sm transition-colors"
65 >
66 <ArrowLeft size={16} />
67 <span className="hidden sm:block">{story.title}</span>
68 </button>
69
70 <div className="text-center">
71 <p className="text-white text-sm font-medium">{chapter.title}</p>
72 <p className="text-slate-500 text-xs">Chapter {chapter.chapter_number}</p>
73 </div>
74
75 <div className="flex items-center gap-1 text-slate-400 text-xs">
76 <Eye size={12} />
77 {scrollProgress.toFixed(0)}%
78 </div>
79 </div>
80 </div>
81
82 {/* Content */}
83 <div className="max-w-3xl mx-auto px-4 py-12">
84 {/* Chapter header */}
85 <div className="text-center mb-12">
86 <p className="text-indigo-400 text-sm font-medium mb-2 uppercase tracking-widest">
87 Chapter {chapter.chapter_number}
88 </p>
89 <h1 className="font-serif text-3xl sm:text-4xl font-bold text-white mb-4">
90 {chapter.title}
91 </h1>
92 <div className="flex items-center justify-center gap-4 text-slate-500 text-sm">
93 <span className="flex items-center gap-1">
94 <BookOpen size={13} />
95 {chapter.word_count.toLocaleString()} words
96 </span>
97 <span className="flex items-center gap-1">
98 <Eye size={13} />
99 {chapter.view_count.toLocaleString()} reads
100 </span>
101 </div>
102 <div className="mt-4 w-16 h-px bg-gradient-to-r from-transparent via-indigo-500 to-transparent mx-auto" />
103 </div>
104
105 {/* Chapter content */}
106 <div className="prose prose-lg prose-invert max-w-none">
107 {chapter.content.split('\n\n').map((para, i) => (
108 <p
109 key={i}
110 className="text-slate-300 leading-relaxed text-lg mb-6 first-letter:text-4xl first-letter:font-serif first-letter:font-bold first-letter:text-white first-letter:float-left first-letter:mr-2 first-letter:mt-1"
111 style={i !== 0 ? { textIndent: '2rem' } : undefined}
112 >
113 {para}
114 </p>
115 ))}
116 </div>
117
118 {/* Chapter navigation */}
119 <div className="mt-16 pt-8 border-t border-slate-700">
120 <div className="flex items-center justify-between gap-4">
121 {prevChapter ? (
122 <button
123 onClick={() => navigate(`/story/${storyId}/chapter/${prevChapter.chapter_id}`)}
124 className="flex items-center gap-2 px-4 py-3 bg-slate-800 border border-slate-700 rounded-xl hover:border-indigo-500/50 transition-all group flex-1 max-w-[45%]"
125 >
126 <ChevronLeft size={18} className="text-slate-400 group-hover:text-indigo-400 flex-shrink-0" />
127 <div className="text-left min-w-0">
128 <p className="text-xs text-slate-500">Previous</p>
129 <p className="text-white text-sm truncate">{prevChapter.title}</p>
130 </div>
131 </button>
132 ) : (
133 <div className="flex-1" />
134 )}
135
136 <Button
137 variant="ghost"
138 size="sm"
139 onClick={() => navigate(`/story/${story.story_id}`)}
140 >
141 Contents
142 </Button>
143
144 {nextChapter ? (
145 <button
146 onClick={() => navigate(`/story/${storyId}/chapter/${nextChapter.chapter_id}`)}
147 className="flex items-center gap-2 px-4 py-3 bg-slate-800 border border-slate-700 rounded-xl hover:border-indigo-500/50 transition-all group flex-1 max-w-[45%] justify-end"
148 >
149 <div className="text-right min-w-0">
150 <p className="text-xs text-slate-500">Next</p>
151 <p className="text-white text-sm truncate">{nextChapter.title}</p>
152 </div>
153 <ChevronRight size={18} className="text-slate-400 group-hover:text-indigo-400 flex-shrink-0" />
154 </button>
155 ) : (
156 <div className="flex-1 text-center">
157 <p className="text-slate-500 text-sm">End of story</p>
158 </div>
159 )}
160 </div>
161 </div>
162 </div>
163 </div>
164 )
165}
Note: See TracBrowser for help on using the repository browser.