169 lines
6.8 KiB
JavaScript
169 lines
6.8 KiB
JavaScript
import React, { useState } from 'react'
|
|
import { Link, usePage } from '@inertiajs/react'
|
|
|
|
const buildAdminNavGroups = (isAdmin) => [
|
|
{
|
|
label: 'Overview',
|
|
items: [
|
|
{ label: 'Dashboard', href: '/moderation', icon: 'fa-solid fa-gauge-high', exact: true },
|
|
{ label: 'Daily Activity', href: '/moderation/activity', icon: 'fa-solid fa-calendar-day' },
|
|
],
|
|
},
|
|
{
|
|
label: 'People',
|
|
items: [
|
|
{ label: 'All Users', href: '/moderation/users', icon: 'fa-solid fa-users' },
|
|
{ label: 'Staff', href: '/moderation/users?role=admin', icon: 'fa-solid fa-shield-halved' },
|
|
{ label: 'Moderators', href: '/moderation/users?role=moderator', icon: 'fa-solid fa-user-shield' },
|
|
],
|
|
},
|
|
{
|
|
label: 'Content',
|
|
items: [
|
|
{ label: 'Stories', href: '/moderation/stories', icon: 'fa-solid fa-feather-pointed' },
|
|
{ label: 'Artworks', href: '/moderation/artworks', icon: 'fa-solid fa-images' },
|
|
{ label: 'Academy Dashboard', href: '/moderation/academy/dashboard', icon: 'fa-solid fa-graduation-cap' },
|
|
{ label: 'Academy Lessons', href: '/moderation/academy/lessons', icon: 'fa-solid fa-book-open' },
|
|
{ label: 'Academy Prompts', href: '/moderation/academy/prompts', icon: 'fa-solid fa-wand-magic-sparkles' },
|
|
{ label: 'Academy Challenges', href: '/moderation/academy/challenges', icon: 'fa-solid fa-trophy' },
|
|
{ label: 'Featured Artworks', href: '/moderation/artworks/featured', icon: 'fa-solid fa-star' },
|
|
{ label: 'Homepage Announcements', href: '/moderation/homepage/announcements', icon: 'fa-solid fa-bullhorn' },
|
|
{ label: 'Upload Queue', href: '/moderation/uploads', icon: 'fa-solid fa-cloud-arrow-up' },
|
|
{ label: 'Username Queue', href: '/moderation/usernames/moderation', icon: 'fa-solid fa-id-badge' },
|
|
{ label: 'AI Biography', href: '/moderation/ai-biography', icon: 'fa-solid fa-wand-magic-sparkles' },
|
|
],
|
|
},
|
|
{
|
|
label: 'System',
|
|
items: [
|
|
...(isAdmin ? [{ label: 'Auth Audit', href: '/moderation/auth-audit', icon: 'fa-solid fa-user-shield' }] : []),
|
|
{ label: 'Settings', href: '/moderation/settings', icon: 'fa-solid fa-gear' },
|
|
],
|
|
},
|
|
]
|
|
|
|
function NavLink({ item, active }) {
|
|
const cls = `flex items-center gap-3 px-4 py-2.5 rounded-xl text-sm font-medium transition-all duration-200 ${
|
|
active
|
|
? 'bg-rose-500/20 text-rose-300 shadow-sm shadow-rose-500/10'
|
|
: 'text-slate-400 hover:text-white hover:bg-white/5'
|
|
}`
|
|
|
|
return (
|
|
<Link href={item.href} className={cls}>
|
|
<i className={`${item.icon} w-5 text-center text-base`} />
|
|
<span>{item.label}</span>
|
|
</Link>
|
|
)
|
|
}
|
|
|
|
function Sidebar({ pathname, isAdmin }) {
|
|
const adminNavGroups = buildAdminNavGroups(isAdmin)
|
|
|
|
const isActive = (item) => {
|
|
if (item.exact) return pathname === item.href
|
|
return pathname.startsWith(item.href.split('?')[0])
|
|
}
|
|
|
|
return (
|
|
<aside className="flex h-full w-64 flex-col overflow-y-auto border-r border-white/[0.07] bg-[rgba(10,14,22,0.98)] px-3 py-6">
|
|
{/* Brand */}
|
|
<div className="mb-8 px-3">
|
|
<Link href="/moderation" className="flex items-center gap-2.5">
|
|
<span className="flex h-8 w-8 items-center justify-center rounded-lg bg-rose-500/20">
|
|
<i className="fa-solid fa-shield-halved text-sm text-rose-400" />
|
|
</span>
|
|
<div>
|
|
<p className="text-[11px] font-semibold uppercase tracking-[0.22em] text-rose-400/80">Skinbase</p>
|
|
<p className="text-sm font-bold text-white">Admin Panel</p>
|
|
</div>
|
|
</Link>
|
|
</div>
|
|
|
|
{/* Nav groups */}
|
|
<nav className="flex-1 space-y-6">
|
|
{adminNavGroups.map((group) => (
|
|
<div key={group.label}>
|
|
<p className="mb-1.5 px-4 text-[10px] font-bold uppercase tracking-[0.2em] text-slate-600">
|
|
{group.label}
|
|
</p>
|
|
<div className="space-y-0.5">
|
|
{group.items.map((item) => (
|
|
<NavLink key={item.href} item={item} active={isActive(item)} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</nav>
|
|
|
|
{/* Footer links */}
|
|
<div className="mt-6 space-y-0.5 border-t border-white/[0.06] pt-4">
|
|
<Link
|
|
href="/studio"
|
|
className="flex items-center gap-3 rounded-xl px-4 py-2.5 text-sm text-slate-500 transition hover:bg-white/5 hover:text-slate-300"
|
|
>
|
|
<i className="fa-solid fa-arrow-left w-5 text-center text-base" />
|
|
<span>Back to Studio</span>
|
|
</Link>
|
|
</div>
|
|
</aside>
|
|
)
|
|
}
|
|
|
|
export default function AdminLayout({ children, title, subtitle }) {
|
|
const { url, props } = usePage()
|
|
const [mobileOpen, setMobileOpen] = useState(false)
|
|
const pathname = url.split('?')[0]
|
|
const currentUserIsAdmin = Boolean(props.auth?.user?.is_admin)
|
|
|
|
return (
|
|
<div className="flex min-h-screen bg-[radial-gradient(ellipse_at_top,_rgba(239,68,68,0.08),_transparent_40%),linear-gradient(180deg,#060a12_0%,#020409_100%)]">
|
|
|
|
{/* Desktop sidebar */}
|
|
<div className="hidden lg:flex lg:w-64 lg:flex-shrink-0">
|
|
<div className="fixed inset-y-0 left-0 w-64">
|
|
<Sidebar pathname={pathname} isAdmin={currentUserIsAdmin} />
|
|
</div>
|
|
</div>
|
|
|
|
{/* Mobile header */}
|
|
<div className="fixed inset-x-0 top-0 z-40 flex items-center justify-between border-b border-white/10 bg-[rgba(10,14,22,0.97)] px-4 py-3 backdrop-blur-xl lg:hidden">
|
|
<Link href="/moderation" className="flex items-center gap-2">
|
|
<i className="fa-solid fa-shield-halved text-rose-400" />
|
|
<span className="text-sm font-bold text-white">Admin Panel</span>
|
|
</Link>
|
|
<button
|
|
onClick={() => setMobileOpen(!mobileOpen)}
|
|
className="rounded-full border border-white/10 p-2 text-slate-400 hover:text-white"
|
|
aria-label="Toggle navigation"
|
|
>
|
|
<i className={mobileOpen ? 'fa-solid fa-xmark' : 'fa-solid fa-bars'} />
|
|
</button>
|
|
</div>
|
|
|
|
{/* Mobile drawer */}
|
|
{mobileOpen && (
|
|
<div className="fixed inset-0 z-30 lg:hidden">
|
|
<div className="absolute inset-0 bg-black/60 backdrop-blur-sm" onClick={() => setMobileOpen(false)} />
|
|
<div className="absolute left-0 top-0 h-full w-72 pt-14">
|
|
<Sidebar pathname={pathname} isAdmin={currentUserIsAdmin} />
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Main content */}
|
|
<div className="flex flex-1 flex-col lg:pl-8">
|
|
<main className="flex-1 px-6 py-8 pt-20 lg:pt-8">
|
|
{(title || subtitle) && (
|
|
<div className="mb-8">
|
|
{title && <h1 className="text-2xl font-bold text-white">{title}</h1>}
|
|
{subtitle && <p className="mt-1 text-sm text-slate-400">{subtitle}</p>}
|
|
</div>
|
|
)}
|
|
{children}
|
|
</main>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|