my-fullstack-ai-platform/components/sidebar.tsx

143 lines
5.4 KiB
TypeScript
Raw Normal View History

"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>
);
};