This repository has no description
1// src/components/Navbar/Navbar.jsx
2import React, { useContext, useState, useRef, useEffect } from 'react';
3import { Link, useNavigate } from 'react-router-dom';
4import { ThemeContext } from '../../contexts/ThemeContext';
5import { useAuth } from '../../contexts/AuthContext';
6import './Navbar.css';
7
8// Dropdown Menu Component
9const DropdownMenu = ({ title, path, items }) => {
10 const [isOpen, setIsOpen] = useState(false);
11 const dropdownRef = useRef(null);
12
13 // Handle click outside to close the dropdown
14 useEffect(() => {
15 const handleClickOutside = (event) => {
16 if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
17 setIsOpen(false);
18 }
19 };
20
21 // Add event listener only when dropdown is open
22 if (isOpen) {
23 document.addEventListener('mousedown', handleClickOutside);
24 }
25
26 return () => {
27 document.removeEventListener('mousedown', handleClickOutside);
28 };
29 }, [isOpen]);
30
31 // Add event listener to close dropdown on scroll
32 useEffect(() => {
33 const handleScroll = () => {
34 if (isOpen) {
35 setIsOpen(false);
36 }
37 };
38
39 window.addEventListener('scroll', handleScroll);
40 return () => {
41 window.removeEventListener('scroll', handleScroll);
42 };
43 }, [isOpen]);
44
45 // Detect if we're on mobile based on window width
46 const [windowWidth, setWindowWidth] = useState(window.innerWidth);
47 const isMobile = windowWidth <= 940;
48
49 // Update window width on resize
50 useEffect(() => {
51 const handleResize = () => {
52 setWindowWidth(window.innerWidth);
53 // Close dropdown on resize to prevent positioning issues
54 if (isOpen) {
55 setIsOpen(false);
56 }
57 };
58
59 window.addEventListener('resize', handleResize);
60 return () => {
61 window.removeEventListener('resize', handleResize);
62 };
63 }, [isOpen]);
64
65 return (
66 <li
67 className={`dropdown-container ${isOpen ? 'active' : ''}`}
68 ref={dropdownRef}
69 onMouseEnter={() => !isMobile && setIsOpen(true)}
70 onMouseLeave={() => !isMobile && setIsOpen(false)}
71 >
72 <Link
73 to={path}
74 className="dropdown-trigger"
75 onClick={(e) => {
76 if (isMobile) {
77 e.preventDefault();
78 setIsOpen(!isOpen);
79 }
80 }}
81 >
82 {title}
83 </Link>
84 {isOpen && (
85 <ul className="dropdown-menu">
86 {items.map((item, index) => (
87 <li key={index}>
88 <Link
89 to={item.path}
90 onClick={() => setIsOpen(false)}
91 >
92 {item.title}
93 </Link>
94 </li>
95 ))}
96 </ul>
97 )}
98 </li>
99 );
100};
101
102// Regular menu item component to ensure consistent styling
103const MenuItem = ({ title, path }) => {
104 return (
105 <li>
106 <Link to={path}>{title}</Link>
107 </li>
108 );
109};
110
111const Navbar = () => {
112 const { isDarkMode, toggleDarkMode } = useContext(ThemeContext);
113 const { isAuthenticated, session, logout } = useAuth();
114 const navigate = useNavigate();
115
116 // Define dropdown menus structure
117 const scoreDropdown = {
118 title: "score",
119 path: "/",
120 items: [
121 { title: "generate", path: "/" },
122 { title: "compare", path: "/compare" },
123 { title: "leaderboard", path: "/leaderboard" },
124 { title: "shortcut", path: "/shortcut" }
125 ]
126 };
127
128 const resourcesDropdown = {
129 title: "resources",
130 path: "/resources",
131 items: [
132 { title: "library", path: "/resources" },
133 { title: "alt text rating", path: "/alt-text" },
134 // { title: "omnifeed", path: "/omnifeed" }, // Temporarily removed
135 { title: "verifier", path: "/verifier" }
136 ]
137 };
138
139 const aboutDropdown = {
140 title: "about",
141 path: "/about",
142 items: [
143 { title: "about", path: "/about" },
144 { title: "methodology", path: "/methodology" },
145 { title: "definitions", path: "/definitions" }
146 ]
147 };
148
149 // Handle logout
150 const handleLogout = async () => {
151 await logout();
152 navigate('/');
153 };
154
155 // Get shortened display name from session
156 const getDisplayName = () => {
157 if (!session) return '';
158
159 // First try displayName if available
160 if (session.displayName) {
161 return session.displayName;
162 }
163
164 // Try to get the handle from the session
165 const handle = session.handle || '';
166 if (handle) {
167 return handle;
168 }
169
170 // Try to get DID (could be in session.did or session.sub)
171 const did = session.did || session.sub;
172 if (did && did.startsWith('did:')) {
173 return did.substring(0, 15) + '...';
174 }
175
176 // Fallback
177 return 'User';
178 };
179
180 return (
181 <header className="navbar">
182 <div className="navbar-container">
183 <div className="navbar-left">
184 <div className="navbar-logo">
185 <Link to="/">
186 <span className="cred">cred</span>
187 <span className="period">.</span>
188 <span className="blue">blue</span>
189 </Link>
190 <div className="beta-badge">
191 beta
192 </div>
193 </div>
194 <nav className="navbar-links">
195 <ul>
196 <DropdownMenu {...scoreDropdown} />
197 <DropdownMenu {...aboutDropdown} />
198 <DropdownMenu {...resourcesDropdown} />
199 </ul>
200 </nav>
201 </div>
202 <div className="navbar-actions">
203 {/* Social Links */}
204 <a
205 href="https://discord.gg/95ypHb2qPE"
206 target="_blank"
207 rel="noopener noreferrer"
208 className="nav-icon-discord"
209 aria-label="Discord Profile"
210 >
211 <svg className="icon" fill="currentColor">
212 <use href="/icons/icons-sprite.svg#icon-discord" />
213 </svg>
214 </a>
215 <a
216 href="https://bsky.app/profile/cred.blue"
217 target="_blank"
218 rel="noopener noreferrer"
219 className="nav-icon"
220 aria-label="Bluesky Profile"
221 >
222 <svg className="icon" fill="currentColor">
223 <use href="/icons/icons-sprite.svg#icon-bluesky" />
224 </svg>
225 </a>
226 <a
227 href="https://github.com/dame-is/cred.blue"
228 target="_blank"
229 rel="noopener noreferrer"
230 className="nav-icon"
231 aria-label="GitHub Profile"
232 >
233 <svg className="icon" fill="currentColor">
234 <use href="/icons/icons-sprite.svg#icon-github" />
235 </svg>
236 </a>
237 {/* Theme Toggle */}
238 <button
239 className="theme-toggle-button"
240 onClick={toggleDarkMode}
241 aria-label="Toggle dark mode"
242 >
243 <svg className="icon" fill="currentColor">
244 <use href={`/icons/icons-sprite.svg#icon-${isDarkMode ? 'sun' : 'moon'}`} />
245 </svg>
246 </button>
247
248 {/* Auth Button - Show Login or User Profile based on auth state */}
249 {isAuthenticated ? (
250 <div className="navbar-auth-container">
251 <div className="user-profile-button">
252 <button onClick={handleLogout} className="logout-button">
253 Logout
254 </button>
255 </div>
256 </div>
257 ) : (
258 <div className="navbar-auth-container">
259 <button
260 className="login-button"
261 type="button"
262 onClick={() => navigate('/login')}
263 >
264 login
265 </button>
266 </div>
267 )}
268
269 {/* Comment out the support button container
270 <div className="navbar-support-button-container">
271 <button
272 className="navbar-support-button"
273 type="button"
274 onClick={() => navigate(`/supporter`)}
275 >
276 Upgrade
277 </button>
278 </div>
279 */}
280 </div>
281 </div>
282 </header>
283 );
284};
285
286export default Navbar;