147 lines
5.2 KiB
TypeScript
147 lines
5.2 KiB
TypeScript
'use client';
|
|
|
|
import Link from 'next/link';
|
|
import { usePathname, useRouter } from 'next/navigation';
|
|
import { useEffect, useState } from 'react';
|
|
import { useSuperAdmin } from './super-admin-context';
|
|
import { useAuth } from './auth-context';
|
|
|
|
const NAV_LINKS = [
|
|
{ href: '/search', label: 'Search' },
|
|
{ href: '/groups', label: 'Groups & Routes' },
|
|
{ href: '/messages/pending', label: 'Pending messages' },
|
|
{ href: '/settings/rules', label: 'Rules' },
|
|
{ href: '/settings/bot', label: 'Bot' },
|
|
];
|
|
|
|
const SUPER_ADMIN_LINKS = [
|
|
{ href: '/admin', label: 'Dashboard' },
|
|
{ href: '/admin/tenants', label: 'Tenants' },
|
|
{ href: '/admin/bots', label: 'Bot Pool' },
|
|
];
|
|
|
|
const PUBLIC_PATHS = ['/login', '/signup', '/onboard'];
|
|
const ADMIN_PATHS = ['/admin'];
|
|
const MEMBER_PATHS = ['/my'];
|
|
|
|
export function Sidebar() {
|
|
const { admin, loading, logout } = useAuth();
|
|
const { admin: superAdmin, logout: superLogout } = useSuperAdmin();
|
|
const pathname = usePathname();
|
|
const router = useRouter();
|
|
const [pendingCount, setPendingCount] = useState<number | null>(null);
|
|
|
|
useEffect(() => {
|
|
fetch('/api/messages/pending/count')
|
|
.then((r) => r.ok ? r.json() : null)
|
|
.then((data) => setPendingCount(data?.count ?? null))
|
|
.catch(() => setPendingCount(null));
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (loading) return;
|
|
if (PUBLIC_PATHS.some((p) => pathname.startsWith(p))) return;
|
|
if (ADMIN_PATHS.some((p) => pathname.startsWith(p))) return;
|
|
if (MEMBER_PATHS.some((p) => pathname.startsWith(p))) return;
|
|
if (!admin) {
|
|
router.replace(`/login?next=${encodeURIComponent(pathname)}`);
|
|
}
|
|
}, [loading, admin, pathname, router]);
|
|
|
|
if (PUBLIC_PATHS.some((p) => pathname.startsWith(p))) {
|
|
return (
|
|
<nav className="w-52 shrink-0 bg-white border-r border-gray-200 p-4">
|
|
<span className="font-bold text-base">TOWER</span>
|
|
</nav>
|
|
);
|
|
}
|
|
|
|
if (ADMIN_PATHS.some((p) => pathname.startsWith(p))) {
|
|
return (
|
|
<nav className="w-52 shrink-0 bg-white border-r border-gray-200 p-4 flex flex-col">
|
|
<span className="font-bold text-base mb-4">TOWER Admin</span>
|
|
<div className="flex flex-col gap-1 flex-1">
|
|
{SUPER_ADMIN_LINKS.map((link) => (
|
|
<Link key={link.href} href={link.href} className="rounded px-3 py-2 text-sm hover:bg-gray-100">
|
|
{link.label}
|
|
</Link>
|
|
))}
|
|
</div>
|
|
<div className="border-t border-gray-200 pt-3 mt-3 flex flex-col gap-2">
|
|
<div className="px-3 text-xs text-gray-500">
|
|
<div className="font-medium text-gray-700 truncate">{superAdmin?.email}</div>
|
|
<div className="uppercase tracking-wide">Super Admin</div>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
onClick={() => void superLogout()}
|
|
className="text-left rounded px-3 py-2 text-sm hover:bg-gray-100"
|
|
>
|
|
Sign out
|
|
</button>
|
|
</div>
|
|
</nav>
|
|
);
|
|
}
|
|
|
|
if (MEMBER_PATHS.some((p) => pathname.startsWith(p))) {
|
|
return (
|
|
<nav className="w-52 shrink-0 bg-white border-r border-gray-200 p-4 flex flex-col">
|
|
<span className="font-bold text-base mb-4">TOWER</span>
|
|
<div className="flex flex-col gap-1 flex-1">
|
|
<Link href="/my" className="rounded px-3 py-2 text-sm hover:bg-gray-100">
|
|
Profile
|
|
</Link>
|
|
<Link href="/my/groups" className="rounded px-3 py-2 text-sm hover:bg-gray-100">
|
|
Groups
|
|
</Link>
|
|
<Link href="/my/settings" className="rounded px-3 py-2 text-sm hover:bg-gray-100">
|
|
Settings
|
|
</Link>
|
|
</div>
|
|
<div className="border-t border-gray-200 pt-3 mt-3 text-xs text-gray-500 px-3">
|
|
Member portal
|
|
</div>
|
|
</nav>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<nav className="w-52 shrink-0 bg-white border-r border-gray-200 p-4 flex flex-col">
|
|
<span className="font-bold text-base mb-4">TOWER</span>
|
|
<div className="flex flex-col gap-1 flex-1">
|
|
{NAV_LINKS.map((link) => (
|
|
<Link
|
|
key={link.href}
|
|
href={link.href}
|
|
className="rounded px-3 py-2 text-sm hover:bg-gray-100 flex items-center justify-between"
|
|
>
|
|
<span>{link.label}</span>
|
|
{link.href === '/messages/pending' && pendingCount !== null && pendingCount > 0 && (
|
|
<span className="bg-blue-600 text-white text-[11px] font-bold rounded-full w-5 h-5 flex items-center justify-center">
|
|
{pendingCount > 99 ? '99+' : pendingCount}
|
|
</span>
|
|
)}
|
|
</Link>
|
|
))}
|
|
</div>
|
|
{admin && (
|
|
<div className="border-t border-gray-200 pt-3 mt-3 flex flex-col gap-2">
|
|
<div className="px-3 text-xs text-gray-500">
|
|
<div className="font-medium text-gray-700 truncate">{admin.name ?? admin.email}</div>
|
|
<div className="truncate">{admin.tenantName}</div>
|
|
<div className="uppercase tracking-wide">{admin.role}</div>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
onClick={() => void logout()}
|
|
className="text-left rounded px-3 py-2 text-sm hover:bg-gray-100"
|
|
>
|
|
Sign out
|
|
</button>
|
|
</div>
|
|
)}
|
|
</nav>
|
|
);
|
|
}
|