profileShare

rasmusjy / tasteprint

Read-only snapshot

No repository description.

main default branch 169 files Expires Sep 13, 2026, 9:06 AM
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