This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

at main 8.1 kB View raw
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;