(READ ONLY) Margin is an open annotation layer for the internet. Powered by the AT Protocol. margin.at
extension web atproto comments
99
fork

Configure Feed

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

at frontend-rewrite 204 lines 7.8 kB view raw
1import React, { useEffect, useState } from "react"; 2import { 3 Home, 4 Bookmark, 5 PenTool, 6 Settings, 7 LogOut, 8 Bell, 9 Sun, 10 Moon, 11 Monitor, 12 Folder, 13 LogIn, 14 PenSquare, 15} from "lucide-react"; 16import { useStore } from "@nanostores/react"; 17import { $user, logout } from "../../store/auth"; 18import { $theme, cycleTheme } from "../../store/theme"; 19import { getUnreadNotificationCount } from "../../api/client"; 20import { Link, useLocation } from "react-router-dom"; 21import { Avatar, CountBadge } from "../ui"; 22 23export default function Sidebar() { 24 const user = useStore($user); 25 const theme = useStore($theme); 26 const location = useLocation(); 27 const currentPath = location.pathname; 28 const [unreadCount, setUnreadCount] = useState(0); 29 30 useEffect(() => { 31 if (!user) return; 32 33 const checkNotifications = async () => { 34 const count = await getUnreadNotificationCount(); 35 setUnreadCount(count); 36 }; 37 38 checkNotifications(); 39 const interval = setInterval(checkNotifications, 30000); 40 return () => clearInterval(interval); 41 }, [user]); 42 43 const publicNavItems = [ 44 { icon: Home, label: "Feed", href: "/home", badge: undefined }, 45 { 46 icon: Bookmark, 47 label: "Bookmarks", 48 href: "/bookmarks", 49 badge: undefined, 50 }, 51 { 52 icon: PenTool, 53 label: "Highlights", 54 href: "/highlights", 55 badge: undefined, 56 }, 57 ]; 58 59 const authNavItems = [ 60 { icon: Home, label: "Feed", href: "/home" }, 61 { 62 icon: Bell, 63 label: "Activity", 64 href: "/notifications", 65 badge: unreadCount, 66 }, 67 { icon: Bookmark, label: "Bookmarks", href: "/bookmarks" }, 68 { icon: PenTool, label: "Highlights", href: "/highlights" }, 69 { icon: Folder, label: "Collections", href: "/collections" }, 70 ]; 71 72 const navItems = user ? authNavItems : publicNavItems; 73 74 return ( 75 <aside className="sticky top-0 h-screen hidden md:flex flex-col justify-between py-6 px-2 lg:px-3 z-50 border-r border-surface-200/60 dark:border-surface-800/60 w-[68px] lg:w-[220px] transition-all duration-200"> 76 <div className="flex flex-col gap-6"> 77 <Link 78 to="/home" 79 className="px-3 hover:opacity-80 transition-opacity w-fit flex items-center gap-2.5" 80 > 81 <img src="/logo.svg" alt="Margin" className="w-8 h-8" /> 82 <span className="font-display font-bold text-lg text-surface-900 dark:text-white tracking-tight hidden lg:inline"> 83 Margin 84 </span> 85 </Link> 86 87 <nav className="flex flex-col gap-0.5"> 88 {navItems.map((item) => { 89 const isActive = 90 currentPath === item.href || 91 (item.href !== "/home" && currentPath.startsWith(item.href)); 92 return ( 93 <Link 94 key={item.href} 95 to={item.href} 96 title={item.label} 97 className={`flex items-center justify-center lg:justify-start gap-3 px-0 lg:px-3 py-2.5 rounded-lg transition-all duration-150 text-[14px] group ${ 98 isActive 99 ? "font-semibold text-primary-700 dark:text-primary-300 bg-primary-50 dark:bg-primary-950/40" 100 : "font-medium text-surface-600 dark:text-surface-400 hover:bg-surface-100 dark:hover:bg-surface-800/60 hover:text-surface-900 dark:hover:text-white" 101 }`} 102 > 103 <item.icon 104 size={20} 105 className={`transition-colors ${isActive ? "text-primary-600 dark:text-primary-400" : ""}`} 106 strokeWidth={isActive ? 2.25 : 1.75} 107 /> 108 <span className="flex-1 hidden lg:inline">{item.label}</span> 109 {(item.badge ?? 0) > 0 && ( 110 <CountBadge count={item.badge ?? 0} /> 111 )} 112 </Link> 113 ); 114 })} 115 116 {user && ( 117 <Link 118 to="/new" 119 title="New annotation" 120 className="flex items-center justify-center lg:justify-start gap-3 px-0 lg:px-3 py-2.5 mt-2 rounded-lg bg-primary-600 dark:bg-primary-500 text-white hover:bg-primary-700 dark:hover:bg-primary-400 transition-colors text-[14px] font-semibold" 121 > 122 <PenSquare size={20} strokeWidth={1.75} /> 123 <span className="hidden lg:inline">New</span> 124 </Link> 125 )} 126 </nav> 127 </div> 128 129 <div className="space-y-1"> 130 <button 131 onClick={cycleTheme} 132 title={ 133 theme === "light" ? "Light" : theme === "dark" ? "Dark" : "System" 134 } 135 className="flex items-center justify-center lg:justify-start gap-3 px-0 lg:px-3 py-2.5 rounded-lg hover:bg-surface-100 dark:hover:bg-surface-800/60 text-[13px] font-medium text-surface-500 dark:text-surface-400 w-full transition-colors" 136 > 137 {theme === "light" ? ( 138 <Sun size={18} /> 139 ) : theme === "dark" ? ( 140 <Moon size={18} /> 141 ) : ( 142 <Monitor size={18} /> 143 )} 144 <span className="hidden lg:inline"> 145 {theme === "light" ? "Light" : theme === "dark" ? "Dark" : "System"} 146 </span> 147 </button> 148 149 {user ? ( 150 <> 151 <Link 152 to="/settings" 153 title="Settings" 154 className="flex items-center justify-center lg:justify-start gap-3 px-0 lg:px-3 py-2.5 rounded-lg hover:bg-surface-100 dark:hover:bg-surface-800/60 text-[13px] font-medium text-surface-500 dark:text-surface-400 transition-colors" 155 > 156 <Settings size={18} /> 157 <span className="hidden lg:inline">Settings</span> 158 </Link> 159 160 <div className="h-px bg-surface-200/60 dark:bg-surface-800/60 my-2" /> 161 162 <Link 163 to={`/profile/${user.did}`} 164 title={user.displayName || user.handle} 165 className="flex items-center justify-center lg:justify-start gap-2.5 p-2 rounded-lg hover:bg-surface-100 dark:hover:bg-surface-800/60 transition-colors w-full" 166 > 167 <Avatar did={user.did} avatar={user.avatar} size="sm" /> 168 <div className="flex-1 min-w-0 hidden lg:block"> 169 <p className="font-medium text-surface-900 dark:text-white truncate text-[13px]"> 170 {user.displayName || user.handle} 171 </p> 172 <p className="text-[11px] text-surface-500 dark:text-surface-400 truncate"> 173 @{user.handle} 174 </p> 175 </div> 176 </Link> 177 178 <button 179 onClick={logout} 180 title="Log out" 181 className="flex items-center justify-center lg:justify-start gap-3 px-0 lg:px-3 py-2 rounded-lg hover:bg-red-50 dark:hover:bg-red-900/20 text-[13px] font-medium text-surface-400 dark:text-surface-500 hover:text-red-600 dark:hover:text-red-400 w-full text-left transition-colors" 182 > 183 <LogOut size={16} /> 184 <span className="hidden lg:inline">Log out</span> 185 </button> 186 </> 187 ) : ( 188 <> 189 <div className="h-px bg-surface-200/60 dark:bg-surface-800/60 my-2" /> 190 191 <Link 192 to="/login" 193 title="Sign in" 194 className="flex items-center justify-center lg:justify-start gap-3 px-0 lg:px-3 py-2.5 rounded-lg bg-primary-50 dark:bg-primary-950/40 text-primary-700 dark:text-primary-300 hover:bg-primary-100 dark:hover:bg-primary-950/60 text-[13px] font-semibold transition-colors" 195 > 196 <LogIn size={18} /> 197 <span className="hidden lg:inline">Sign in</span> 198 </Link> 199 </> 200 )} 201 </div> 202 </aside> 203 ); 204}