nxtgauge-frontend-solid/src/components/dashboard/DashboardLayout.tsx

167 lines
8 KiB
TypeScript
Raw Normal View History

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">&nbsp;</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>
);
}