Phase 1 (Foundation): - Project restructure (presenton-main → backend/ + frontend/) - Database schema (8 new models, Alembic config, seed script) - Auth (Azure AD SSO + dev bypass, JWT sessions, AuthMiddleware) - RBAC (access_service, rbac_middleware, admin routers) - Audit logging (fire-and-forget, AuditMiddleware, admin router) - i18n (react-i18next with 5 namespace files) Phase 2 (Admin Panel & Client Management): - Admin panel shell (sidebar layout, role guard, 12 pages) - Redux admin slice with 18 async thunks - User management (role changes, deactivation) - Client management (CRUD, brand config, team management) - Brand config editor (colors, fonts, logos, voice rules) - Master deck upload & parser (PPTX → HTML → React pipeline) - Audit log viewer with filters and CSV/JSON export Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
80 lines
2.9 KiB
TypeScript
80 lines
2.9 KiB
TypeScript
'use client';
|
|
|
|
import Link from 'next/link';
|
|
import { usePathname } from 'next/navigation';
|
|
import { useSelector } from 'react-redux';
|
|
import { RootState } from '@/store/store';
|
|
import { Separator } from '@/components/ui/separator';
|
|
import {
|
|
Users,
|
|
Building2,
|
|
Users2,
|
|
Layers,
|
|
Palette,
|
|
FileText,
|
|
BarChart3,
|
|
Settings,
|
|
ArrowLeft,
|
|
} from 'lucide-react';
|
|
|
|
interface NavItem {
|
|
label: string;
|
|
href: string;
|
|
icon: React.ReactNode;
|
|
roles: string[];
|
|
}
|
|
|
|
const NAV_ITEMS: NavItem[] = [
|
|
{ label: 'Users', href: '/admin/users', icon: <Users className="w-4 h-4" />, roles: ['super_admin'] },
|
|
{ label: 'Clients', href: '/admin/clients', icon: <Building2 className="w-4 h-4" />, roles: ['super_admin', 'client_admin'] },
|
|
{ label: 'Teams', href: '/admin/clients', icon: <Users2 className="w-4 h-4" />, roles: ['super_admin', 'client_admin'] },
|
|
{ label: 'Audit Log', href: '/admin/audit', icon: <FileText className="w-4 h-4" />, roles: ['super_admin', 'client_admin'] },
|
|
{ label: 'Analytics', href: '/admin/analytics', icon: <BarChart3 className="w-4 h-4" />, roles: ['super_admin', 'client_admin'] },
|
|
{ label: 'Settings', href: '/admin/settings', icon: <Settings className="w-4 h-4" />, roles: ['super_admin'] },
|
|
];
|
|
|
|
export default function AdminSidebar() {
|
|
const pathname = usePathname();
|
|
const user = useSelector((state: RootState) => state.auth.user);
|
|
const role = user?.role || 'user';
|
|
|
|
const visibleItems = NAV_ITEMS.filter((item) => item.roles.includes(role));
|
|
|
|
return (
|
|
<aside className="w-60 min-h-screen bg-white border-r border-gray-200 flex flex-col">
|
|
<div className="p-4">
|
|
<Link href="/dashboard" className="flex items-center gap-2 text-sm text-gray-500 hover:text-gray-700 mb-4">
|
|
<ArrowLeft className="w-4 h-4" />
|
|
Back to Dashboard
|
|
</Link>
|
|
<h2 className="text-lg font-semibold text-gray-900 font-inter">Admin Panel</h2>
|
|
<p className="text-xs text-gray-500 mt-1">Manage your organization</p>
|
|
</div>
|
|
<Separator />
|
|
<nav className="flex-1 p-2">
|
|
{visibleItems.map((item) => {
|
|
const isActive = pathname === item.href || pathname.startsWith(item.href + '/');
|
|
return (
|
|
<Link
|
|
key={item.href + item.label}
|
|
href={item.href}
|
|
className={`flex items-center gap-3 px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
|
isActive
|
|
? 'bg-[#5146E5]/10 text-[#5146E5]'
|
|
: 'text-gray-700 hover:bg-gray-100'
|
|
}`}
|
|
>
|
|
{item.icon}
|
|
{item.label}
|
|
</Link>
|
|
);
|
|
})}
|
|
</nav>
|
|
<div className="p-4 border-t border-gray-200">
|
|
<div className="text-xs text-gray-400">
|
|
Signed in as <span className="font-medium text-gray-600">{user?.email}</span>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
);
|
|
}
|