ai-helpdesk-api / Frontend /src /master-admin /layout /MasterAdminLayout.jsx
ritesh19180's picture
Upload folder using huggingface_hub
d96d2a2 verified
Raw
History Blame Contribute Delete
8.14 kB
import React from "react";
import { Link, useNavigate, useLocation, Outlet } from "react-router-dom";
import { supabase } from "../../lib/supabaseClient";
import useAuthStore from "../../store/authStore";
import {
ShieldCheck,
LayoutDashboard,
Users,
Building2,
Bell,
LogOut,
ExternalLink,
Search,
UserCircle,
Bug,
Settings,
Activity
} from "lucide-react";
/**
* MasterAdminLayout — Professional sidebar-based layout for platform oversight.
*/
function MasterAdminLayout() {
const { profile, logout } = useAuthStore();
const navigate = useNavigate();
const location = useLocation();
const handleLogout = async () => {
await logout();
navigate("/master-admin-login");
};
const [pendingCount, setPendingCount] = React.useState(0);
React.useEffect(() => {
const fetchCount = async () => {
const { count } = await supabase
.from('admin_requests')
.select('*', { count: 'exact', head: true })
.eq('status', 'pending');
setPendingCount(count || 0);
};
fetchCount();
const sub = supabase.channel('pending_badge')
.on('postgres_changes', { event: '*', schema: 'public', table: 'admin_requests' }, () => fetchCount())
.subscribe();
return () => supabase.removeChannel(sub);
}, []);
const navItems = [
{ path: "/master-admin/dashboard", icon: <LayoutDashboard />, label: "Overview" },
{ path: "/master-admin/admin-requests", icon: <Bell />, label: "Pending Requests", count: pendingCount },
{ path: "/master-admin/companies", icon: <Building2 />, label: "All Companies" },
{ path: "/master-admin/all-admins", icon: <Users />, label: "All Admins" },
{ path: "/master-admin/bug-reports", icon: <Bug />, label: "Bug Radar" },
];
return (
<div className="min-h-screen bg-[#050508] flex font-sans overflow-hidden text-slate-300">
{/* Ambient Background Glows */}
<div className="fixed inset-0 pointer-events-none z-0">
<div className="absolute top-0 right-0 w-[800px] h-[800px] bg-indigo-600/5 rounded-full blur-[150px] mix-blend-screen" />
<div className="absolute bottom-0 left-0 w-[600px] h-[600px] bg-emerald-600/5 rounded-full blur-[120px] mix-blend-screen" />
<div className="absolute top-0 left-0 w-full h-full bg-[url('https://grainy-gradients.vercel.app/noise.svg')] opacity-[0.03]"></div>
</div>
{/* Sidebar */}
<aside className="w-64 border-r border-white/5 bg-white/[0.02] backdrop-blur-xl z-20 flex flex-col shrink-0">
<div className="p-6 border-b border-white/5">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-indigo-500/20 border border-indigo-500/30 flex items-center justify-center shadow-[0_0_15px_rgba(99,102,241,0.2)]">
<ShieldCheck className="w-5 h-5 text-indigo-400" />
</div>
<div>
<h1 className="text-white font-bold tracking-tight">HelpDesk.ai</h1>
<p className="text-[10px] text-indigo-400 uppercase tracking-widest font-semibold">Master Admin</p>
</div>
</div>
</div>
<nav className="flex-1 p-4 space-y-1 overflow-y-auto">
{navItems.map((item) => {
const isActive = location.pathname === item.path;
return (
<Link
key={item.path}
to={item.path}
className={`flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-medium transition-all group ${isActive
? "bg-indigo-500/10 text-indigo-300 border border-indigo-500/20"
: "text-slate-400 hover:text-white hover:bg-white/5 border border-transparent"
}`}
>
{React.cloneElement(item.icon, {
className: `w-5 h-5 transition-colors ${isActive ? "text-indigo-400" : "text-slate-500 group-hover:text-slate-300"}`
})}
{item.label}
{item.count > 0 && (
<span className="ml-auto px-2 py-0.5 rounded-full bg-emerald-500/10 text-emerald-400 text-[10px] font-black border border-emerald-500/20">
{item.count}
</span>
)}
{item.label === "Pending Requests" && item.count === 0 && (
<span className="ml-auto w-1.5 h-1.5 rounded-full bg-slate-700"></span>
)}
</Link>
);
})}
</nav>
<div className="p-4 border-t border-white/5">
<button
onClick={handleLogout}
className="w-full flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-medium text-slate-400 hover:text-red-400 hover:bg-red-500/5 transition-all group"
>
<LogOut className="w-5 h-5 text-slate-500 group-hover:text-red-400 transition-colors" />
Sign Out
</button>
</div>
</aside>
{/* Main Content Area */}
<main className="flex-1 flex flex-col min-w-0 relative z-10 h-screen overflow-hidden">
{/* Top Bar */}
<header className="h-16 border-b border-white/5 bg-white/[0.01] backdrop-blur-md flex items-center justify-between px-8 shrink-0">
<div className="flex items-center gap-4 flex-1 max-w-xl">
<div className="relative w-full group">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-500 group-focus-within:text-indigo-400 transition-colors" />
<input
type="text"
placeholder="Global search admins, companies, or IDs..."
className="w-full bg-white/5 border border-white/10 rounded-lg py-2 pl-10 pr-4 text-sm focus:outline-none focus:ring-1 focus:ring-indigo-500/50 transition-all"
onChange={(e) => console.log("Global search:", e.target.value)}
/>
</div>
</div>
<div className="flex items-center gap-6">
<div className="flex items-center gap-3 pl-6 border-l border-white/10">
<div className="text-right hidden sm:block">
<p className="text-sm font-semibold text-white truncate max-w-[150px]">{profile?.full_name}</p>
<p className="text-[10px] text-emerald-400 font-medium uppercase tracking-wider">Superuser Access</p>
</div>
<div className="w-10 h-10 rounded-full bg-indigo-500/20 border border-indigo-500/30 flex items-center justify-center overflow-hidden">
<UserCircle className="w-6 h-6 text-indigo-400" />
</div>
</div>
</div>
</header>
{/* Page Content */}
<div className="flex-1 overflow-y-auto p-8 custom-scrollbar">
<Outlet />
</div>
</main>
</div>
);
}
export default MasterAdminLayout;