import { Match, Show, Switch, createEffect, createMemo, createResource, createSignal, onMount } from 'solid-js'; import { useNavigate } from '@solidjs/router'; import { useAuth, RequireAuth } from '~/lib/auth'; import DashboardDesignPreview from '~/components/admin/DashboardDesignPreview'; import DashboardShell from '~/components/DashboardShell'; import ProfilePage from '~/components/dashboard/ProfilePage'; import PortfolioPage from '~/components/dashboard/PortfolioPage'; import VerificationStatusPage from '~/components/dashboard/VerificationStatusPage'; import CompanyJobsPage from '~/components/dashboard/CompanyJobsPage'; import CompanyApplicationsPage from '~/components/dashboard/CompanyApplicationsPage'; import SettingsPage from '~/components/dashboard/SettingsPage'; import MyDashboardPage from '~/components/dashboard/MyDashboardPage'; import CustomerRequirementsPage from '~/components/dashboard/CustomerRequirementsPage'; import CustomerResponsesPage from '~/components/dashboard/CustomerResponsesPage'; import CompanyShortlistedCandidatesPage from '~/components/dashboard/CompanyShortlistedCandidatesPage'; import JobSeekerApplicationsPage from '~/components/dashboard/JobSeekerApplicationsPage'; import JobSeekerSavedJobsPage from '~/components/dashboard/JobSeekerSavedJobsPage'; import JobSeekerJobsPage from '~/components/dashboard/JobSeekerJobsPage'; import ProfessionalLeadsPage from '~/components/dashboard/ProfessionalLeadsPage'; import ProfessionalResponsesPage from '~/components/dashboard/ProfessionalResponsesPage'; import CreditsPage from '~/components/dashboard/CreditsPage'; import ExploreServicesPage from '~/components/dashboard/ExploreServicesPage'; import HelpCenterDashboardPage from '~/components/dashboard/HelpCenterDashboardPage'; import SwitchServicesPage from '~/components/dashboard/SwitchServicesPage'; import LogoutPage from '~/components/dashboard/LogoutPage'; import { PROFESSIONAL_ROLE_SET } from '~/components/dashboard/RoleDashboardShared'; // Sidebar items that have real implementations (not the preview mock) const BASE_REAL_PAGES = ['my dashboard', 'my profile', 'my portfolio', 'verification', 'settings']; const COMMON_REAL_PAGES = ['credits', 'explore nxtgauge', 'help center', 'switch services', 'logout']; const COMPANY_REAL_PAGES = ['jobs', 'applications', 'shortlisted candidates']; const CUSTOMER_REAL_PAGES = ['my requirements', 'received responses', 'shortlisted responses']; const JOB_SEEKER_REAL_PAGES = ['jobs', 'my applications', 'saved jobs']; const PROFESSIONAL_REAL_PAGES = ['leads', 'my responses']; type RoleKey = | 'COMPANY' | 'CUSTOMER' | 'JOB_SEEKER' | 'PHOTOGRAPHER' | 'MAKEUP_ARTIST' | 'TUTOR' | 'DEVELOPER' | 'VIDEO_EDITOR' | 'UGC_CONTENT_CREATOR' | 'GRAPHIC_DESIGNER' | 'SOCIAL_MEDIA_MANAGER' | 'FITNESS_TRAINER' | 'CATERING_SERVICES'; type RuntimeBundle = { role: RoleKey; status: 'ACTIVE' | 'INACTIVE'; sidebarItems: string[]; tabs: string[]; widgets: string[]; fields: string[]; verificationStatus?: string; source: 'dashboard-config'; }; const API_GATEWAY = '/api/gateway'; const SERVER_API_BASE = (process.env.PUBLIC_API_URL || 'http://localhost:8080/api').replace(/\/+$/, ''); const ROLE_OPTIONS: RoleKey[] = [ 'COMPANY', 'CUSTOMER', 'JOB_SEEKER', 'PHOTOGRAPHER', 'MAKEUP_ARTIST', 'TUTOR', 'DEVELOPER', 'VIDEO_EDITOR', 'UGC_CONTENT_CREATOR', 'GRAPHIC_DESIGNER', 'SOCIAL_MEDIA_MANAGER', 'FITNESS_TRAINER', 'CATERING_SERVICES', ]; const ROLE_BASED_SIDEBAR: Record = { PHOTOGRAPHER: ['My Dashboard', 'My Profile', 'My Portfolio', 'Leads', 'My Responses', 'Credits', 'Explore Nxtgauge', 'Verification', 'Help Center', 'Settings', 'Switch Services', 'Logout'], MAKEUP_ARTIST: ['My Dashboard', 'My Profile', 'My Portfolio', 'Leads', 'My Responses', 'Credits', 'Explore Nxtgauge', 'Verification', 'Help Center', 'Settings', 'Switch Services', 'Logout'], TUTOR: ['My Dashboard', 'My Profile', 'My Portfolio', 'Leads', 'My Responses', 'Credits', 'Explore Nxtgauge', 'Verification', 'Help Center', 'Settings', 'Switch Services', 'Logout'], DEVELOPER: ['My Dashboard', 'My Profile', 'My Portfolio', 'Leads', 'My Responses', 'Credits', 'Explore Nxtgauge', 'Verification', 'Help Center', 'Settings', 'Switch Services', 'Logout'], VIDEO_EDITOR: ['My Dashboard', 'My Profile', 'My Portfolio', 'Leads', 'My Responses', 'Credits', 'Explore Nxtgauge', 'Verification', 'Help Center', 'Settings', 'Switch Services', 'Logout'], UGC_CONTENT_CREATOR: ['My Dashboard', 'My Profile', 'My Portfolio', 'Leads', 'My Responses', 'Credits', 'Explore Nxtgauge', 'Verification', 'Help Center', 'Settings', 'Switch Services', 'Logout'], GRAPHIC_DESIGNER: ['My Dashboard', 'My Profile', 'My Portfolio', 'Leads', 'My Responses', 'Credits', 'Explore Nxtgauge', 'Verification', 'Help Center', 'Settings', 'Switch Services', 'Logout'], SOCIAL_MEDIA_MANAGER: ['My Dashboard', 'My Profile', 'My Portfolio', 'Leads', 'My Responses', 'Credits', 'Explore Nxtgauge', 'Verification', 'Help Center', 'Settings', 'Switch Services', 'Logout'], FITNESS_TRAINER: ['My Dashboard', 'My Profile', 'My Portfolio', 'Leads', 'My Responses', 'Credits', 'Explore Nxtgauge', 'Verification', 'Help Center', 'Settings', 'Switch Services', 'Logout'], CATERING_SERVICES: ['My Dashboard', 'My Profile', 'My Portfolio', 'Leads', 'My Responses', 'Credits', 'Explore Nxtgauge', 'Verification', 'Help Center', 'Settings', 'Switch Services', 'Logout'], COMPANY: ['My Dashboard', 'My Profile', 'Jobs', 'Applications', 'Shortlisted Candidates', 'Credits', 'Explore Nxtgauge', 'Verification', 'Help Center', 'Settings', 'Switch Services', 'Logout'], JOB_SEEKER: ['My Dashboard', 'My Profile', 'Jobs', 'My Applications', 'Saved Jobs', 'Explore Nxtgauge', 'Verification', 'Help Center', 'Settings', 'Switch Services', 'Logout'], CUSTOMER: ['My Dashboard', 'My Profile', 'My Requirements', 'Received Responses', 'Shortlisted Responses', 'Credits', 'Explore Nxtgauge', 'Verification', 'Help Center', 'Settings', 'Switch Services', 'Logout'], }; const ROLE_PREFIXES: Record = { PHOTOGRAPHER: 'photographers', MAKEUP_ARTIST: 'makeup-artists', TUTOR: 'tutors', DEVELOPER: 'developers', VIDEO_EDITOR: 'video-editors', GRAPHIC_DESIGNER: 'graphic-designers', SOCIAL_MEDIA_MANAGER: 'social-media-managers', FITNESS_TRAINER: 'fitness-trainers', CATERING_SERVICES: 'catering-services', UGC_CONTENT_CREATOR: 'ugc-content-creators', CUSTOMER: 'customers', COMPANY: 'companies', JOB_SEEKER: 'jobseeker', }; function getNameFromStorage(): string { if (typeof window === 'undefined') return 'User'; const keys = ['nxtgauge_signup_profile_v1', 'nxtgauge_auth_user', 'nxtgauge_user']; for (const key of keys) { try { const raw = window.localStorage.getItem(key) || window.sessionStorage.getItem(key); if (!raw) continue; const parsed = JSON.parse(raw); const name = parsed?.name || parsed?.first_name || parsed?.user?.name || parsed?.user?.first_name; if (name) return String(name); } catch { /* ignore */ } } return 'User'; } const EXPLORE_ROLES = [ { key: 'PHOTOGRAPHER', name: 'Photographer' }, { key: 'MAKEUP_ARTIST', name: 'Makeup Artist' }, { key: 'TUTOR', name: 'Tutor' }, { key: 'DEVELOPER', name: 'Developer' }, { key: 'VIDEO_EDITOR', name: 'Video Editor' }, { key: 'UGC_CONTENT_CREATOR', name: 'UGC Content Creator' }, { key: 'GRAPHIC_DESIGNER', name: 'Graphic Designer' }, { key: 'SOCIAL_MEDIA_MANAGER', name: 'Social Media Manager' }, { key: 'FITNESS_TRAINER', name: 'Fitness Trainer' }, { key: 'CATERING_SERVICES', name: 'Catering Services' }, ]; function normalizeRole(value: string): RoleKey { const up = String(value || '').trim().toUpperCase().replace(/\s+/g, '_'); return (ROLE_OPTIONS.find((r) => r === up) || 'JOB_SEEKER') as RoleKey; } function asStringArray(value: unknown): string[] { if (!Array.isArray(value)) return []; return value .map((item) => String(item || '').trim()) .filter(Boolean); } function getInitialRoleFromStorage(): RoleKey { if (typeof window === 'undefined') return 'JOB_SEEKER'; const keys = ['nxtgauge_signup_profile_v1', 'nxtgauge_auth_user', 'nxtgauge_user']; for (const key of keys) { try { const raw = window.localStorage.getItem(key) || window.sessionStorage.getItem(key); if (!raw) continue; const parsed = JSON.parse(raw); const found = normalizeRole( String( parsed?.roleKey || parsed?.role || parsed?.active_role || parsed?.user?.active_role || parsed?.user?.roles?.[0] || '', ), ); if (ROLE_OPTIONS.includes(found)) return found; } catch { // ignore invalid payload } } return 'JOB_SEEKER'; } async function fetchJson(path: string): Promise { try { const isServer = typeof window === 'undefined'; const target = isServer ? `${SERVER_API_BASE}${path}` : `${API_GATEWAY}${path}`; const res = await fetch(target, { credentials: 'include' }); if (!res.ok) return null; return await res.json(); } catch { return null; } } async function loadRoleBundle(role: RoleKey): Promise { if (typeof window === 'undefined') { return null; } const runtime = await fetchJson('/api/runtime-config'); if (runtime) { const runtimeRole = normalizeRole( String(runtime?.role || runtime?.user?.active_role || role), ); const runtimeSidebar = asStringArray( runtime?.dashboard_config?.sidebar_items ?? runtime?.dashboard_config?.sidebarItems ?? runtime?.sidebar_items ?? runtime?.sidebarItems, ); const runtimeTabs = asStringArray( runtime?.dashboard_config?.tabs ?? runtime?.tabs, ); const runtimeWidgetsRaw = Array.isArray(runtime?.dashboard_config?.widgets) ? runtime.dashboard_config.widgets : (Array.isArray(runtime?.widgets) ? runtime.widgets : []); const runtimeWidgets = runtimeWidgetsRaw .map((item: any) => String(typeof item === 'string' ? item : (item?.key || item?.id || '')).trim()) .filter(Boolean); const runtimeFields = asStringArray( runtime?.dashboard_config?.fields ?? runtime?.fields, ); return { role: runtimeRole, status: 'ACTIVE', sidebarItems: runtimeSidebar, tabs: runtimeTabs, widgets: runtimeWidgets, fields: runtimeFields, verificationStatus: String(runtime?.verification_status || runtime?.user?.verification_status || '').toUpperCase() || undefined, source: 'dashboard-config', }; } let payload = await fetchJson(`/api/config/dashboard/by-key/${encodeURIComponent(role)}?audience=EXTERNAL`); if (!payload) { const listPayload = await fetchJson('/api/admin/dashboard-config?audience=EXTERNAL'); const rows = Array.isArray(listPayload) ? listPayload : (Array.isArray(listPayload?.items) ? listPayload.items : []); const matched = rows.find((row: any) => String(row?.role_key || row?.config_json?.role_key || '').toUpperCase() === role); if (matched) payload = matched; } const config = (payload?.config_json || payload || null) as Record | null; if (!config) return null; const sidebarItems = asStringArray((config as any)?.sidebar_items ?? (config as any)?.sidebarItems); const tabs = asStringArray((config as any)?.tabs); const widgetsRaw = Array.isArray((config as any)?.widgets) ? (config as any).widgets : []; const widgets = widgetsRaw .map((item: any) => String(typeof item === 'string' ? item : (item?.key || item?.id || '')).trim()) .filter(Boolean); const fields = asStringArray((config as any)?.fields); return { role, status: payload?.is_active === false ? 'INACTIVE' : 'ACTIVE', sidebarItems, tabs, widgets, fields, source: 'dashboard-config', }; } function mergeSidebar(role: RoleKey, runtimeSidebar: string[], verificationStatus?: string): string[] { const base = ROLE_BASED_SIDEBAR[role] || ['My Dashboard', 'My Profile', 'Switch Services', 'Logout']; const fromRuntime = runtimeSidebar.filter(Boolean); const map = new Map(); for (const item of [...fromRuntime, ...base]) { const key = item.trim().toLowerCase(); if (!map.has(key)) map.set(key, item); } let merged = Array.from(map.values()); if (role === 'JOB_SEEKER') { merged = merged.filter((item) => item.trim().toLowerCase() !== 'credits'); } if (!merged.some((item) => item.trim().toLowerCase() === 'explore nxtgauge')) { const insertBefore = merged.findIndex((item) => item.trim().toLowerCase() === 'verification'); if (insertBefore >= 0) merged.splice(insertBefore, 0, 'Explore Nxtgauge'); else merged.push('Explore Nxtgauge'); } const status = String(verificationStatus || '').toUpperCase(); const approved = status === 'APPROVED'; if (!approved && status) { const restricted = new Set( [ 'my profile', 'help center', 'settings', 'verification', 'logout', ...(PROFESSIONAL_ROLE_SET.has(role) ? ['my portfolio', 'credits'] : []), ], ); merged = merged.filter((item) => restricted.has(item.trim().toLowerCase())); } return merged; } export default function RuntimeDashboardPage() { const navigate = useNavigate(); const auth = useAuth(); const [hydrated, setHydrated] = createSignal(false); const [role, setRole] = createSignal('JOB_SEEKER'); const [activeSidebar, setActiveSidebar] = createSignal('My Dashboard'); const [activeTab, setActiveTab] = createSignal('overview'); const [userName, setUserName] = createSignal('User'); const [userId, setUserId] = createSignal(''); onMount(() => { setHydrated(true); const storedRole = getInitialRoleFromStorage(); setRole(storedRole); setUserName(getNameFromStorage()); if (auth.user()) { const u = auth.user()!; if (u.full_name) setUserName(u.full_name); if (u.id) setUserId(u.id); if (u.active_role) setRole(normalizeRole(u.active_role)); } }); createEffect(() => { const u = auth.user(); if (u) { if (u.full_name && userName() === 'User') setUserName(u.full_name); if (u.id && !userId()) setUserId(u.id); if (u.active_role) setRole(normalizeRole(u.active_role)); } }); const [bundle] = createResource( () => role(), loadRoleBundle, ); const sidebarItems = createMemo(() => mergeSidebar(role(), bundle()?.sidebarItems || [], bundle()?.verificationStatus)); createEffect(() => { const runtimeRole = bundle()?.role; if (runtimeRole && runtimeRole !== role()) setRole(runtimeRole); }); createEffect(() => { const first = sidebarItems()[0] || 'My Dashboard'; const current = activeSidebar(); const exists = sidebarItems().some((item) => item.toLowerCase() === current.toLowerCase()); if (!exists) setActiveSidebar(first); }); const tabs = createMemo(() => { const fromRuntime = bundle()?.tabs || []; return fromRuntime.length > 0 ? fromRuntime : ['overview']; }); createEffect(() => { role(); const firstTab = tabs()[0] || 'overview'; setActiveTab((prev) => (prev ? prev : firstTab)); }); const loading = createMemo(() => !hydrated() || bundle.loading); const ready = createMemo(() => hydrated() && !bundle.loading); const liveData = createMemo(() => { const prefix = ROLE_PREFIXES[role()]; if (!prefix) return undefined; return { userName: userName(), userId: userId(), rolePrefix: prefix }; }); const isRealPage = createMemo(() => { const key = activeSidebar().toLowerCase(); if (BASE_REAL_PAGES.includes(key)) return true; if (COMMON_REAL_PAGES.includes(key)) return true; if (role() === 'COMPANY' && COMPANY_REAL_PAGES.includes(key)) return true; if (role() === 'CUSTOMER' && CUSTOMER_REAL_PAGES.includes(key)) return true; if (role() === 'JOB_SEEKER' && JOB_SEEKER_REAL_PAGES.includes(key)) return true; if (PROFESSIONAL_ROLE_SET.has(role()) && PROFESSIONAL_REAL_PAGES.includes(key)) return true; return false; }); return (
Loading dashboard…
{/* ── Real pages: DashboardShell + actual components ── */} {/* ── All other views: DashboardDesignPreview mock ── */}
); } const cardStyle = { border: '1px solid #E5E7EB', 'border-radius': '12px', padding: '16px', background: '#fff', color: '#111827', } as const;