3d381944d2
Next.js + Express event management app for Hope Family Church.
107 lines
3.8 KiB
TypeScript
107 lines
3.8 KiB
TypeScript
"use client";
|
|
|
|
import React from "react";
|
|
import Link from "next/link";
|
|
import { useAuth } from "@/hooks/useAuth";
|
|
import { RoleBadge } from "@/components/shared/RoleBadge";
|
|
import { useRouter, usePathname } from "next/navigation";
|
|
|
|
const navLinks: { href: string; label: string; roles?: string[] }[] = [
|
|
{ href: "/dashboard/user", label: "My Events", roles: ["user", "staff", "supervisor", "admin"] },
|
|
{ href: "/dashboard/user/profile", label: "Profile & Security", roles: ["user", "staff", "supervisor", "admin"] },
|
|
{ href: "/dashboard/staff", label: "Staff", roles: ["staff"] },
|
|
{ href: "/dashboard/supervisor", label: "Supervisor", roles: ["supervisor"] },
|
|
{ href: "/dashboard/admin", label: "Admin", roles: ["admin"] },
|
|
{ href: "/dashboard/admin/settings", label: "Site Settings", roles: ["admin"] }
|
|
];
|
|
|
|
export function Sidebar() {
|
|
const { user } = useAuth();
|
|
const role = user?.role || "user";
|
|
|
|
return (
|
|
<aside className="w-60 shrink-0 border-r bg-white p-4">
|
|
<div className="mb-4">
|
|
<div className="font-semibold">Dashboard</div>
|
|
{user && (
|
|
<div className="mt-1 text-xs text-gray-600 flex items-center space-x-2">
|
|
<span>{user.name}</span>
|
|
<RoleBadge role={role as any} />
|
|
</div>
|
|
)}
|
|
</div>
|
|
<nav className="space-y-1">
|
|
{navLinks
|
|
.filter((l) => !l.roles || l.roles.includes(role))
|
|
.map((l) => (
|
|
<Link key={l.href} className="block rounded px-3 py-2 text-sm hover:bg-gray-50" href={l.href}>
|
|
{l.label}
|
|
</Link>
|
|
))}
|
|
</nav>
|
|
</aside>
|
|
);
|
|
}
|
|
|
|
export function MobileSidebar() {
|
|
const { user } = useAuth();
|
|
const router = useRouter();
|
|
const role = user?.role || "user";
|
|
const pathname = usePathname();
|
|
|
|
const options = navLinks.filter((l) => !l.roles || l.roles.includes(role));
|
|
|
|
// Determine selected value based on current path or default by role
|
|
// Use longest matching href to handle nested paths (e.g. /dashboard/user/profile over /dashboard/user)
|
|
const selectedFromPath = options
|
|
.filter((o) => pathname?.startsWith(o.href))
|
|
.sort((a, b) => b.href.length - a.href.length)[0]?.href;
|
|
const roleDefault: Record<string, string> = {
|
|
admin: "/dashboard/admin",
|
|
supervisor: "/dashboard/supervisor",
|
|
staff: "/dashboard/staff",
|
|
user: "/dashboard/user",
|
|
};
|
|
const selectedValue = selectedFromPath || roleDefault[role] || options[0]?.href || "";
|
|
|
|
const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
const value = e.target.value;
|
|
if (value && value !== selectedValue) router.push(value);
|
|
};
|
|
|
|
return (
|
|
<div className="md:hidden border-b bg-white px-4 py-3 shadow-sm">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<div className="font-semibold">Dashboard</div>
|
|
{user && (
|
|
<div className="mt-0.5 text-xs text-gray-600 flex items-center space-x-2">
|
|
<span>{user.name}</span>
|
|
<RoleBadge role={role as any} />
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<label className="sr-only" htmlFor="mobile-dashboard-nav">Navigate dashboard</label>
|
|
<select
|
|
id="mobile-dashboard-nav"
|
|
className="mt-3 w-full rounded-md border-gray-300 text-sm py-2 px-3 shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-white"
|
|
value={selectedValue}
|
|
onChange={handleChange}
|
|
>
|
|
{/* Keep placeholder hidden if a value is selected */}
|
|
{!selectedValue && (
|
|
<option value="" disabled>
|
|
Select section...
|
|
</option>
|
|
)}
|
|
{options.map((l) => (
|
|
<option key={l.href} value={l.href}>
|
|
{l.label}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
);
|
|
}
|