import { Component, Show, createEffect, For, createSignal } from 'solid-js'; import { useNavigate, A } from '@solidjs/router'; import { authState, logout, switchRole } from '~/lib/auth'; import { shouldShowRoleSwitcher } from '~/lib/auth-flow'; import { getRoleTourStorageKey, getWelcomeTourStorageKey, pickGuidedTour, readSeenRoleTours, WELCOME_TOUR_VALUE, writeSeenRoleTours, } from '~/lib/guided-tour'; import type { GuidedTourKind } from '~/lib/guided-tour'; import { type GuidedTourStep, resolveRoleApprovedTourSteps, resolveWelcomeTourSteps, } from '~/lib/guided-tour-content'; // ── Icons (inline SVGs for zero deps) ───────────────────────────────────────── const IconDashboard = () => ( ); const IconJobs = () => ( ); const IconBell = () => ( ); const IconSettings = () => ( ); const IconLogout = () => ( ); const IconCompass = () => ( ); // ── Module → nav item mapping ───────────────────────────────────────────────── const MODULE_NAV_MAP: Record = { // Uppercase module keys 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 }, // Lowercase module keys (from seed/runtime config) jobs: { label: 'Jobs', href: '/dashboard/jobs', icon: IconJobs }, applications: { label: 'Applications', href: '/dashboard/applications', icon: IconJobs }, profile: { label: 'Profile', href: '/dashboard/profile', icon: IconSettings }, browse_jobs: { label: 'Browse Jobs', href: '/dashboard/jobs', 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 }, leads: { label: 'My Leads', href: '/dashboard/requests', 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 }, notifications: { label: 'Notifications', href: '/dashboard/notifications', icon: IconBell }, settings: { label: 'Settings', href: '/dashboard/settings', icon: IconSettings }, }; // ── Dashboard Layout ────────────────────────────────────────────────────────── export default function DashboardLayout(props: { children: any }) { const navigate = useNavigate(); const [switchingRole, setSwitchingRole] = createSignal(false); const [tourKind, setTourKind] = createSignal(null); const [tourStepIndex, setTourStepIndex] = createSignal(0); 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; } }); const rc = () => authState().runtime_config; const roleOptions = () => rc()?.user?.roles ?? []; const activeRole = () => rc()?.user?.active_role ?? rc()?.role ?? ''; const activeTourSteps = (): GuidedTourStep[] => { const kind = tourKind(); if (!kind) return []; if (kind === 'welcome') return resolveWelcomeTourSteps(rc()?.guided_tours ?? null); return resolveRoleApprovedTourSteps(activeRole(), rc()?.guided_tours ?? null); }; const tourStep = () => activeTourSteps()[tourStepIndex()]; const navItems = () => { if (rc()?.role === 'USER') { return [ { label: 'Dashboard', href: '/dashboard', icon: IconDashboard }, { label: 'Explore Nxtgauge', href: '/dashboard/explore', icon: IconCompass }, { label: 'Settings', href: '/dashboard/settings', icon: IconSettings }, ]; } const modules = rc()?.enabled_modules ?? []; const seen = new Set(); 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 }); } async function handleRoleSwitch(nextRole: string) { const normalized = String(nextRole || '').trim().toUpperCase(); if (!normalized || normalized === activeRole()) return; setSwitchingRole(true); try { await switchRole(normalized); navigate('/dashboard', { replace: true }); } finally { setSwitchingRole(false); } } function finishTour() { if (typeof window === 'undefined') return; const userId = rc()?.user?.id; if (!userId || !tourKind()) { setTourKind(null); setTourStepIndex(0); return; } if (tourKind() === 'welcome') { window.localStorage.setItem(getWelcomeTourStorageKey(userId), WELCOME_TOUR_VALUE); } else { const key = getRoleTourStorageKey(userId); const seen = readSeenRoleTours(window.localStorage.getItem(key)); seen.add(activeRole()); window.localStorage.setItem(key, writeSeenRoleTours(seen)); } setTourKind(null); setTourStepIndex(0); } function nextTourStep() { const total = activeTourSteps().length; if (tourStepIndex() >= total - 1) { finishTour(); return; } setTourStepIndex((current) => current + 1); } createEffect(() => { if (typeof window === 'undefined') return; const runtime = rc(); const userId = runtime?.user?.id; if (!runtime || !userId) return; const welcomeTourSeen = window.localStorage.getItem(getWelcomeTourStorageKey(userId)) === WELCOME_TOUR_VALUE; const seenRoleTours = readSeenRoleTours(window.localStorage.getItem(getRoleTourStorageKey(userId))); const nextTour = pickGuidedTour({ userId, activeRole: runtime?.user?.active_role ?? runtime?.role, welcomeTourSeen, seenRoleTours, }); if (nextTour && nextTour !== tourKind()) { setTourKind(nextTour); setTourStepIndex(0); } }); return ( {/* ── Sidebar ── */} {/* ── Main Content ── */} {/* Top bar */} handleRoleSwitch(e.currentTarget.value)} title="Switch your dashboard view" > {(role) => {role.replaceAll('_', ' ')}} Choose What You Need {rc()?.user?.full_name ?? 'User'} {/* Page content */} Loading...}> {props.children} Guided Tour {tourStep()?.title} {tourStep()?.body} Skip for now 0}> setTourStepIndex((current) => current - 1)}>Back {tourStepIndex() >= activeTourSteps().length - 1 ? 'Finish' : 'Next'} ); }
Guided Tour
{tourStep()?.body}