source: chapterx-frontend/src/components/layout/Navbar.tsx

main
Last change on this file was 73b69b2, checked in by kikisrbinoska <srbinoskakristina07@…>, 3 months ago

Fixed reading lists,comments and likes

  • Property mode set to 100644
File size: 10.4 KB
RevLine 
[b62cefc]1import React, { useState, useRef, useEffect } from 'react'
2import { Link, useNavigate, useLocation } from 'react-router-dom'
3import { Bell, Menu, X, ChevronDown, BookOpen, PenLine, Shield, List } from 'lucide-react'
4import logo from '../../assets/chapterX-removebg-preview.png'
5import { useAuthStore } from '../../store/authStore'
6import { useNotificationStore } from '../../store/notificationStore'
7import { Avatar } from '../ui/Avatar'
8import { RoleBadge } from '../ui/Badge'
9import { Button } from '../ui/Button'
10import { NotificationDropdown } from '../notifications/NotificationDropdown'
11
12export const Navbar: React.FC = () => {
13 const navigate = useNavigate()
14 const location = useLocation()
15 const { currentUser, logout } = useAuthStore()
[73b69b2]16 const { notifications, fetchUserNotifications } = useNotificationStore()
[b62cefc]17 const unread = notifications.filter(n => !n.is_read).length
18
[73b69b2]19 useEffect(() => {
20 if (currentUser) fetchUserNotifications(currentUser.user_id)
21 }, [currentUser?.user_id])
22
[b62cefc]23 const [mobileOpen, setMobileOpen] = useState(false)
24 const [userMenuOpen, setUserMenuOpen] = useState(false)
25 const [notifOpen, setNotifOpen] = useState(false)
26 const userMenuRef = useRef<HTMLDivElement>(null)
27 const notifRef = useRef<HTMLDivElement>(null)
28
29 useEffect(() => {
30 const handler = (e: MouseEvent) => {
31 if (userMenuRef.current && !userMenuRef.current.contains(e.target as Node)) setUserMenuOpen(false)
32 if (notifRef.current && !notifRef.current.contains(e.target as Node)) setNotifOpen(false)
33 }
34 document.addEventListener('mousedown', handler)
35 return () => document.removeEventListener('mousedown', handler)
36 }, [])
37
38 const navLinks = [
39 { to: '/browse', label: 'Browse', icon: <BookOpen size={16} /> },
40 { to: '/genres', label: 'Genres', icon: null },
41 { to: '/community-lists', label: 'Reading Lists', icon: <List size={16} /> },
42 ]
43
44 const isActive = (path: string) => location.pathname.startsWith(path)
45
46 return (
47 <nav className="sticky top-0 z-40 glass border-b border-white/10">
48 <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
49 <div className="flex items-center justify-between h-16">
50 {/* Logo */}
51 <Link to="/" className="flex items-center gap-2 flex-shrink-0 group">
52 <img src={logo} alt="ChapterX" className="h-14 w-14 object-contain group-hover:scale-110 transition-transform" />
53 </Link>
54
55 {/* Desktop nav */}
56 <div className="hidden md:flex items-center gap-1">
57 {navLinks.map(link => (
58 <Link
59 key={link.to}
60 to={link.to}
61 className={`flex items-center gap-1.5 px-3 py-2 rounded-lg text-sm font-medium transition-colors ${
62 isActive(link.to)
63 ? 'bg-indigo-500/20 text-indigo-300'
64 : 'text-slate-400 hover:text-white hover:bg-slate-800'
65 }`}
66 >
67 {link.icon}
68 {link.label}
69 </Link>
70 ))}
71 {currentUser?.role === 'writer' || currentUser?.role === 'admin' ? (
72 <Link
73 to="/writer"
74 className={`flex items-center gap-1.5 px-3 py-2 rounded-lg text-sm font-medium transition-colors ${
75 isActive('/writer')
76 ? 'bg-indigo-500/20 text-indigo-300'
77 : 'text-slate-400 hover:text-white hover:bg-slate-800'
78 }`}
79 >
80 <PenLine size={16} />
81 Write
82 </Link>
83 ) : null}
84 {currentUser?.role === 'admin' && (
85 <Link
86 to="/admin"
87 className={`flex items-center gap-1.5 px-3 py-2 rounded-lg text-sm font-medium transition-colors ${
88 isActive('/admin')
89 ? 'bg-rose-500/20 text-rose-300'
90 : 'text-slate-400 hover:text-white hover:bg-slate-800'
91 }`}
92 >
93 <Shield size={16} />
94 Admin
95 </Link>
96 )}
97 </div>
98
99 {/* Right side */}
100 <div className="flex items-center gap-2">
101 {currentUser ? (
102 <>
103 {/* Notification bell */}
104 <div className="relative" ref={notifRef}>
105 <button
[73b69b2]106 onClick={() => { const opening = !notifOpen; setNotifOpen(opening); setUserMenuOpen(false); if (opening && currentUser) fetchUserNotifications(currentUser.user_id) }}
[b62cefc]107 className="relative p-2 rounded-lg text-slate-400 hover:text-white hover:bg-slate-800 transition-colors"
108 >
109 <Bell size={18} />
110 {unread > 0 && (
111 <span className="absolute -top-0.5 -right-0.5 w-4 h-4 bg-rose-500 text-white text-xs rounded-full flex items-center justify-center font-medium">
112 {unread > 9 ? '9+' : unread}
113 </span>
114 )}
115 </button>
116 {notifOpen && <NotificationDropdown onClose={() => setNotifOpen(false)} />}
117 </div>
118
119 {/* User menu */}
120 <div className="relative" ref={userMenuRef}>
121 <button
122 onClick={() => { setUserMenuOpen(!userMenuOpen); setNotifOpen(false) }}
123 className="flex items-center gap-2 px-2 py-1.5 rounded-xl hover:bg-slate-800 transition-colors"
124 >
125 <Avatar name={currentUser.name} size="sm" />
126 <span className="hidden sm:block text-sm text-slate-300">{currentUser.username}</span>
127 <ChevronDown size={14} className="text-slate-500" />
128 </button>
129
130 {userMenuOpen && (
131 <div className="absolute right-0 top-full mt-2 w-52 bg-slate-900 border border-slate-700 rounded-2xl shadow-2xl z-50 overflow-hidden py-1">
132 <div className="px-4 py-3 border-b border-slate-700">
133 <p className="text-sm font-medium text-white">{currentUser.name} {currentUser.surname}</p>
134 <div className="flex items-center gap-2 mt-1">
135 <p className="text-xs text-slate-500">@{currentUser.username}</p>
136 <RoleBadge role={currentUser.role} />
137 </div>
138 </div>
139 <div className="py-1">
140 <button onClick={() => { navigate(`/profile/${currentUser.username}`); setUserMenuOpen(false) }}
141 className="w-full text-left px-4 py-2 text-sm text-slate-300 hover:bg-slate-800 hover:text-white transition-colors">
142 My Profile
143 </button>
144 <button onClick={() => { navigate('/reading-lists'); setUserMenuOpen(false) }}
145 className="w-full text-left px-4 py-2 text-sm text-slate-300 hover:bg-slate-800 hover:text-white transition-colors">
146 My Reading Lists
147 </button>
148 {(currentUser.role === 'writer' || currentUser.role === 'admin') && (
149 <button onClick={() => { navigate('/writer'); setUserMenuOpen(false) }}
150 className="w-full text-left px-4 py-2 text-sm text-slate-300 hover:bg-slate-800 hover:text-white transition-colors">
151 Writer Dashboard
152 </button>
153 )}
154 {currentUser.role === 'admin' && (
155 <button onClick={() => { navigate('/admin'); setUserMenuOpen(false) }}
156 className="w-full text-left px-4 py-2 text-sm text-slate-300 hover:bg-slate-800 hover:text-white transition-colors">
157 Admin Panel
158 </button>
159 )}
160 </div>
161 <div className="border-t border-slate-700 py-1">
162 <button
163 onClick={() => { logout(); setUserMenuOpen(false); navigate('/') }}
164 className="w-full text-left px-4 py-2 text-sm text-rose-400 hover:bg-slate-800 transition-colors"
165 >
166 Sign Out
167 </button>
168 </div>
169 </div>
170 )}
171 </div>
172 </>
173 ) : (
174 <div className="hidden sm:flex items-center gap-2">
175 <Button variant="ghost" size="sm" onClick={() => navigate('/login')}>
176 Sign In
177 </Button>
178 <Button size="sm" onClick={() => navigate('/register')}>
179 Get Started
180 </Button>
181 </div>
182 )}
183
184 {/* Mobile hamburger */}
185 <button
186 className="md:hidden p-2 rounded-lg text-slate-400 hover:text-white hover:bg-slate-800"
187 onClick={() => setMobileOpen(!mobileOpen)}
188 >
189 {mobileOpen ? <X size={20} /> : <Menu size={20} />}
190 </button>
191 </div>
192 </div>
193
194 {/* Mobile menu */}
195 {mobileOpen && (
196 <div className="md:hidden pb-4 border-t border-slate-800 mt-2 pt-4 space-y-1">
197 {navLinks.map(link => (
198 <Link
199 key={link.to}
200 to={link.to}
201 onClick={() => setMobileOpen(false)}
202 className={`flex items-center gap-2 px-3 py-2 rounded-lg text-sm transition-colors ${
203 isActive(link.to)
204 ? 'bg-indigo-500/20 text-indigo-300'
205 : 'text-slate-400 hover:text-white hover:bg-slate-800'
206 }`}
207 >
208 {link.icon}
209 {link.label}
210 </Link>
211 ))}
212 {!currentUser && (
213 <div className="flex gap-2 pt-2">
214 <Button variant="ghost" size="sm" onClick={() => { navigate('/login'); setMobileOpen(false) }} className="flex-1">
215 Sign In
216 </Button>
217 <Button size="sm" onClick={() => { navigate('/register'); setMobileOpen(false) }} className="flex-1">
218 Get Started
219 </Button>
220 </div>
221 )}
222 </div>
223 )}
224 </div>
225 </nav>
226 )
227}
Note: See TracBrowser for help on using the repository browser.