Write on the margins of the internet. Powered by the AT Protocol. margin.at
extension web atproto comments
at main 281 lines 11 kB view raw
1import { useStore } from "@nanostores/react"; 2import { 3 Bell, 4 Bookmark, 5 Folder, 6 Highlighter, 7 Home, 8 LogOut, 9 MessageSquareText, 10 MoreHorizontal, 11 PenSquare, 12 Search, 13 Settings, 14 User, 15 X, 16} from "lucide-react"; 17import React, { useEffect, useState } from "react"; 18import { Link, useLocation } from "react-router-dom"; 19import { getUnreadNotificationCount } from "../../api/client"; 20import { $user, logout } from "../../store/auth"; 21import { AppleIcon } from "../common/Icons"; 22 23export default function MobileNav() { 24 const user = useStore($user); 25 const location = useLocation(); 26 const [isMenuOpen, setIsMenuOpen] = useState(false); 27 const [unreadCount, setUnreadCount] = useState(0); 28 29 const isAuthenticated = !!user; 30 31 const isActive = (path: string) => { 32 if (path === "/") return location.pathname === "/"; 33 return location.pathname.startsWith(path); 34 }; 35 36 useEffect(() => { 37 if (isAuthenticated) { 38 getUnreadNotificationCount() 39 .then((count) => setUnreadCount(count || 0)) 40 .catch(() => {}); 41 } 42 }, [isAuthenticated]); 43 44 const closeMenu = () => setIsMenuOpen(false); 45 46 return ( 47 <> 48 {isMenuOpen && ( 49 <div 50 className="fixed inset-0 bg-black/50 z-40 md:hidden" 51 onClick={closeMenu} 52 /> 53 )} 54 55 {isMenuOpen && ( 56 <div className="fixed bottom-16 left-0 right-0 bg-white dark:bg-surface-900 rounded-t-2xl shadow-2xl z-50 md:hidden animate-slide-up"> 57 <div className="p-4 space-y-1"> 58 {isAuthenticated && user ? ( 59 <> 60 <Link 61 to={`/profile/${user.did}`} 62 className="flex items-center gap-3 p-3 rounded-xl hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors" 63 onClick={closeMenu} 64 > 65 {user.avatar ? ( 66 <img 67 src={user.avatar} 68 alt="" 69 className="w-10 h-10 rounded-full object-cover" 70 /> 71 ) : ( 72 <div className="w-10 h-10 rounded-full bg-surface-200 dark:bg-surface-700 flex items-center justify-center"> 73 <User size={18} className="text-surface-500" /> 74 </div> 75 )} 76 <div className="flex flex-col"> 77 <span className="font-semibold text-surface-900 dark:text-white"> 78 {user.displayName || user.handle} 79 </span> 80 <span className="text-sm text-surface-500"> 81 @{user.handle} 82 </span> 83 </div> 84 </Link> 85 86 <div className="h-px bg-surface-200 dark:bg-surface-700 my-2" /> 87 88 <Link 89 to="/annotations" 90 className="flex items-center gap-3 p-3 rounded-xl hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors text-surface-700 dark:text-surface-200" 91 onClick={closeMenu} 92 > 93 <MessageSquareText size={20} /> 94 <span>Annotations</span> 95 </Link> 96 97 <Link 98 to="/highlights" 99 className="flex items-center gap-3 p-3 rounded-xl hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors text-surface-700 dark:text-surface-200" 100 onClick={closeMenu} 101 > 102 <Highlighter size={20} /> 103 <span>Highlights</span> 104 </Link> 105 106 <Link 107 to="/bookmarks" 108 className="flex items-center gap-3 p-3 rounded-xl hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors text-surface-700 dark:text-surface-200" 109 onClick={closeMenu} 110 > 111 <Bookmark size={20} /> 112 <span>Bookmarks</span> 113 </Link> 114 115 <Link 116 to="/collections" 117 className="flex items-center gap-3 p-3 rounded-xl hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors text-surface-700 dark:text-surface-200" 118 onClick={closeMenu} 119 > 120 <Folder size={20} /> 121 <span>Collections</span> 122 </Link> 123 124 <Link 125 to="/settings" 126 className="flex items-center gap-3 p-3 rounded-xl hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors text-surface-700 dark:text-surface-200" 127 onClick={closeMenu} 128 > 129 <Settings size={20} /> 130 <span>Settings</span> 131 </Link> 132 133 <div className="h-px bg-surface-200 dark:bg-surface-700 my-2" /> 134 135 <a 136 href="https://www.icloud.com/shortcuts/1e33ebf52f55431fae1e187cfe9738c3" 137 target="_blank" 138 rel="noopener noreferrer" 139 className="flex items-center gap-3 p-3 rounded-xl hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors text-surface-700 dark:text-surface-200" 140 onClick={closeMenu} 141 > 142 <AppleIcon size={20} /> 143 <span>iOS Shortcut</span> 144 </a> 145 146 <div className="h-px bg-surface-200 dark:bg-surface-700 my-2" /> 147 148 <button 149 className="flex items-center gap-3 p-3 rounded-xl hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors text-red-600 w-full" 150 onClick={() => { 151 logout(); 152 closeMenu(); 153 }} 154 > 155 <LogOut size={20} /> 156 <span>Log Out</span> 157 </button> 158 </> 159 ) : ( 160 <> 161 <Link 162 to="/login" 163 className="flex items-center gap-3 p-3 rounded-xl hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors text-surface-700 dark:text-surface-200" 164 onClick={closeMenu} 165 > 166 <User size={20} /> 167 <span>Sign In</span> 168 </Link> 169 <Link 170 to="/collections" 171 className="flex items-center gap-3 p-3 rounded-xl hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors text-surface-700 dark:text-surface-200" 172 onClick={closeMenu} 173 > 174 <Folder size={20} /> 175 <span>Collections</span> 176 </Link> 177 <Link 178 to="/settings" 179 className="flex items-center gap-3 p-3 rounded-xl hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors text-surface-700 dark:text-surface-200" 180 onClick={closeMenu} 181 > 182 <Settings size={20} /> 183 <span>Settings</span> 184 </Link> 185 186 <div className="h-px bg-surface-200 dark:bg-surface-700 my-2" /> 187 188 <a 189 href="https://www.icloud.com/shortcuts/1e33ebf52f55431fae1e187cfe9738c3" 190 target="_blank" 191 rel="noopener noreferrer" 192 className="flex items-center gap-3 p-3 rounded-xl hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors text-surface-700 dark:text-surface-200" 193 onClick={closeMenu} 194 > 195 <AppleIcon size={20} /> 196 <span>iOS Shortcut</span> 197 </a> 198 </> 199 )} 200 </div> 201 </div> 202 )} 203 204 <nav className="fixed bottom-0 left-0 right-0 h-14 bg-white dark:bg-surface-900 border-t border-surface-200 dark:border-surface-700 flex items-center justify-around px-2 z-50 md:hidden safe-area-bottom"> 205 <Link 206 to="/home" 207 className={`flex flex-col items-center justify-center w-14 h-14 rounded-xl transition-colors ${ 208 isActive("/home") 209 ? "text-primary-600" 210 : "text-surface-500 hover:text-surface-700" 211 }`} 212 onClick={closeMenu} 213 > 214 <Home size={24} strokeWidth={1.5} /> 215 </Link> 216 217 <Link 218 to="/search" 219 className={`flex flex-col items-center justify-center w-14 h-14 rounded-xl transition-colors ${ 220 isActive("/search") 221 ? "text-primary-600" 222 : "text-surface-500 hover:text-surface-700" 223 }`} 224 onClick={closeMenu} 225 > 226 <Search size={24} strokeWidth={1.5} /> 227 </Link> 228 229 {isAuthenticated ? ( 230 <> 231 <Link 232 to="/new" 233 className="flex items-center justify-center w-12 h-12 rounded-full bg-primary-600 text-white shadow-lg hover:bg-primary-500 transition-colors -mt-4" 234 onClick={closeMenu} 235 > 236 <PenSquare size={20} strokeWidth={2} /> 237 </Link> 238 239 <Link 240 to="/notifications" 241 className={`relative flex flex-col items-center justify-center w-14 h-14 rounded-xl transition-colors ${ 242 isActive("/notifications") 243 ? "text-primary-600" 244 : "text-surface-500 hover:text-surface-700" 245 }`} 246 onClick={closeMenu} 247 > 248 <Bell size={24} strokeWidth={1.5} /> 249 {unreadCount > 0 && ( 250 <span className="absolute top-2 right-2 w-2 h-2 bg-red-500 rounded-full" /> 251 )} 252 </Link> 253 </> 254 ) : ( 255 <Link 256 to="/login" 257 className="flex items-center justify-center w-12 h-12 rounded-full bg-primary-600 text-white shadow-lg hover:bg-primary-500 transition-colors -mt-4" 258 onClick={closeMenu} 259 > 260 <User size={20} strokeWidth={2} /> 261 </Link> 262 )} 263 264 <button 265 className={`flex flex-col items-center justify-center w-14 h-14 rounded-xl transition-colors ${ 266 isMenuOpen 267 ? "text-primary-600" 268 : "text-surface-500 hover:text-surface-700" 269 }`} 270 onClick={() => setIsMenuOpen(!isMenuOpen)} 271 > 272 {isMenuOpen ? ( 273 <X size={24} strokeWidth={1.5} /> 274 ) : ( 275 <MoreHorizontal size={24} strokeWidth={1.5} /> 276 )} 277 </button> 278 </nav> 279 </> 280 ); 281}