167 lines
8 KiB
TypeScript
167 lines
8 KiB
TypeScript
|
|
import { Component, Show, createEffect, For } from 'solid-js';
|
||
|
|
import { useNavigate, A } from '@solidjs/router';
|
||
|
|
import { authState, fetchRuntimeConfig, logout, hasModule } from '~/lib/auth';
|
||
|
|
|
||
|
|
// ── Icons (inline SVGs for zero deps) ─────────────────────────────────────────
|
||
|
|
|
||
|
|
const IconDashboard = () => (
|
||
|
|
<svg width="20" height="20" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||
|
|
<rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/>
|
||
|
|
<rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/>
|
||
|
|
</svg>
|
||
|
|
);
|
||
|
|
const IconJobs = () => (
|
||
|
|
<svg width="20" height="20" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||
|
|
<rect x="2" y="7" width="20" height="14" rx="2"/><path d="M16 7V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v2"/>
|
||
|
|
<line x1="12" y1="12" x2="12" y2="17"/><line x1="9" y1="14.5" x2="15" y2="14.5"/>
|
||
|
|
</svg>
|
||
|
|
);
|
||
|
|
const IconBell = () => (
|
||
|
|
<svg width="20" height="20" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||
|
|
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/>
|
||
|
|
</svg>
|
||
|
|
);
|
||
|
|
const IconSettings = () => (
|
||
|
|
<svg width="20" height="20" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||
|
|
<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>
|
||
|
|
</svg>
|
||
|
|
);
|
||
|
|
const IconLogout = () => (
|
||
|
|
<svg width="20" height="20" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||
|
|
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/>
|
||
|
|
<line x1="21" y1="12" x2="9" y2="12"/>
|
||
|
|
</svg>
|
||
|
|
);
|
||
|
|
const IconCompass = () => (
|
||
|
|
<svg width="20" height="20" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||
|
|
<circle cx="12" cy="12" r="10"/><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"/>
|
||
|
|
</svg>
|
||
|
|
);
|
||
|
|
|
||
|
|
// ── Module → nav item mapping ─────────────────────────────────────────────────
|
||
|
|
|
||
|
|
const MODULE_NAV_MAP: Record<string, { label: string; href: string; icon: Component }> = {
|
||
|
|
COMPANY_DASHBOARD: { label: 'Dashboard', href: '/dashboard', icon: IconDashboard },
|
||
|
|
JOBSEEKER_DASHBOARD: { label: 'Dashboard', href: '/dashboard', icon: IconDashboard },
|
||
|
|
CUSTOMER_DASHBOARD: { label: 'Dashboard', href: '/dashboard', icon: IconDashboard },
|
||
|
|
PROFESSIONAL_DASHBOARD: { label: 'Dashboard', href: '/dashboard', icon: IconDashboard },
|
||
|
|
JOBS: { label: 'Jobs', href: '/dashboard/jobs', icon: IconJobs },
|
||
|
|
JOBS_BROWSE: { label: 'Browse Jobs', href: '/dashboard/jobs', icon: IconJobs },
|
||
|
|
APPLICATIONS: { label: 'Applications', href: '/dashboard/applications', icon: IconJobs },
|
||
|
|
MY_APPLICATIONS: { label: 'My Applications', href: '/dashboard/applications', icon: IconJobs },
|
||
|
|
REQUIREMENTS: { label: 'Requirements', href: '/dashboard/requirements', icon: IconJobs },
|
||
|
|
MARKETPLACE: { label: 'Marketplace', href: '/dashboard/marketplace', icon: IconCompass },
|
||
|
|
MY_REQUESTS: { label: 'My Requests', href: '/dashboard/requests', icon: IconJobs },
|
||
|
|
ACCEPTED_LEADS: { label: 'Accepted Leads',href: '/dashboard/leads/accepted', icon: IconJobs },
|
||
|
|
PORTFOLIO: { label: 'Portfolio', href: '/dashboard/portfolio', icon: IconJobs },
|
||
|
|
SERVICES: { label: 'Services', href: '/dashboard/services', icon: IconJobs },
|
||
|
|
WALLET: { label: 'Wallet', href: '/dashboard/wallet', icon: IconCompass },
|
||
|
|
COMPANY_PROFILE: { label: 'Profile', href: '/dashboard/profile', icon: IconSettings },
|
||
|
|
JOBSEEKER_PROFILE: { label: 'Profile', href: '/dashboard/profile', icon: IconSettings },
|
||
|
|
CUSTOMER_PROFILE: { label: 'Profile', href: '/dashboard/profile', icon: IconSettings },
|
||
|
|
PROFESSIONAL_PROFILE: { label: 'Profile', href: '/dashboard/profile', icon: IconSettings },
|
||
|
|
PURCHASE_PACKAGES: { label: 'Buy Packages', href: '/dashboard/packages', icon: IconCompass },
|
||
|
|
NOTIFICATIONS: { label: 'Notifications', href: '/dashboard/notifications', icon: IconBell },
|
||
|
|
SETTINGS: { label: 'Settings', href: '/dashboard/settings', icon: IconSettings },
|
||
|
|
EXPLORE_NXTGAUGE: { label: 'Explore Nxtgauge', href: '/dashboard/explore', icon: IconCompass },
|
||
|
|
};
|
||
|
|
|
||
|
|
// ── Dashboard Layout ──────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
export default function DashboardLayout(props: { children: any }) {
|
||
|
|
const navigate = useNavigate();
|
||
|
|
const state = authState();
|
||
|
|
|
||
|
|
createEffect(() => {
|
||
|
|
const s = authState();
|
||
|
|
if (!s.access_token) {
|
||
|
|
navigate('/auth/login', { replace: true });
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (s.runtime_config?.onboarding_required) {
|
||
|
|
navigate('/onboarding', { replace: true });
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (s.runtime_config?.role === 'USER') {
|
||
|
|
navigate('/choose-role', { replace: true });
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
const rc = () => authState().runtime_config;
|
||
|
|
|
||
|
|
const navItems = () => {
|
||
|
|
const modules = rc()?.enabled_modules ?? [];
|
||
|
|
const seen = new Set<string>();
|
||
|
|
return modules
|
||
|
|
.map(m => MODULE_NAV_MAP[m])
|
||
|
|
.filter(item => {
|
||
|
|
if (!item || seen.has(item.href)) return false;
|
||
|
|
seen.add(item.href);
|
||
|
|
return true;
|
||
|
|
});
|
||
|
|
};
|
||
|
|
|
||
|
|
async function handleLogout() {
|
||
|
|
await logout();
|
||
|
|
navigate('/auth/login', { replace: true });
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div class="dashboard-shell">
|
||
|
|
{/* ── Sidebar ── */}
|
||
|
|
<aside class="sidebar">
|
||
|
|
<div class="sidebar-logo">
|
||
|
|
<A href="/dashboard">
|
||
|
|
<span class="logo-text">NXTGAUGE</span>
|
||
|
|
</A>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="sidebar-role-badge">
|
||
|
|
<span class="role-badge">{rc()?.user?.active_role ?? 'Loading...'}</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<nav class="sidebar-nav">
|
||
|
|
<For each={navItems()}>
|
||
|
|
{(item) => (
|
||
|
|
<A href={item.href} class="nav-item" activeClass="nav-item-active">
|
||
|
|
<item.icon />
|
||
|
|
<span>{item.label}</span>
|
||
|
|
</A>
|
||
|
|
)}
|
||
|
|
</For>
|
||
|
|
</nav>
|
||
|
|
|
||
|
|
<div class="sidebar-footer">
|
||
|
|
<button class="nav-item nav-item-logout" onClick={handleLogout}>
|
||
|
|
<IconLogout />
|
||
|
|
<span>Logout</span>
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</aside>
|
||
|
|
|
||
|
|
{/* ── Main Content ── */}
|
||
|
|
<div class="dashboard-main">
|
||
|
|
{/* Top bar */}
|
||
|
|
<header class="dashboard-topbar">
|
||
|
|
<div class="topbar-title"> </div>
|
||
|
|
<div class="topbar-right">
|
||
|
|
<A href="/dashboard/notifications" class="topbar-icon-btn" title="Notifications">
|
||
|
|
<IconBell />
|
||
|
|
</A>
|
||
|
|
<div class="topbar-user">
|
||
|
|
<span class="topbar-name">{rc()?.user?.full_name ?? 'User'}</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</header>
|
||
|
|
|
||
|
|
{/* Page content */}
|
||
|
|
<main class="dashboard-content">
|
||
|
|
<Show when={rc()} fallback={<div class="loading-spinner">Loading...</div>}>
|
||
|
|
{props.children}
|
||
|
|
</Show>
|
||
|
|
</main>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|