Some checks failed
Deploy to Cloudflare Workers / deploy (push) Has been cancelled
157 lines
5.5 KiB
TypeScript
157 lines
5.5 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { usePathname } from "next/navigation";
|
|
import { cn } from "@/lib/utils";
|
|
import { YaltopiaFooter } from "./YaltopiaFooter";
|
|
import type { LucideIcon } from "lucide-react";
|
|
import {
|
|
Bell,
|
|
Search,
|
|
Settings,
|
|
Trophy,
|
|
LogOut,
|
|
} from "lucide-react";
|
|
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { createClient } from "@/lib/supabase/client";
|
|
import { useRouter } from "next/navigation";
|
|
|
|
export type NavItem = { href: string; label: string; icon: LucideIcon };
|
|
|
|
export function DashboardShell({
|
|
brand,
|
|
navItems,
|
|
user,
|
|
children,
|
|
}: {
|
|
brand: string;
|
|
navItems: NavItem[];
|
|
user: { name: string; email: string };
|
|
children: React.ReactNode;
|
|
}) {
|
|
const pathname = usePathname();
|
|
const router = useRouter();
|
|
const initials = user.name
|
|
.split(" ")
|
|
.map((n) => n[0])
|
|
.join("")
|
|
.slice(0, 2)
|
|
.toUpperCase();
|
|
|
|
async function signOut() {
|
|
const supabase = createClient();
|
|
await supabase.auth.signOut();
|
|
router.push("/");
|
|
router.refresh();
|
|
}
|
|
|
|
return (
|
|
<div className="retro-grid flex min-h-screen bg-background">
|
|
<aside className="hidden w-64 shrink-0 flex-col border-r border-border/80 bg-card/80 backdrop-blur-md md:flex">
|
|
<div className="flex h-14 items-center gap-2 border-b border-border/80 px-4">
|
|
<div className="flex h-9 w-9 items-center justify-center rounded-xl bg-primary shadow-[0_0_20px_-4px_var(--neon)]">
|
|
<Trophy className="h-4 w-4 text-primary-foreground" />
|
|
</div>
|
|
<div className="min-w-0">
|
|
<p className="font-display truncate text-sm font-bold uppercase tracking-wide">
|
|
Yaltopia FIFA
|
|
</p>
|
|
<p className="truncate text-[10px] uppercase tracking-wider text-neon-muted">
|
|
{brand}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<nav className="flex-1 space-y-0.5 p-3">
|
|
<p className="font-display px-3 pb-2 text-[10px] font-bold uppercase tracking-[0.2em] text-muted-foreground">
|
|
Menu
|
|
</p>
|
|
{navItems.map(({ href, label, icon: Icon }) => {
|
|
const active =
|
|
pathname === href ||
|
|
(href !== "/" && pathname.startsWith(href + "/"));
|
|
return (
|
|
<Link
|
|
key={href}
|
|
href={href}
|
|
className={cn(
|
|
"flex items-center gap-3 rounded-xl px-3 py-2.5 text-sm font-medium transition-all",
|
|
active
|
|
? "border border-neon/25 bg-neon/10 text-foreground shadow-[inset_3px_0_0_0_var(--neon)]"
|
|
: "text-muted-foreground hover:bg-secondary/80 hover:text-foreground"
|
|
)}
|
|
>
|
|
<Icon
|
|
className={cn(
|
|
"h-4 w-4 shrink-0",
|
|
active ? "text-neon" : "opacity-70"
|
|
)}
|
|
/>
|
|
{label}
|
|
</Link>
|
|
);
|
|
})}
|
|
</nav>
|
|
|
|
<div className="border-t border-border/80 p-3">
|
|
<div className="flex items-center gap-3 rounded-xl px-2 py-2">
|
|
<Avatar className="h-9 w-9 border border-border">
|
|
<AvatarFallback className="bg-secondary text-xs font-bold">
|
|
{initials}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
<div className="min-w-0 flex-1">
|
|
<p className="truncate text-sm font-semibold">{user.name}</p>
|
|
<p className="truncate text-xs text-muted-foreground">
|
|
{user.email}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="mt-1 w-full justify-start text-muted-foreground"
|
|
onClick={() => void signOut()}
|
|
>
|
|
<LogOut className="mr-2 h-4 w-4" />
|
|
Sign out
|
|
</Button>
|
|
</div>
|
|
</aside>
|
|
|
|
<div className="flex min-w-0 flex-1 flex-col">
|
|
<header className="sticky top-0 z-30 flex h-14 items-center gap-4 border-b border-border/80 bg-background/80 px-4 backdrop-blur-md lg:px-6">
|
|
<div className="relative max-w-md flex-1">
|
|
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
|
<Input
|
|
placeholder="Search…"
|
|
className="h-9 rounded-full border-border bg-muted/40 pl-9"
|
|
readOnly
|
|
/>
|
|
</div>
|
|
<div className="ml-auto flex items-center gap-1">
|
|
<Button variant="ghost" size="icon" className="rounded-full text-muted-foreground">
|
|
<Bell className="h-4 w-4" />
|
|
</Button>
|
|
<Button variant="ghost" size="icon" className="rounded-full text-muted-foreground">
|
|
<Settings className="h-4 w-4" />
|
|
</Button>
|
|
<div className="mx-1 h-6 w-px bg-border" aria-hidden />
|
|
<Avatar className="h-8 w-8 border border-border md:hidden">
|
|
<AvatarFallback className="text-[10px]">{initials}</AvatarFallback>
|
|
</Avatar>
|
|
</div>
|
|
</header>
|
|
|
|
<main className="flex-1 p-4 lg:p-6">{children}</main>
|
|
|
|
<div className="border-t border-border/80 px-6 py-3">
|
|
<YaltopiaFooter />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|