From 188de040aed3a2c149b7448ae83cfae060a7e680 Mon Sep 17 00:00:00 2001 From: Tracewebstudio Dev Date: Wed, 22 Apr 2026 01:13:56 +0200 Subject: [PATCH] fix: MyDashboardPage reads role from URL directly, skips stale prop closure; loadRoleBundle uses auth; no fallback config fetch --- src/components/dashboard/MyDashboardPage.tsx | 36 ++++++++--- .../dashboard/RoleDashboardShared.ts | 24 +++++++ src/routes/dashboard.tsx | 62 ++++--------------- 3 files changed, 64 insertions(+), 58 deletions(-) diff --git a/src/components/dashboard/MyDashboardPage.tsx b/src/components/dashboard/MyDashboardPage.tsx index 7d24fd5..0f007e8 100644 --- a/src/components/dashboard/MyDashboardPage.tsx +++ b/src/components/dashboard/MyDashboardPage.tsx @@ -1,6 +1,6 @@ import { For, Show, createMemo, createSignal, onMount } from 'solid-js'; import { BTN_GHOST, CARD } from '~/components/DashboardShell'; -import { PROFESSIONAL_ROLE_SET, ROLE_PREFIXES, type RoleKey } from './RoleDashboardShared'; +import { normalizeRole, PROFESSIONAL_ROLE_SET, ROLE_PREFIXES, type RoleKey } from './RoleDashboardShared'; import { readJobSeekerProfile } from '~/lib/job-seeker-custom-data'; const API = '/api/gateway'; @@ -31,13 +31,29 @@ export default function MyDashboardPage(props: Props) { const roleLabel = createMemo(() => String(props.roleKey || '').replace(/_/g, ' ')); + const getEffectiveRole = (): RoleKey => { + if (typeof window === 'undefined') return props.roleKey; + const urlParams = new URLSearchParams(window.location.search); + const urlRole = urlParams.get('role'); + if (urlRole) { + const normalized = normalizeRole(urlRole); + if (normalized) return normalized; + } + return props.roleKey; + }; + const loadData = async () => { + const effectiveRole = getEffectiveRole(); + console.log('[MyDashboardPage] loadData called, effectiveRole:', effectiveRole, 'props.roleKey:', props.roleKey); + setLoading(true); setErr(''); const next: Metric[] = []; + const roleKey = effectiveRole; + try { - if (props.roleKey === 'COMPANY') { + if (roleKey === 'COMPANY') { const [jobsRes, appsRes] = await Promise.all([ apiFetch('/api/companies/jobs?page=1&limit=100'), apiFetch('/api/companies/jobs?page=1&limit=1'), @@ -52,7 +68,7 @@ export default function MyDashboardPage(props: Props) { { title: 'Latest Sync', value: appsRes.ok ? 'Live' : 'Partial', hint: 'Dashboard data status' }, ); if (!jobsRes.ok && !appsRes.ok) setErr('Some company metrics could not be loaded.'); - } else if (props.roleKey === 'CUSTOMER') { + } else if (roleKey === 'CUSTOMER') { const reqRes = await apiFetch('/api/customers/requirements?page=1&limit=100'); const reqJson = await reqRes.json().catch(() => ({})); const reqs = Array.isArray(reqJson?.data) ? reqJson.data : []; @@ -63,7 +79,7 @@ export default function MyDashboardPage(props: Props) { { title: 'Drafts', value: String(reqs.filter((r: any) => String(r.status || '').toUpperCase() === 'DRAFT').length), hint: 'Not yet submitted' }, ); if (!reqRes.ok) setErr('Some customer metrics could not be loaded.'); - } else if (props.roleKey === 'JOB_SEEKER') { + } else if (roleKey === 'JOB_SEEKER') { const [jobsRes, appsRes, profile] = await Promise.all([ apiFetch('/api/jobseeker/jobs?page=1&limit=100'), apiFetch('/api/jobseeker/applications?page=1&limit=100'), @@ -77,8 +93,8 @@ export default function MyDashboardPage(props: Props) { ? (profile.custom_data as Record) : {}; const savedJobs = Array.isArray(customData.saved_jobs) ? customData.saved_jobs : []; - const portfolio = (customData.job_seeker_portfolio && typeof customData.job_seeker_portfolio === 'object') - ? (customData.job_seeker_portfolio as Record) + const portfolio = (customData.job_seeker_portfolio && typeof profile.job_seeker_portfolio === 'object') + ? (profile.job_seeker_portfolio as Record) : {}; const profileStatus = String(profile?.status || 'NOT_SUBMITTED').replace(/_/g, ' '); const portfolioDone = Boolean( @@ -96,8 +112,8 @@ export default function MyDashboardPage(props: Props) { { title: 'Portfolio', value: portfolioDone ? 'Complete' : 'Incomplete', hint: 'Education/work/skills sections' }, ); if (!jobsRes.ok && !appsRes.ok && !profile) setErr('Some job seeker metrics could not be loaded.'); - } else if (PROFESSIONAL_ROLE_SET.has(props.roleKey)) { - const prefix = ROLE_PREFIXES[props.roleKey]; + } else if (PROFESSIONAL_ROLE_SET.has(roleKey)) { + const prefix = ROLE_PREFIXES[roleKey]; const [marketRes, reqRes, walletRes] = await Promise.all([ apiFetch(`/api/${prefix}/marketplace?page=1&limit=100`), apiFetch(`/api/${prefix}/leads/requests/me?page=1&limit=100`), @@ -133,7 +149,9 @@ export default function MyDashboardPage(props: Props) { } }; - onMount(loadData); + onMount(() => { + setTimeout(() => loadData(), 0); + }); return (
diff --git a/src/components/dashboard/RoleDashboardShared.ts b/src/components/dashboard/RoleDashboardShared.ts index fbc0238..a41f896 100644 --- a/src/components/dashboard/RoleDashboardShared.ts +++ b/src/components/dashboard/RoleDashboardShared.ts @@ -42,3 +42,27 @@ export const PROFESSIONAL_ROLE_SET = new Set([ 'CATERING_SERVICES', ]); +const ALL_ROLE_KEYS: RoleKey[] = [ + 'COMPANY', + 'CUSTOMER', + 'JOB_SEEKER', + 'PHOTOGRAPHER', + 'MAKEUP_ARTIST', + 'TUTOR', + 'DEVELOPER', + 'VIDEO_EDITOR', + 'UGC_CONTENT_CREATOR', + 'GRAPHIC_DESIGNER', + 'SOCIAL_MEDIA_MANAGER', + 'FITNESS_TRAINER', + 'CATERING_SERVICES', +]; + +export function normalizeRole(value: string): RoleKey { + const up = String(value || '') + .trim() + .toUpperCase() + .replace(/\s+/g, '_'); + return (ALL_ROLE_KEYS.find((r) => r === up) || 'JOB_SEEKER') as RoleKey; +} + diff --git a/src/routes/dashboard.tsx b/src/routes/dashboard.tsx index e0cb289..d5b9655 100644 --- a/src/routes/dashboard.tsx +++ b/src/routes/dashboard.tsx @@ -437,11 +437,16 @@ function resolveRoleForDashboard(rawRole: string | null | undefined): RoleKey { return normalized; } -async function fetchJson(path: string): Promise { +async function fetchJson(path: string, includeAuth = false): Promise { try { const isServer = typeof window === "undefined"; const target = isServer ? `${SERVER_API_BASE}${path}` : `${API_GATEWAY}${path}`; - const res = await fetch(target, { credentials: "include" }); + const headers: Record = { credentials: "include" } as any; + if (includeAuth) { + const token = typeof getToken === "function" ? getToken() : null; + if (token) headers["Authorization"] = `Bearer ${token}`; + } + const res = await fetch(target, headers); if (!res.ok) return null; return await res.json(); } catch { @@ -449,12 +454,12 @@ async function fetchJson(path: string): Promise { } } -async function loadRoleBundle(role: RoleKey): Promise { - if (typeof window === "undefined") { - return null; - } - const runtime = await fetchJson("/api/runtime-config"); - if (runtime) { + async function loadRoleBundle(role: RoleKey): Promise { + if (typeof window === "undefined") { + return null; + } + const runtime = await fetchJson("/api/runtime-config", true); + if (!runtime) return null; const runtimeRole = normalizeRole(String(runtime?.role || runtime?.user?.active_role || role)); const runtimeRenderMode = String( runtime?.dashboard_config?.render_mode ?? runtime?.render_mode ?? "" @@ -494,47 +499,6 @@ async function loadRoleBundle(role: RoleKey): Promise { }; } - 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); - const configRenderMode = String((config as any)?.render_mode || "").toLowerCase(); - return { - role, - status: payload?.is_active === false ? "INACTIVE" : "ACTIVE", - sidebarItems, - tabs, - widgets, - fields, - source: "dashboard-config", - renderMode: configRenderMode === "preview" ? "preview" : undefined, - }; -} - function mergeSidebar( role: RoleKey, runtimeSidebar: string[],