Changeset 99c1e45 for chapterx-frontend/src/pages
- Timestamp:
- 06/24/26 16:28:50 (12 days ago)
- Branches:
- main
- Children:
- a8f4a2d
- Parents:
- 0b502c2
- Location:
- chapterx-frontend/src/pages
- Files:
-
- 4 edited
-
profile/ProfilePage.tsx (modified) (6 diffs)
-
story/StoryDetailPage.tsx (modified) (5 diffs)
-
writer/CreateStoryPage.tsx (modified) (3 diffs)
-
writer/WriterDashboard.tsx (modified) (6 diffs)
Legend:
- Unmodified
- Added
- Removed
-
chapterx-frontend/src/pages/profile/ProfilePage.tsx
r0b502c2 r99c1e45 1 1 import React, { useState, useEffect } from 'react' 2 2 import { useParams, useNavigate } from 'react-router-dom' 3 import { BookOpen, Heart, Users, Calendar, MessageCircle, Eye } from 'lucide-react'3 import { BookOpen, Heart, Calendar, MessageCircle } from 'lucide-react' 4 4 import { useAuthStore } from '../../store/authStore' 5 5 import { useStoryStore } from '../../store/storyStore' … … 18 18 const navigate = useNavigate() 19 19 const { allUsers, currentUser, fetchAllUsers, updateUser } = useAuthStore() 20 const { stories, comments } = useStoryStore()20 const { stories, fetchStories } = useStoryStore() 21 21 const { addToast } = useUIStore() 22 22 const [tab, setTab] = useState<Tab>('stories') … … 27 27 28 28 useEffect(() => { 29 fetchStories() 29 30 if (allUsers.length === 0) { 30 31 setLoading(true) … … 74 75 const userStories = stories.filter(s => s.user_id === user.user_id && s.status === 'published') 75 76 const totalLikes = userStories.reduce((acc, s) => acc + s.total_likes, 0) 76 const totalViews = userStories.reduce((acc, s) => acc + s.total_views, 0) 77 const userComments = comments.filter(c => c.user_id === user.user_id) 77 const totalComments = userStories.reduce((acc, s) => acc + s.total_comments, 0) 78 78 const allGenres = [...new Set(userStories.flatMap(s => s.genres))] 79 79 … … 116 116 Joined {new Date(user.created_at).toLocaleDateString('en-US', { month: 'long', year: 'numeric' })} 117 117 </span> 118 <span className="flex items-center gap-1 text-slate-400 text-sm">119 <Users size={14} />120 {user.follower_count} followers · {user.following_count} following121 </span>122 118 </div> 123 119 </div> 124 120 125 121 {/* Stats */} 126 <div className="grid grid-cols- 2 sm:grid-cols-4gap-4 mb-8">122 <div className="grid grid-cols-3 gap-4 mb-8"> 127 123 {[ 128 124 { icon: <BookOpen size={16} className="text-indigo-400" />, value: userStories.length, label: 'Stories' }, 129 125 { icon: <Heart size={16} className="text-rose-400" />, value: totalLikes.toLocaleString(), label: 'Likes' }, 130 { icon: <Eye size={16} className="text-blue-400" />, value: totalViews.toLocaleString(), label: 'Views' }, 131 { icon: <MessageCircle size={16} className="text-amber-400" />, value: userComments.length, label: 'Comments' }, 126 { icon: <MessageCircle size={16} className="text-amber-400" />, value: totalComments, label: 'Comments' }, 132 127 ].map(s => ( 133 128 <div key={s.label} className="bg-slate-800 border border-slate-700 rounded-xl p-4 text-center"> … … 172 167 ) : ( 173 168 <div className="space-y-6"> 174 <div className="bg-slate-800 border border-slate-700 rounded-2xl p-6">175 <h3 className="text-white font-semibold mb-3">About</h3>176 <p className="text-slate-300">{user.bio || 'No bio provided.'}</p>177 </div>178 169 {allGenres.length > 0 && ( 179 170 <div className="bg-slate-800 border border-slate-700 rounded-2xl p-6"> -
chapterx-frontend/src/pages/story/StoryDetailPage.tsx
r0b502c2 r99c1e45 1 1 import React, { useState } from 'react' 2 2 import { useParams, useNavigate } from 'react-router-dom' 3 import { ArrowLeft, BookOpen, Eye,Users, Calendar, Plus, BookmarkPlus } from 'lucide-react'3 import { ArrowLeft, BookOpen, Users, Calendar, Plus, BookmarkPlus } from 'lucide-react' 4 4 import { useStoryStore } from '../../store/storyStore' 5 5 import { useAuthStore } from '../../store/authStore' … … 117 117 {/* Hero */} 118 118 <div className={`relative rounded-2xl overflow-hidden mb-8 bg-gradient-to-br ${gradient}`}> 119 {story.cover_image && ( 120 <img src={story.cover_image} alt={story.title} className="absolute inset-0 w-full h-full object-cover opacity-30" /> 121 )} 119 122 <div className="absolute inset-0 bg-gradient-to-t from-slate-950/90 via-slate-950/30 to-transparent" /> 120 123 <div className="relative p-8 sm:p-12"> … … 134 137 <Avatar name={story.author_username} size="sm" /> 135 138 <span className="text-white text-sm font-medium">{story.author_username}</span> 136 </div>137 <div className="flex items-center gap-1 text-slate-400 text-sm">138 <Eye size={14} />139 {story.total_views.toLocaleString()} views140 139 </div> 141 140 <div className="flex items-center gap-1 text-slate-400 text-sm"> … … 170 169 </div> 171 170 171 {/* Story content */} 172 {story.content && ( 173 <div className="bg-slate-800/50 border border-slate-700 rounded-2xl p-6"> 174 <p className="text-slate-200 leading-relaxed font-serif whitespace-pre-wrap">{story.content}</p> 175 </div> 176 )} 177 172 178 {/* Chapters */} 173 179 <div> … … 207 213 <span className="text-slate-400">Likes</span> 208 214 <span className="text-white">{(liveLikes ?? story.total_likes).toLocaleString()}</span> 209 </div>210 <div className="flex justify-between">211 <span className="text-slate-400">Views</span>212 <span className="text-white">{story.total_views.toLocaleString()}</span>213 215 </div> 214 216 <div className="flex justify-between"> -
chapterx-frontend/src/pages/writer/CreateStoryPage.tsx
r0b502c2 r99c1e45 21 21 short_description: '', 22 22 content: '', 23 cover_image: '', 23 24 genres: [] as string[], 24 25 mature_content: false, … … 45 46 story_id: Date.now(), 46 47 ...form, 48 cover_image: form.cover_image || undefined, 47 49 user_id: currentUser.user_id, 48 50 author_username: currentUser.username, … … 124 126 <span className="text-slate-600 text-xs">{form.short_description.length}/280</span> 125 127 </div> 128 </div> 129 130 {/* Cover image */} 131 <div> 132 <label className="block text-sm font-medium text-slate-300 mb-1.5">Cover Image URL <span className="text-slate-500 font-normal">(optional)</span></label> 133 <input 134 value={form.cover_image} 135 onChange={e => { 136 const val = e.target.value 137 if (!val || val.startsWith('http')) setField('cover_image', val) 138 }} 139 placeholder="https://example.com/image.jpg" 140 className="w-full px-4 py-3 bg-slate-800 border border-slate-700 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500" 141 /> 142 {form.cover_image?.startsWith('http') && ( 143 <img src={form.cover_image} alt="Cover preview" className="mt-2 h-32 w-full object-cover rounded-xl opacity-80" onError={e => (e.currentTarget.style.display = 'none')} /> 144 )} 126 145 </div> 127 146 -
chapterx-frontend/src/pages/writer/WriterDashboard.tsx
r0b502c2 r99c1e45 1 import React from 'react'1 import React, { useEffect } from 'react' 2 2 import { useNavigate } from 'react-router-dom' 3 import { Plus, BookOpen, Eye,Heart, MessageCircle, TrendingUp, Bell } from 'lucide-react'3 import { Plus, BookOpen, Heart, MessageCircle, TrendingUp, Bell } from 'lucide-react' 4 4 import { useAuthStore } from '../../store/authStore' 5 5 import { useStoryStore } from '../../store/storyStore' … … 22 22 const navigate = useNavigate() 23 23 const { currentUser } = useAuthStore() 24 const { stories, collaborations } = useStoryStore()24 const { stories, collaborations, fetchStories } = useStoryStore() 25 25 const { notifications } = useNotificationStore() 26 27 useEffect(() => { fetchStories() }, []) 26 28 27 29 if (!currentUser) return null … … 34 36 const published = myStories.filter(s => s.status === 'published') 35 37 const drafts = myStories.filter(s => s.status === 'draft') 36 const totalViews = myStories.reduce((acc, s) => acc + s.total_views, 0)37 38 const totalLikes = myStories.reduce((acc, s) => acc + s.total_likes, 0) 38 39 … … 56 57 57 58 {/* Stats */} 58 <div className="grid grid-cols-2 lg:grid-cols- 4gap-4 mb-8">59 <div className="grid grid-cols-2 lg:grid-cols-3 gap-4 mb-8"> 59 60 {[ 60 61 { icon: <BookOpen size={20} className="text-indigo-300" />, label: 'Total Stories', value: myStories.length, sub: `${published.length} published`, color: 'bg-indigo-500/20' }, 61 { icon: <Eye size={20} className="text-blue-300" />, label: 'Total Views', value: totalViews.toLocaleString(), sub: 'All time', color: 'bg-blue-500/20' },62 62 { icon: <Heart size={20} className="text-rose-300" />, label: 'Total Likes', value: totalLikes.toLocaleString(), sub: 'Across all stories', color: 'bg-rose-500/20' }, 63 63 { icon: <TrendingUp size={20} className="text-emerald-300" />, label: 'Drafts', value: drafts.length, sub: 'In progress', color: 'bg-emerald-500/20' }, … … 108 108 <div className="flex items-center gap-3 text-slate-500 text-xs"> 109 109 {isCollab && <span className="text-slate-500">by {story.author_username}</span>} 110 <span className="flex items-center gap-1"><Eye size={11} /> {story.total_views.toLocaleString()}</span>111 110 <span className="flex items-center gap-1"><Heart size={11} /> {story.total_likes}</span> 112 111 <span className="flex items-center gap-1"><MessageCircle size={11} /> {story.total_comments}</span> … … 155 154 <TrendingUp size={18} className="text-indigo-400" /> 156 155 <h2 className="font-serif text-xl font-bold text-white">Analytics</h2> 157 <span className="text-slate-500 text-sm">(Story: The Chronicles of Eldoria)</span>158 156 </div> 159 <StoryAnalytics />157 <StoryAnalytics stories={myStories} /> 160 158 </div> 161 159 )}
Note:
See TracChangeset
for help on using the changeset viewer.
