P0 Critical: presentation isolation (client scoping), storage super_admin fix, template selection in worker, IMAGE_PROVIDERS list fix. P1 High: template layout management UI (delete/filter/bulk), slide-based parsing mode, LLM model listing & connection test, settings persistence to DB (Fernet encryption), logout button. P2 Polish: storage improvements (master deck files, per-client breakdown, bulk delete, hard purge, client selector), image generation error visibility (__image_error__ badge), hamster wheel loading animation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
94 lines
3.4 KiB
TypeScript
94 lines
3.4 KiB
TypeScript
'use client';
|
|
|
|
import Link from 'next/link';
|
|
import { usePathname, useRouter } from 'next/navigation';
|
|
import { useSelector, useDispatch } from 'react-redux';
|
|
import { RootState, AppDispatch } from '@/store/store';
|
|
import { logoutUser } from '@/store/slices/authSlice';
|
|
import { Separator } from '@/components/ui/separator';
|
|
import {
|
|
Users,
|
|
Building2,
|
|
HardDrive,
|
|
FileText,
|
|
BarChart3,
|
|
Settings,
|
|
ArrowLeft,
|
|
LogOut,
|
|
} 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: 'Storage', href: '/admin/storage', icon: <HardDrive 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 router = useRouter();
|
|
const dispatch = useDispatch<AppDispatch>();
|
|
const user = useSelector((state: RootState) => state.auth.user);
|
|
const role = user?.role || 'user';
|
|
|
|
const handleLogout = async () => {
|
|
await dispatch(logoutUser());
|
|
router.push('/login');
|
|
};
|
|
|
|
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 mb-2">
|
|
Signed in as <span className="font-medium text-gray-600">{user?.email}</span>
|
|
</div>
|
|
<button
|
|
onClick={handleLogout}
|
|
className="flex items-center gap-2 text-xs text-gray-500 hover:text-red-600 transition-colors"
|
|
>
|
|
<LogOut className="w-3.5 h-3.5" />
|
|
Sign out
|
|
</button>
|
|
</div>
|
|
</aside>
|
|
);
|
|
}
|