import { A, useLocation, useNavigate, useSearchParams } from '@solidjs/router'; import { For, Show, createEffect, createMemo, createSignal, onCleanup, onMount, type JSX, } from 'solid-js'; import { Bell, Search, Settings, User } from 'lucide-solid'; import AdminSidebar from './AdminSidebar'; import { isExternalIdentity } from '~/lib/admin-auth'; import { clearAdminSession, hasAdminSession, setAdminSession } from '~/lib/admin-session'; 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 TAB_SETS: Array<{ prefixes: string[]; tabs: Tab[] }> = [ { prefixes: ['/admin/runtime-roles'], tabs: [ { href: '/admin/runtime-roles', label: 'Roles', exact: true }, { href: '/admin/runtime-roles/new', label: 'Create Role' }, { href: '/admin/role-ui-configs', label: 'View Roles' }, ], }, ]; const SEARCH_MODULES = [ { label: 'Users', viewAllHref: '/admin/users', api: '/api/gateway/api/admin/users', listKeys: ['users', 'items'], titleKeys: ['full_name', 'name'], subtitleKeys: ['email', 'phone'], detailBase: '/admin/users', }, { label: 'Companies', viewAllHref: '/admin/company', api: '/api/gateway/api/admin/companies', listKeys: ['companies', 'items'], titleKeys: ['name', 'companyName'], subtitleKeys: ['email', 'phone'], detailBase: '/admin/company', }, { label: 'Employees', viewAllHref: '/admin/employees', api: '/api/gateway/api/admin/employees', listKeys: ['employees', 'items'], titleKeys: ['full_name', 'name'], subtitleKeys: ['email', 'department_name'], detailBase: '/admin/employees', }, { label: 'Jobs', viewAllHref: '/admin/jobs', api: '/api/gateway/api/admin/jobs', listKeys: ['jobs', 'items'], titleKeys: ['title', 'name'], subtitleKeys: ['status', 'company_name'], detailBase: '/admin/jobs', }, { label: 'Leads', viewAllHref: '/admin/leads', api: '/api/gateway/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-14 w-full rounded-2xl border-2 border-transparent bg-[#f9fafb] pl-12 pr-4 text-[16px] text-[#000032] placeholder:text-[rgba(0,0,50,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-[#fa5014]' : 'text-[rgba(0,0,50,0.6)] hover:text-[#000032]' }`} > {tab.label} )}
); } export default function AdminShell(props: { children: JSX.Element }) { const location = useLocation(); const navigate = useNavigate(); const [searchParams] = useSearchParams(); const [checkedSession, setCheckedSession] = createSignal(false); const [adminName, setAdminName] = createSignal('Admin User'); const [sidebarOpen, setSidebarOpen] = createSignal(false); const [sidebarCollapsed, setSidebarCollapsed] = createSignal(false); const [notifCount] = createSignal(0); const [tabsTrackEl, setTabsTrackEl] = createSignal(); const [tabRefs, setTabRefs] = createSignal>({}); const [tabIndicator, setTabIndicator] = createSignal({ left: 0, width: 0, ready: false }); 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); }); onMount(() => { window.addEventListener('resize', refreshTabIndicator); onCleanup(() => window.removeEventListener('resize', refreshTabIndicator)); const isLocalDev = typeof window !== 'undefined' && (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'); const isPreview = searchParams._preview === '1' || (typeof sessionStorage !== 'undefined' && sessionStorage.getItem('nxtgauge_admin_preview') === '1'); if (isPreview || isLocalDev) { if (typeof sessionStorage !== 'undefined') sessionStorage.setItem('nxtgauge_admin_preview', '1'); setAdminSession(); setCheckedSession(true); return; } 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/gateway/users/auth/me', { 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); setCheckedSession(true); } catch { clearAdminSession(); navigate(`/login?from=${encodeURIComponent(location.pathname + location.search)}`, { replace: true }); } }; void verify(); }); const adminInitials = createMemo(() => { 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(); }); return (
Checking session…
} >
setSidebarOpen(false)} />
setSidebarCollapsed((v) => !v)} onNavigate={() => setSidebarOpen(false)} adminName={adminName()} adminInitials={adminInitials()} />
{props.children}
); }