source: chapterx-frontend/src/pages/writer/WriterDashboard.tsx@ a8f4a2d

main
Last change on this file since a8f4a2d was a8f4a2d, checked in by kikisrbinoska <srbinoskakristina07@…>, 11 days ago

Fixed views count

  • Property mode set to 100644
File size: 8.0 KB
Line 
1import React, { useEffect } from 'react'
2import { useNavigate } from 'react-router-dom'
3import { Plus, BookOpen, Heart, MessageCircle, TrendingUp, Bell, Eye } from 'lucide-react'
4import { useAuthStore } from '../../store/authStore'
5import { useStoryStore } from '../../store/storyStore'
6import { useNotificationStore } from '../../store/notificationStore'
7import { Button } from '../../components/ui/Button'
8import { StatusBadge } from '../../components/ui/Badge'
9import { StoryAnalytics } from '../../components/writer/StoryAnalytics'
10
11function timeAgo(str: string): string {
12 const diff = Date.now() - new Date(str).getTime()
13 const m = Math.floor(diff / 60000)
14 if (m < 1) return 'just now'
15 if (m < 60) return `${m}m ago`
16 const h = Math.floor(m / 60)
17 if (h < 24) return `${h}h ago`
18 return `${Math.floor(h / 24)}d ago`
19}
20
21export const WriterDashboard: React.FC = () => {
22 const navigate = useNavigate()
23 const { currentUser } = useAuthStore()
24 const { stories, collaborations, fetchStories } = useStoryStore()
25 const { notifications } = useNotificationStore()
26
27 useEffect(() => { fetchStories() }, [])
28
29 if (!currentUser) return null
30
31 const ownedStories = stories.filter(s => s.user_id === currentUser.user_id)
32 const collabStoryIds = new Set(collaborations.filter(c => c.user_id === currentUser.user_id).map(c => c.story_id))
33 const collabStories = stories.filter(s => collabStoryIds.has(s.story_id))
34 const myStories = ownedStories
35 const allDashboardStories = [...ownedStories, ...collabStories]
36 const published = myStories.filter(s => s.status === 'published')
37 const drafts = myStories.filter(s => s.status === 'draft')
38 const totalLikes = myStories.reduce((acc, s) => acc + s.total_likes, 0)
39 const totalViews = myStories.reduce((acc, s) => acc + s.total_views, 0)
40
41 const recentNotifs = notifications.slice(0, 5)
42
43 return (
44 <div className="max-w-7xl mx-auto px-4 py-8">
45 {/* Header */}
46 <div className="flex items-center justify-between mb-8">
47 <div>
48 <h1 className="font-serif text-3xl font-bold text-white">
49 Welcome back, {currentUser.name}!
50 </h1>
51 <p className="text-slate-400 mt-1">Here's how your stories are performing</p>
52 </div>
53 <Button onClick={() => navigate('/writer/create-story')}>
54 <Plus size={16} />
55 New Story
56 </Button>
57 </div>
58
59 {/* Stats */}
60 <div className="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
61 {[
62 { icon: <BookOpen size={20} className="text-indigo-300" />, label: 'Total Stories', value: myStories.length, sub: `${published.length} published`, color: 'bg-indigo-500/20' },
63 { icon: <Heart size={20} className="text-rose-300" />, label: 'Total Likes', value: totalLikes.toLocaleString(), sub: 'Across all stories', color: 'bg-rose-500/20' },
64 { icon: <Eye size={20} className="text-cyan-300" />, label: 'Total Views', value: totalViews.toLocaleString(), sub: 'Chapter reads', color: 'bg-cyan-500/20' },
65 { icon: <TrendingUp size={20} className="text-emerald-300" />, label: 'Drafts', value: drafts.length, sub: 'In progress', color: 'bg-emerald-500/20' },
66 ].map(s => (
67 <div key={s.label} className="bg-slate-800 border border-slate-700 rounded-2xl p-5">
68 <div className={`w-10 h-10 rounded-xl ${s.color} flex items-center justify-center mb-3`}>
69 {s.icon}
70 </div>
71 <p className="text-2xl font-bold text-white">{s.value}</p>
72 <p className="text-slate-400 text-sm mt-0.5">{s.label}</p>
73 <p className="text-slate-600 text-xs mt-0.5">{s.sub}</p>
74 </div>
75 ))}
76 </div>
77
78 <div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
79 {/* My stories */}
80 <div className="lg:col-span-2">
81 <div className="flex items-center justify-between mb-4">
82 <h2 className="font-serif text-xl font-bold text-white">My Stories</h2>
83 </div>
84 {allDashboardStories.length === 0 ? (
85 <div className="bg-slate-800 border border-slate-700 rounded-2xl p-12 text-center">
86 <BookOpen size={40} className="mx-auto mb-4 text-slate-600" />
87 <h3 className="text-white font-medium mb-2">No stories yet</h3>
88 <p className="text-slate-500 text-sm mb-4">Start your writing journey today!</p>
89 <Button onClick={() => navigate('/writer/create-story')}>
90 <Plus size={14} />
91 Create Your First Story
92 </Button>
93 </div>
94 ) : (
95 <div className="space-y-3">
96 {allDashboardStories.map(story => {
97 const isCollab = collabStoryIds.has(story.story_id)
98 return (
99 <div key={story.story_id} className="flex items-center gap-4 p-4 bg-slate-800 border border-slate-700 rounded-xl hover:border-indigo-500/40 transition-colors">
100 <div className="flex-1 min-w-0">
101 <div className="flex items-center gap-2 mb-1">
102 <h3 className="text-white font-medium text-sm truncate">{story.title}</h3>
103 <StatusBadge status={story.status} />
104 {isCollab && (
105 <span className="px-2 py-0.5 text-xs font-medium rounded-full bg-violet-500/20 text-violet-400 border border-violet-500/30">
106 Collaborator
107 </span>
108 )}
109 </div>
110 <div className="flex items-center gap-3 text-slate-500 text-xs">
111 {isCollab && <span className="text-slate-500">by {story.author_username}</span>}
112 <span className="flex items-center gap-1"><Heart size={11} /> {story.total_likes}</span>
113 <span className="flex items-center gap-1"><MessageCircle size={11} /> {story.total_comments}</span>
114 <span className="flex items-center gap-1"><Eye size={11} /> {story.total_views.toLocaleString()}</span>
115 <span>{story.total_chapters} chapters</span>
116 </div>
117 </div>
118 <div className="flex gap-2 flex-shrink-0">
119 <Button size="sm" variant="ghost" onClick={() => navigate(`/story/${story.story_id}`)}>View</Button>
120 <Button size="sm" variant="secondary" onClick={() => navigate(`/writer/edit-story/${story.story_id}`)}>Edit</Button>
121 </div>
122 </div>
123 )
124 })}
125 </div>
126 )}
127 </div>
128
129 {/* Recent notifications */}
130 <div>
131 <div className="flex items-center gap-2 mb-4">
132 <Bell size={16} className="text-amber-400" />
133 <h2 className="font-serif text-xl font-bold text-white">Recent Activity</h2>
134 </div>
135 <div className="space-y-2">
136 {recentNotifs.length === 0 ? (
137 <div className="text-center py-8 text-slate-500 text-sm">No recent activity</div>
138 ) : (
139 recentNotifs.map(n => (
140 <div key={n.notification_id} className={`p-3 rounded-xl border text-sm ${
141 !n.is_read ? 'bg-indigo-500/5 border-indigo-500/20' : 'bg-slate-800 border-slate-700'
142 }`}>
143 <p className="text-white text-xs font-medium">{n.title}</p>
144 <p className="text-slate-400 text-xs mt-0.5 line-clamp-2">{n.message}</p>
145 <p className="text-slate-600 text-xs mt-1">{timeAgo(n.created_at)}</p>
146 </div>
147 ))
148 )}
149 </div>
150 </div>
151 </div>
152
153 {/* Analytics */}
154 {myStories.some(s => s.status === 'published') && (
155 <div className="mt-10">
156 <div className="flex items-center gap-2 mb-6">
157 <TrendingUp size={18} className="text-indigo-400" />
158 <h2 className="font-serif text-xl font-bold text-white">Analytics</h2>
159 </div>
160 <StoryAnalytics stories={myStories} />
161 </div>
162 )}
163 </div>
164 )
165}
Note: See TracBrowser for help on using the repository browser.