AppShell.tsx
2,855 bytes
| 1 | import { NavLink, Outlet, useNavigate } from "react-router-dom"; |
|---|---|
| 2 | import { Compass, Globe2, LogOut, Plane, Plus, Stamp, UserRound, UsersRound } from "lucide-react"; |
| 3 | import { Logo } from "./Logo"; |
| 4 | import { Avatar } from "./Ui"; |
| 5 | import { useAuth } from "../lib/auth"; |
| 6 | import { useQuickTaste } from "./QuickTaste"; |
| 7 | |
| 8 | const navigation = [ |
| 9 | { to: "/app", end: true, label: "Overview", icon: Compass }, |
| 10 | { to: "/app/explore", label: "Explore", icon: Globe2 }, |
| 11 | { to: "/app/tastings", label: "Tastings", icon: Stamp }, |
| 12 | { to: "/app/trips", label: "Trips", icon: Plane }, |
| 13 | { to: "/app/challenges", label: "Together", icon: UsersRound } |
| 14 | ]; |
| 15 | |
| 16 | export function AppShell() { |
| 17 | const { user, logout } = useAuth(); |
| 18 | const { openTaste } = useQuickTaste(); |
| 19 | const navigate = useNavigate(); |
| 20 | |
| 21 | return ( |
| 22 | <div className="app-frame"> |
| 23 | <a className="skip-link" href="#main-content">Skip to content</a> |
| 24 | <aside className="sidebar"> |
| 25 | <Logo to="/app" /> |
| 26 | <button className="button quick-stamp-button" type="button" onClick={() => openTaste()}> |
| 27 | <Plus size={18} /> Log a taste |
| 28 | </button> |
| 29 | <nav className="side-nav" aria-label="Main navigation"> |
| 30 | {navigation.map(item => { |
| 31 | const Icon = item.icon; |
| 32 | return ( |
| 33 | <NavLink key={item.to} to={item.to} end={item.end}> |
| 34 | <Icon size={19} /> |
| 35 | <span>{item.label}</span> |
| 36 | </NavLink> |
| 37 | ); |
| 38 | })} |
| 39 | </nav> |
| 40 | {user && ( |
| 41 | <div className="sidebar-account"> |
| 42 | <button type="button" className="account-link" onClick={() => navigate("/app/profile")}> |
| 43 | <Avatar name={user.displayName} url={user.avatarUrl} size="small" /> |
| 44 | <span> |
| 45 | <strong>{user.displayName}</strong> |
| 46 | <small>View passport</small> |
| 47 | </span> |
| 48 | <UserRound size={16} /> |
| 49 | </button> |
| 50 | <button className="logout-button" type="button" onClick={() => void logout()} aria-label="Sign out"> |
| 51 | <LogOut size={17} /> |
| 52 | </button> |
| 53 | </div> |
| 54 | )} |
| 55 | </aside> |
| 56 | |
| 57 | <header className="mobile-header"> |
| 58 | <Logo to="/app" /> |
| 59 | <button className="icon-button icon-button-primary" type="button" onClick={() => openTaste()} aria-label="Log a taste"> |
| 60 | <Plus size={21} /> |
| 61 | </button> |
| 62 | </header> |
| 63 | |
| 64 | <main id="main-content" className="app-main"> |
| 65 | <Outlet /> |
| 66 | </main> |
| 67 | |
| 68 | <nav className="mobile-nav" aria-label="Mobile navigation"> |
| 69 | {navigation.map(item => { |
| 70 | const Icon = item.icon; |
| 71 | return ( |
| 72 | <NavLink key={item.to} to={item.to} end={item.end}> |
| 73 | <Icon size={20} /> |
| 74 | <span>{item.label}</span> |
| 75 | </NavLink> |
| 76 | ); |
| 77 | })} |
| 78 | </nav> |
| 79 | </div> |
| 80 | ); |
| 81 | } |
| 82 | |