Files
pocket-chords/app/app/components/bottom-nav.tsx
Gabriel Kaszewski c4e9076b11 feat(app): auth UI, SPA mode, /api base URL
- auth: login/register bottom sheets, token storage, authFetch
- AuthProvider context w/ auto-refresh on mount
- hide add/edit/delete for guests, sign-in button in nav
- SPA mode (ssr:false), loaders→client-side useEffect
- API base URL /api (same-origin), loading spinners
2026-07-11 21:40:57 +02:00

105 lines
3.3 KiB
TypeScript

import { useState } from "react";
import { NavLink } from "react-router";
import { LogOut, Music, Sun, Moon, User } from "lucide-react";
import { useTheme } from "next-themes";
import { cn } from "~/lib/utils";
import { Button } from "~/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "~/components/ui/dropdown-menu";
import { useAuth } from "~/lib/auth";
import { LoginSheet } from "~/components/login-sheet";
import { RegisterSheet } from "~/components/register-sheet";
export function BottomNav() {
const { resolvedTheme, setTheme } = useTheme();
const { isAuthenticated, logout } = useAuth();
const [loginOpen, setLoginOpen] = useState(false);
const [registerOpen, setRegisterOpen] = useState(false);
return (
<>
<nav className="border-t bg-background shrink-0">
<div className="max-w-lg mx-auto flex items-center">
<NavLink
to="/"
end
className={({ isActive }) =>
cn(
"flex flex-col items-center gap-0.5 flex-1 py-2 text-xs transition-colors",
isActive
? "text-primary"
: "text-muted-foreground hover:text-foreground",
)
}
>
<Music className="w-5 h-5" />
<span>Library</span>
</NavLink>
<div className="flex items-center gap-1 mr-2">
<Button
variant="ghost"
size="icon"
className="text-muted-foreground hover:text-foreground"
onClick={() =>
setTheme(resolvedTheme === "dark" ? "light" : "dark")
}
aria-label="Toggle theme"
>
{resolvedTheme === "dark" ? (
<Sun className="w-5 h-5" />
) : (
<Moon className="w-5 h-5" />
)}
</Button>
{isAuthenticated ? (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="text-muted-foreground hover:text-foreground"
>
<User className="w-5 h-5" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => logout()}>
<LogOut className="w-4 h-4 mr-2" />
Sign out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
) : (
<Button
variant="ghost"
size="sm"
className="text-xs text-muted-foreground hover:text-foreground"
onClick={() => setLoginOpen(true)}
>
Sign in
</Button>
)}
</div>
</div>
</nav>
<LoginSheet
open={loginOpen}
onOpenChange={setLoginOpen}
onSwitchToRegister={() => setRegisterOpen(true)}
/>
<RegisterSheet
open={registerOpen}
onOpenChange={setRegisterOpen}
onSwitchToLogin={() => setLoginOpen(true)}
/>
</>
);
}