Write on the margins of the internet. Powered by the AT Protocol. margin.at
extension web atproto comments
at main 5.8 kB view raw
1import { useState, useRef, useEffect } from "react"; 2import { Link, useLocation } from "react-router-dom"; 3import { useAuth } from "../context/AuthContext"; 4import { 5 Home, 6 Search, 7 Folder, 8 Bell, 9 PenSquare, 10 User, 11 LogOut, 12 MoreHorizontal, 13 Highlighter, 14 Bookmark, 15} from "lucide-react"; 16import { getUnreadNotificationCount } from "../api/client"; 17import logo from "../assets/logo.svg"; 18 19export default function Sidebar() { 20 const { user, isAuthenticated, logout, loading } = useAuth(); 21 const location = useLocation(); 22 const [menuOpen, setMenuOpen] = useState(false); 23 const [unreadCount, setUnreadCount] = useState(0); 24 const menuRef = useRef(null); 25 26 const isActive = (path) => { 27 if (path === "/") return location.pathname === "/"; 28 return location.pathname.startsWith(path); 29 }; 30 31 useEffect(() => { 32 if (isAuthenticated) { 33 getUnreadNotificationCount() 34 .then((data) => setUnreadCount(data.count || 0)) 35 .catch(() => {}); 36 const interval = setInterval(() => { 37 getUnreadNotificationCount() 38 .then((data) => setUnreadCount(data.count || 0)) 39 .catch(() => {}); 40 }, 60000); 41 return () => clearInterval(interval); 42 } 43 }, [isAuthenticated]); 44 45 useEffect(() => { 46 const handleClickOutside = (e) => { 47 if (menuRef.current && !menuRef.current.contains(e.target)) { 48 setMenuOpen(false); 49 } 50 }; 51 document.addEventListener("mousedown", handleClickOutside); 52 return () => document.removeEventListener("mousedown", handleClickOutside); 53 }, []); 54 55 const getInitials = () => { 56 if (user?.displayName) { 57 return user.displayName.substring(0, 2).toUpperCase(); 58 } 59 if (user?.handle) { 60 return user.handle.substring(0, 2).toUpperCase(); 61 } 62 return "U"; 63 }; 64 65 return ( 66 <aside className="sidebar"> 67 <Link to="/" className="sidebar-header"> 68 <img src={logo} alt="Margin" className="sidebar-logo" /> 69 <span className="sidebar-brand">Margin</span> 70 </Link> 71 72 <nav className="sidebar-nav"> 73 <Link 74 to="/" 75 className={`sidebar-link ${isActive("/") ? "active" : ""}`} 76 > 77 <Home size={20} /> 78 <span>Home</span> 79 </Link> 80 <Link 81 to="/url" 82 className={`sidebar-link ${isActive("/url") ? "active" : ""}`} 83 > 84 <Search size={20} /> 85 <span>Browse</span> 86 </Link> 87 88 {isAuthenticated && ( 89 <> 90 <div className="sidebar-section-title">Library</div> 91 <Link 92 to="/highlights" 93 className={`sidebar-link ${isActive("/highlights") ? "active" : ""}`} 94 > 95 <Highlighter size={20} /> 96 <span>Highlights</span> 97 </Link> 98 <Link 99 to="/bookmarks" 100 className={`sidebar-link ${isActive("/bookmarks") ? "active" : ""}`} 101 > 102 <Bookmark size={20} /> 103 <span>Bookmarks</span> 104 </Link> 105 <Link 106 to="/collections" 107 className={`sidebar-link ${isActive("/collections") ? "active" : ""}`} 108 > 109 <Folder size={20} /> 110 <span>Collections</span> 111 </Link> 112 <Link 113 to="/notifications" 114 className={`sidebar-link ${isActive("/notifications") ? "active" : ""}`} 115 onClick={() => setUnreadCount(0)} 116 > 117 <Bell size={20} /> 118 <span>Notifications</span> 119 {unreadCount > 0 && ( 120 <span className="notification-badge">{unreadCount}</span> 121 )} 122 </Link> 123 </> 124 )} 125 </nav> 126 127 {isAuthenticated && ( 128 <Link to="/new" className="sidebar-new-btn"> 129 <PenSquare size={18} /> 130 <span>New</span> 131 </Link> 132 )} 133 134 <div className="sidebar-footer" ref={menuRef}> 135 {!loading && 136 (isAuthenticated ? ( 137 <> 138 <div 139 className="sidebar-user" 140 onClick={() => setMenuOpen(!menuOpen)} 141 > 142 <div className="sidebar-avatar"> 143 {user?.avatar ? ( 144 <img src={user.avatar} alt={user.displayName} /> 145 ) : ( 146 <span>{getInitials()}</span> 147 )} 148 </div> 149 <div className="sidebar-user-info"> 150 <div className="sidebar-user-name"> 151 {user?.displayName || user?.handle} 152 </div> 153 <div className="sidebar-user-handle">@{user?.handle}</div> 154 </div> 155 <MoreHorizontal size={18} className="sidebar-user-menu" /> 156 </div> 157 158 {menuOpen && ( 159 <div className="sidebar-dropdown"> 160 <Link 161 to={`/profile/${user?.did}`} 162 className="sidebar-dropdown-item" 163 onClick={() => setMenuOpen(false)} 164 > 165 <User size={16} /> 166 View Profile 167 </Link> 168 <button 169 onClick={() => { 170 logout(); 171 setMenuOpen(false); 172 }} 173 className="sidebar-dropdown-item danger" 174 > 175 <LogOut size={16} /> 176 Sign Out 177 </button> 178 </div> 179 )} 180 </> 181 ) : ( 182 <Link to="/login" className="sidebar-new-btn" style={{ margin: 0 }}> 183 Sign In 184 </Link> 185 ))} 186 </div> 187 </aside> 188 ); 189}