import { A, useLocation, useNavigate, useSearchParams } from "@solidjs/router"; import { For, Show, createEffect, createMemo, createSignal, onCleanup, onMount, type JSX, } from "solid-js"; import { Bell, Moon, Search, Settings, Sun, User } from "lucide-solid"; import AdminSidebar from "./AdminSidebar"; import { isExternalIdentity } from "~/lib/admin-auth"; import { clearAdminSession, hasAdminSession, setAdminSession } from "~/lib/admin-session"; import { normalizeAllowedModules } from "~/lib/admin/module-access"; import { sessionExpired, clearSessionExpired, installSessionExpiryWatcher } from "~/lib/session-expired"; import { installSessionRefreshLoop } from "~/lib/session-refresh"; type Tab = { href: string; label: string; exact?: boolean }; type SearchResult = { id: string; title: string; subtitle: string; href: string }; type SearchGroup = { label: string; viewAllHref: string; results: SearchResult[] }; const PAGE_TITLES: Array<{ prefix: string; label: string; exact?: boolean }> = [ { prefix: "/admin", label: "Dashboard", exact: true }, { prefix: "/admin/department", label: "Department Management" }, { prefix: "/admin/designation", label: "Designation Management" }, { prefix: "/admin/roles", label: "Internal Role Management" }, { prefix: "/admin/employees", label: "Employee Management" }, { prefix: "/admin/external-roles", label: "External Role Management" }, { prefix: "/admin/internal-dashboard-management", label: "Internal Dashboard Management" }, { prefix: "/admin/external-dashboard-management", label: "External Dashboard Management" }, { prefix: "/admin/role-ui-configs", label: "External Dashboard Management" }, { prefix: "/admin/verification", label: "Verification Management" }, { prefix: "/admin/verification-status", label: "Verification Management" }, { prefix: "/admin/approval", label: "Approval Management" }, { prefix: "/admin/approvals", label: "Approval Management" }, { prefix: "/admin/approval-management", label: "Approval Management" }, { prefix: "/admin/users", label: "Users Management" }, { prefix: "/admin/company", label: "Company Management" }, { prefix: "/admin/candidate", label: "Candidate Management" }, { prefix: "/admin/customer", label: "Customer Management" }, { prefix: "/admin/photographer", label: "Photographer Management" }, { prefix: "/admin/makeup-artist", label: "Makeup Artist Management" }, { prefix: "/admin/tutors", label: "Tutors Management" }, { prefix: "/admin/developers", label: "Developers Management" }, { prefix: "/admin/video-editors", label: "Video Editor Management" }, { prefix: "/admin/fitness-trainers", label: "Fitness Trainer Management" }, { prefix: "/admin/catering-services", label: "Catering Services Management" }, { prefix: "/admin/ugc-content-creators", label: "UGC Content Creator Management" }, { prefix: "/admin/graphic-designers", label: "Graphic Designer Management" }, { prefix: "/admin/social-media-managers", label: "Social Media Manager Management" }, { prefix: "/admin/jobs", label: "Jobs Management" }, { prefix: "/admin/leads", label: "Leads Management" }, { prefix: "/admin/applications", label: "Applications Management" }, { prefix: "/admin/responses", label: "Responses Management" }, { prefix: "/admin/pricing", label: "Pricing Management" }, { prefix: "/admin/credit", label: "Credit Management" }, { prefix: "/admin/coupon", label: "Coupon Management" }, { prefix: "/admin/discount", label: "Discount Management" }, { prefix: "/admin/tax", label: "Tax Management" }, { prefix: "/admin/order", label: "Order Management" }, { prefix: "/admin/invoice", label: "Invoice Management" }, { prefix: "/admin/payment-gateway", label: "Payment Gateway Management" }, { prefix: "/admin/smtp", label: "SMTP Management" }, { prefix: "/admin/kb", label: "Knowledge Base Management" }, { prefix: "/admin/notifications", label: "Notifications" }, { prefix: "/admin/review", label: "Review Management" }, { prefix: "/admin/support", label: "Support Management" }, { prefix: "/admin/report", label: "Report Management" }, { prefix: "/admin/ledger", label: "Ledger Management" }, ]; const TAB_SETS: Array<{ prefixes: string[]; tabs: Tab[] }> = []; const ROUTE_MODULE_KEYS: Array<{ prefix: string; keys: string[] }> = [ { prefix: "/admin", keys: ["ADMIN_DASHBOARD", "DASHBOARD"] }, { prefix: "/admin/department", keys: ["DEPARTMENT_MANAGEMENT", "DEPARTMENTS"] }, { prefix: "/admin/department-management", keys: ["DEPARTMENT_MANAGEMENT", "DEPARTMENTS"] }, { prefix: "/admin/designation", keys: ["DESIGNATION_MANAGEMENT", "DESIGNATIONS"] }, { prefix: "/admin/designation-management", keys: ["DESIGNATION_MANAGEMENT", "DESIGNATIONS"] }, { prefix: "/admin/roles", keys: ["INTERNAL_ROLE_MANAGEMENT", "ROLES"] }, { prefix: "/admin/employees", keys: ["EMPLOYEE_MANAGEMENT", "EMPLOYEES"] }, { prefix: "/admin/external-roles", keys: ["EXTERNAL_ROLE_MANAGEMENT", "EXTERNAL_ROLES"] }, { prefix: "/admin/internal-dashboard-management", keys: ["INTERNAL_DASHBOARD_MANAGEMENT", "INTERNAL_DASHBOARDS", "INTERNAL_DASHBOARD_CONFIG"], }, { prefix: "/admin/external-dashboard-management", keys: [ "DASHBOARD_CONFIG_MANAGEMENT", "EXTERNAL_DASHBOARD_MANAGEMENT", "EXTERNAL_DASHBOARDS", "EXTERNAL_DASHBOARD_CONFIG", "RUNTIME_ROLES", ], }, { prefix: "/admin/role-ui-configs", keys: [ "DASHBOARD_CONFIG_MANAGEMENT", "EXTERNAL_DASHBOARD_MANAGEMENT", "EXTERNAL_DASHBOARDS", "EXTERNAL_DASHBOARD_CONFIG", "RUNTIME_ROLES", ], }, { prefix: "/admin/verification", keys: ["VERIFICATION_MANAGEMENT", "VERIFICATIONS"] }, { prefix: "/admin/verification-status", keys: ["VERIFICATION_MANAGEMENT", "VERIFICATIONS"] }, { prefix: "/admin/approval", keys: ["APPROVAL_MANAGEMENT", "APPROVALS"] }, { prefix: "/admin/approvals", keys: ["APPROVAL_MANAGEMENT", "APPROVALS"] }, { prefix: "/admin/approval-management", keys: ["APPROVAL_MANAGEMENT", "APPROVALS"] }, { prefix: "/admin/users", keys: ["USER_MANAGEMENT", "USERS"] }, { prefix: "/admin/company", keys: ["COMPANY_MANAGEMENT", "COMPANIES"] }, { prefix: "/admin/candidate", keys: ["CANDIDATE_MANAGEMENT", "CANDIDATES"] }, { prefix: "/admin/customer", keys: ["CUSTOMER_MANAGEMENT", "CUSTOMERS"] }, { prefix: "/admin/photographer", keys: ["PHOTOGRAPHER_MANAGEMENT", "PHOTOGRAPHERS"] }, { prefix: "/admin/makeup-artist", keys: ["MAKEUP_ARTIST_MANAGEMENT", "MAKEUP_ARTISTS"] }, { prefix: "/admin/tutors", keys: ["TUTOR_MANAGEMENT", "TUTORS"] }, { prefix: "/admin/developers", keys: ["DEVELOPER_MANAGEMENT", "DEVELOPERS"] }, { prefix: "/admin/video-editors", keys: ["VIDEO_EDITOR_MANAGEMENT", "VIDEO_EDITORS"] }, { prefix: "/admin/fitness-trainers", keys: ["FITNESS_TRAINER_MANAGEMENT", "FITNESS_TRAINERS"] }, { prefix: "/admin/catering-services", keys: ["CATERING_SERVICES_MANAGEMENT", "CATERING_SERVICES"], }, { prefix: "/admin/ugc-content-creator", keys: ["UGC_CONTENT_CREATOR_MANAGEMENT", "UGC_CONTENT_CREATOR"], }, { prefix: "/admin/graphic-designers", keys: ["GRAPHIC_DESIGNER_MANAGEMENT", "GRAPHIC_DESIGNERS"], }, { prefix: "/admin/social-media-managers", keys: ["SOCIAL_MEDIA_MANAGEMENT", "SOCIAL_MEDIA_MANAGER_MANAGEMENT", "SOCIAL_MEDIA_MANAGERS"], }, { prefix: "/admin/jobs", keys: ["JOBS_MANAGEMENT", "JOBS"] }, { prefix: "/admin/leads", keys: ["LEADS_MANAGEMENT", "LEADS", "REQUIREMENTS_MANAGEMENT", "REQUIREMENTS"], }, { prefix: "/admin/applications", keys: ["APPLICATIONS_MANAGEMENT", "APPLICATIONS"] }, { prefix: "/admin/responses", keys: ["RESPONSES_MANAGEMENT", "RESPONSES"] }, { prefix: "/admin/pricing", keys: ["PRICING_MANAGEMENT", "PRICING"] }, { prefix: "/admin/credit", keys: ["CREDIT_MANAGEMENT", "CREDITS"] }, { prefix: "/admin/coupon", keys: ["COUPON_MANAGEMENT", "COUPONS"] }, { prefix: "/admin/discount", keys: ["DISCOUNT_MANAGEMENT", "DISCOUNTS"] }, { prefix: "/admin/tax", keys: ["TAX_MANAGEMENT", "TAXES"] }, { prefix: "/admin/order", keys: ["ORDER_MANAGEMENT", "ORDERS"] }, { prefix: "/admin/invoice", keys: ["INVOICE_MANAGEMENT", "INVOICES"] }, { prefix: "/admin/payment-gateway", keys: ["PAYMENT_GATEWAY_MANAGEMENT", "PAYMENT_GATEWAY"] }, { prefix: "/admin/smtp", keys: ["SMTP_MANAGEMENT", "SMTP"] }, { prefix: "/admin/kb", keys: ["KNOWLEDGE_BASE_MANAGEMENT", "KNOWLEDGE_BASE", "KB"] }, { prefix: "/admin/notifications", keys: ["NOTIFICATIONS_MANAGEMENT", "NOTIFICATIONS"] }, { prefix: "/admin/review", keys: ["REVIEW_MANAGEMENT", "REVIEWS"] }, { prefix: "/admin/support", keys: ["SUPPORT_MANAGEMENT", "SUPPORT"] }, { prefix: "/admin/report", keys: ["REPORT_MANAGEMENT", "REPORTS"] }, { prefix: "/admin/ledger", keys: ["LEDGER", "LEDGER_MANAGEMENT"] }, ]; const SEARCH_MODULES = [ { label: "Users", viewAllHref: "/admin/users", api: "/api/admin/users", listKeys: ["users", "items"], titleKeys: ["full_name", "name"], subtitleKeys: ["email", "phone"], detailBase: "/admin/users", }, { label: "Companies", viewAllHref: "/admin/company", api: "/api/admin/companies", listKeys: ["companies", "items"], titleKeys: ["name", "companyName"], subtitleKeys: ["email", "phone"], detailBase: "/admin/company", }, { label: "Employees", viewAllHref: "/admin/employees", api: "/api/admin/employees", listKeys: ["employees", "items"], titleKeys: ["full_name", "name"], subtitleKeys: ["email", "department_name"], detailBase: "/admin/employees", }, { label: "Jobs", viewAllHref: "/admin/jobs", api: "/api/admin/jobs", listKeys: ["jobs", "items"], titleKeys: ["title", "name"], subtitleKeys: ["status", "company_name"], detailBase: "/admin/jobs", }, { label: "Leads", viewAllHref: "/admin/leads", api: "/api/admin/leads", listKeys: ["leads", "items"], titleKeys: ["name", "full_name"], subtitleKeys: ["email", "status"], detailBase: "/admin/leads", }, ]; function pickStr(obj: Record, keys: string[]): string { for (const k of keys) if (obj[k]) return String(obj[k]); return "—"; } function extractList(data: any, keys: string[]): any[] { if (Array.isArray(data)) return data; for (const k of keys) if (Array.isArray(data[k])) return data[k]; return []; } function GlobalSearch() { const [query, setQuery] = createSignal(""); const [open, setOpen] = createSignal(false); const [groups, setGroups] = createSignal([]); const [searching, setSearching] = createSignal(false); let wrapRef!: HTMLDivElement; let timer: ReturnType; const doSearch = async (q: string) => { const trimmed = q.trim(); if (trimmed.length < 2) { setGroups([]); setOpen(false); return; } setSearching(true); const settled = await Promise.allSettled( SEARCH_MODULES.map(async (mod) => { const res = await fetch(`${mod.api}?search=${encodeURIComponent(trimmed)}&limit=4`).catch( () => null ); if (!res?.ok) return null; const data = await res.json().catch(() => null); if (!data) return null; const items = extractList(data, mod.listKeys).slice(0, 4); if (!items.length) return null; return { label: mod.label, viewAllHref: mod.viewAllHref, results: items.map((item: any) => ({ id: item.id, title: pickStr(item, mod.titleKeys), subtitle: pickStr(item, mod.subtitleKeys), href: `${mod.detailBase}/${item.id}`, })), } satisfies SearchGroup; }) ); setGroups(settled.flatMap((r) => (r.status === "fulfilled" && r.value ? [r.value] : []))); setOpen(true); setSearching(false); }; const handleInput = (val: string) => { setQuery(val); clearTimeout(timer); if (val.trim().length < 2) { setGroups([]); setOpen(false); return; } timer = setTimeout(() => doSearch(val), 350); }; const close = () => { setOpen(false); setQuery(""); setGroups([]); }; const onOutside = (e: MouseEvent) => { if (!wrapRef.contains(e.target as Node)) setOpen(false); }; onMount(() => document.addEventListener("mousedown", onOutside)); onCleanup(() => document.removeEventListener("mousedown", onOutside)); return (
handleInput(e.currentTarget.value)} onFocus={() => groups().length > 0 && setOpen(true)} onKeyDown={(e) => e.key === "Escape" && close()} class="h-[68px] w-full rounded-[24px] border-2 border-transparent bg-[#f4f5f8] pl-[60px] pr-6 text-[16px] text-[#0D0D2A] placeholder:text-[rgba(13,13,42,0.4)] outline-none transition-all focus:border-[#e5e7eb] focus:bg-white" /> 0}> = 2 && groups().length === 0}>
No results for "{query()}"
); } function ShowTabs(props: { tabs: Tab[]; isTabActive: (tab: Tab) => boolean; setTabsTrackEl: (el: HTMLDivElement) => void; setTabRefs: ( fn: (prev: Record) => Record ) => void; tabIndicator: () => { left: number; width: number; ready: boolean }; }) { if (props.tabs.length === 0) return null; return (
{(tab) => ( props.setTabRefs((prev) => ({ ...prev, [tab.href]: el }))} aria-current={props.isTabActive(tab) ? "page" : undefined} class={`px-4 pb-3 pt-3 text-[14px] font-semibold transition-colors ${ props.isTabActive(tab) ? "text-[#FF5E13]" : "text-[rgba(13,13,42,0.6)] hover:text-[#0D0D2A]" }`} > {tab.label} )}
); } export default function AdminShell(props: { children: JSX.Element }) { const location = useLocation(); const navigate = useNavigate(); const [searchParams] = useSearchParams(); const [checkedSession, setCheckedSession] = createSignal(true); const [adminName, setAdminName] = createSignal("Admin User"); const [allowedModules, setAllowedModules] = createSignal(null); const [isSuperAdmin, setIsSuperAdmin] = createSignal(false); const [sidebarOpen, setSidebarOpen] = createSignal(false); const [sidebarCollapsed, setSidebarCollapsed] = createSignal(false); const [unreadCount, setUnreadCount] = createSignal(0); const [theme, setTheme] = createSignal<"light" | "dark">("light"); const [routeTransitioning, setRouteTransitioning] = createSignal(false); const [tabsTrackEl, setTabsTrackEl] = createSignal(); const [tabRefs, setTabRefs] = createSignal>({}); const [tabIndicator, setTabIndicator] = createSignal({ left: 0, width: 0, ready: false }); let contentScrollRef: HTMLDivElement | undefined; const logout = async () => { try { const accessToken = typeof sessionStorage !== "undefined" ? sessionStorage.getItem("nxtgauge_admin_access_token") || "" : ""; await fetch("/api/auth/logout", { method: "POST", headers: { Accept: "application/json", "x-portal-target": "admin", ...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}), }, credentials: "include", }).catch(() => null); } finally { if (typeof sessionStorage !== "undefined") { sessionStorage.removeItem("nxtgauge_admin_access_token"); sessionStorage.removeItem("nxtgauge_admin_preview"); } clearAdminSession(); navigate("/login", { replace: true }); } }; const tabs = createMemo(() => { const path = location.pathname; for (const set of TAB_SETS) { if (set.prefixes.some((p) => path === p || path.startsWith(`${p}/`))) return set.tabs; } return []; }); const isTabActive = (tab: Tab) => tab.exact ? location.pathname === tab.href : location.pathname === tab.href || location.pathname.startsWith(`${tab.href}/`); const refreshTabIndicator = () => { const activeTab = tabs().find((tab) => isTabActive(tab)); const track = tabsTrackEl(); if (!activeTab || !track) { setTabIndicator((p) => ({ ...p, ready: false })); return; } const el = tabRefs()[activeTab.href]; if (!el) return; setTabIndicator({ left: el.offsetLeft, width: el.offsetWidth, ready: true }); }; createEffect(() => { tabs(); location.pathname; requestAnimationFrame(refreshTabIndicator); }); createEffect(() => { location.pathname; if (contentScrollRef) { contentScrollRef.scrollTop = 0; } }); const goToLoginAfterExpiry = () => { clearSessionExpired(); if (typeof sessionStorage !== "undefined") { sessionStorage.removeItem("nxtgauge_admin_access_token"); sessionStorage.removeItem("nxtgauge_admin_preview"); } clearAdminSession(); navigate(`/login?from=${encodeURIComponent(location.pathname + location.search)}`, { replace: true, }); }; onMount(() => { installSessionExpiryWatcher(); installSessionRefreshLoop(); const savedTheme = ( typeof localStorage !== "undefined" ? localStorage.getItem("nxtgauge_admin_theme") : null ) as "light" | "dark" | null; const nextTheme = savedTheme === "dark" ? "dark" : "light"; setTheme(nextTheme); if (typeof document !== "undefined") { document.documentElement.setAttribute("data-theme", nextTheme); } window.addEventListener("resize", refreshTabIndicator); onCleanup(() => window.removeEventListener("resize", refreshTabIndicator)); // Fetch unread notification count and poll every 30 seconds const fetchUnreadCount = async () => { try { const accessToken = typeof sessionStorage !== "undefined" ? sessionStorage.getItem("nxtgauge_admin_access_token") || "" : ""; if (!accessToken) return; const res = await fetch("/api/me/notifications/unread-count", { method: "GET", headers: { Accept: "application/json", "x-portal-target": "admin", ...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}), }, credentials: "include", }); if (res.ok) { const data = await res.json(); setUnreadCount(data.unread_count || 0); } } catch (e) { console.error("Failed to fetch unread count:", e); } }; fetchUnreadCount(); const interval = setInterval(fetchUnreadCount, 30000); onCleanup(() => clearInterval(interval)); const verify = async () => { if (!hasAdminSession()) { navigate(`/login?from=${encodeURIComponent(location.pathname + location.search)}`, { replace: true, }); return; } try { const accessToken = typeof sessionStorage !== "undefined" ? sessionStorage.getItem("nxtgauge_admin_access_token") || "" : ""; const response = await fetch("/api/auth/session", { method: "GET", headers: { Accept: "application/json", "x-portal-target": "admin", ...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}), }, credentials: "include", }); const payload = await response.json().catch(() => ({})); if (!response.ok || isExternalIdentity(payload)) throw new Error("Unauthorized"); if (payload?.full_name) setAdminName(payload.full_name); const roleKey = String( payload?.active_role || payload?.role || payload?.user?.active_role || payload?.user?.active_role_key || payload?.user?.role || payload?.user?.role_key || "" ).toUpperCase(); setIsSuperAdmin(roleKey === "SUPER_ADMIN"); try { const res = await fetch("/api/runtime-config", { method: "GET", headers: { Accept: "application/json", "x-portal-target": "admin", ...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}), }, credentials: "include", }); const runtime = await res.json().catch(() => ({})); if (res.ok) { setAllowedModules(normalizeAllowedModules(runtime)); const activeRole = String( runtime?.active_role || runtime?.user?.active_role || roleKey || "" ).toUpperCase(); if (activeRole) setIsSuperAdmin(activeRole === "SUPER_ADMIN"); } else { setAllowedModules(null); } } catch { setAllowedModules(null); } setCheckedSession(true); } catch { clearAdminSession(); navigate(`/login?from=${encodeURIComponent(location.pathname + location.search)}`, { replace: true, }); } }; void verify(); }); const pageTitle = createMemo(() => { const path = location.pathname; for (const entry of PAGE_TITLES) { if ( entry.exact ? path === entry.prefix : path === entry.prefix || path.startsWith(`${entry.prefix}/`) ) { return entry.label; } } return "Admin"; }); const adminInitials = createMemo(() => { if (adminName().trim().toLowerCase() === "admin user") return "AD"; const parts = adminName() .split(" ") .map((s) => s.trim()) .filter(Boolean); if (parts.length === 0) return "U"; if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase(); return `${parts[0][0]}${parts[1][0]}`.toUpperCase(); }); createEffect(() => { const t = theme(); if (typeof localStorage !== "undefined") localStorage.setItem("nxtgauge_admin_theme", t); if (typeof document !== "undefined") document.documentElement.setAttribute("data-theme", t); }); const toggleTheme = () => setTheme((v) => (v === "dark" ? "light" : "dark")); const isDark = () => theme() === "dark"; createEffect(() => { if (!checkedSession()) return; if (isSuperAdmin()) return; const modules = allowedModules(); if (!modules || modules.length === 0) return; const path = location.pathname; if (path === "/admin") return; const matches = ROUTE_MODULE_KEYS.filter( (entry) => path === entry.prefix || path.startsWith(`${entry.prefix}/`) ); const guard = matches.sort((a, b) => b.prefix.length - a.prefix.length)[0]; if (!guard) return; const allowed = new Set( modules .map((m) => String(m || "") .trim() .toUpperCase() ) .filter(Boolean) ); const ok = guard.keys.some((k) => allowed.has(String(k).toUpperCase())); if (ok) return; navigate( `/admin?denied=${encodeURIComponent(guard.keys[0])}&from=${encodeURIComponent(path)}`, { replace: true } ); }); return (

You have been logged out

Your session has expired for security reasons. Please log in again to continue.

Checking session…
} >
setSidebarOpen(false)} />
setSidebarCollapsed((v) => !v)} onNavigate={() => setSidebarOpen(false)} adminName={adminName()} adminInitials={adminInitials()} theme={theme()} allowedModules={allowedModules()} isSuperAdmin={isSuperAdmin()} />
{ contentScrollRef = el; }} class="min-h-0 flex-1 overflow-y-scroll" style={{ background: isDark() ? "#0B1220" : "#F9FAFB", "scrollbar-gutter": "stable" }} >
{props.children}
); }