142 lines
5.4 KiB
TypeScript
142 lines
5.4 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { usePathname, useRouter } from "next/navigation";
|
|
import { FC, useState, useEffect } from "react";
|
|
import { LayoutDashboard, Users, ChevronLeft, ChevronRight, User, LogOut } from "lucide-react";
|
|
import { cn } from "@/lib/utils";
|
|
import { createClient } from "@/lib/supabase/client";
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuLabel,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuTrigger,
|
|
} from "@/components/ui/dropdown-menu";
|
|
|
|
const navItems = [
|
|
{ href: "/dashboard", icon: LayoutDashboard, label: "Overview" },
|
|
{ href: "/dashboard/settings/team", icon: Users, label: "Team" },
|
|
];
|
|
|
|
export const Sidebar: FC = () => {
|
|
const pathname = usePathname();
|
|
const router = useRouter();
|
|
const [collapsed, setCollapsed] = useState(false);
|
|
const [user, setUser] = useState<{ id: string; email: string } | null>(null);
|
|
const [organizationName, setOrganizationName] = useState<string>("Organization");
|
|
const supabase = createClient();
|
|
|
|
useEffect(() => {
|
|
const fetchOrg = async (userId: string) => {
|
|
const { data: profile } = await supabase.from("profiles").select("organization_id").eq("id", userId).single();
|
|
if (profile?.organization_id) {
|
|
const { data: org } = await supabase.from("organizations").select("name").eq("id", profile.organization_id).single();
|
|
if (org?.name) {
|
|
setOrganizationName(org.name);
|
|
}
|
|
}
|
|
};
|
|
|
|
supabase.auth.getUser().then(({ data: { user: currentUser } }) => {
|
|
if (currentUser) {
|
|
setUser({ id: currentUser.id, email: currentUser.email ?? "" });
|
|
fetchOrg(currentUser.id);
|
|
}
|
|
});
|
|
|
|
const { data: { subscription } } = supabase.auth.onAuthStateChange((event, session) => {
|
|
if (session?.user) {
|
|
setUser({ id: session.user.id, email: session.user.email ?? "" });
|
|
fetchOrg(session.user.id);
|
|
} else {
|
|
setUser(null);
|
|
setOrganizationName("Organization");
|
|
}
|
|
});
|
|
|
|
return () => {
|
|
subscription.unsubscribe();
|
|
};
|
|
}, [supabase]);
|
|
|
|
const handleLogout = async () => {
|
|
await supabase.auth.signOut();
|
|
router.refresh();
|
|
};
|
|
|
|
return (
|
|
<aside
|
|
className={cn(
|
|
"flex flex-col h-screen border-r border-border bg-background transition-all duration-300",
|
|
collapsed ? "w-16" : "w-64"
|
|
)}
|
|
>
|
|
<div className="flex items-center justify-between p-4 border-b border-border h-16">
|
|
{!collapsed && <span className="font-semibold text-sm truncate">{organizationName}</span>}
|
|
<button
|
|
onClick={() => setCollapsed(!collapsed)}
|
|
className={cn("p-1 rounded-md hover:bg-muted transition-colors flex-shrink-0", collapsed && "mx-auto")}
|
|
>
|
|
{collapsed ? <ChevronRight size={18} /> : <ChevronLeft size={18} />}
|
|
</button>
|
|
</div>
|
|
|
|
<nav className="flex-1 p-2 space-y-1 overflow-y-auto">
|
|
{navItems.map((item) => {
|
|
// Exact match for the root dashboard, prefix match for others to keep them highlighted if nested
|
|
const isActive = item.href === "/dashboard" ? pathname === "/dashboard" : pathname.startsWith(item.href);
|
|
const Icon = item.icon;
|
|
return (
|
|
<Link
|
|
key={item.href}
|
|
href={item.href}
|
|
className={cn(
|
|
"flex items-center gap-3 px-3 py-2 rounded-md text-sm transition-colors",
|
|
isActive ? "bg-accent text-accent-foreground font-medium" : "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
collapsed && "justify-center px-0"
|
|
)}
|
|
>
|
|
<Icon size={18} className="shrink-0" />
|
|
{!collapsed && <span>{item.label}</span>}
|
|
</Link>
|
|
);
|
|
})}
|
|
</nav>
|
|
|
|
<div className="p-2 border-t border-border mt-auto">
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger className={cn(
|
|
"flex items-center gap-3 w-full p-2 rounded-md hover:bg-muted text-sm transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring data-[state=open]:bg-muted",
|
|
collapsed ? "justify-center px-0" : "px-3"
|
|
)}>
|
|
<div className="flex items-center justify-center bg-accent text-accent-foreground rounded-full w-8 h-8 shrink-0 overflow-hidden">
|
|
<User size={16} />
|
|
</div>
|
|
{!collapsed && (
|
|
<span className="truncate flex-1 text-left font-medium">
|
|
{user?.email || "User"}
|
|
</span>
|
|
)}
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align={collapsed ? "center" : "end"} side="right" sideOffset={8} className="w-56">
|
|
<DropdownMenuLabel className="font-normal">
|
|
<div className="flex flex-col space-y-1">
|
|
<p className="text-sm font-medium leading-none">Account</p>
|
|
<p className="text-xs leading-none text-muted-foreground">
|
|
{user?.email || "Unknown email"}
|
|
</p>
|
|
</div>
|
|
</DropdownMenuLabel>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem className="cursor-pointer text-destructive focus:text-destructive focus:bg-destructive/10" onClick={handleLogout}>
|
|
<LogOut className="mr-2 h-4 w-4" />
|
|
<span>Log out</span>
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</div>
|
|
</aside>
|
|
);
|
|
};
|