2026-04-06 17:20:48 +02:00
|
|
|
import { Match, Show, Switch, createEffect, createMemo, createResource, createSignal, onMount } from 'solid-js';
|
2026-04-06 06:19:23 +02:00
|
|
|
import { useNavigate } from '@solidjs/router';
|
|
|
|
|
import { useAuth, RequireAuth } from '~/lib/auth';
|
2026-04-05 16:52:02 +02:00
|
|
|
import DashboardDesignPreview from '~/components/admin/DashboardDesignPreview';
|
2026-04-06 17:20:48 +02:00
|
|
|
import DashboardShell from '~/components/DashboardShell';
|
|
|
|
|
import ProfilePage from '~/components/dashboard/ProfilePage';
|
|
|
|
|
import PortfolioPage from '~/components/dashboard/PortfolioPage';
|
|
|
|
|
import VerificationStatusPage from '~/components/dashboard/VerificationStatusPage';
|
2026-04-08 22:40:43 +02:00
|
|
|
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';
|
2026-04-06 17:20:48 +02:00
|
|
|
|
|
|
|
|
// Sidebar items that have real implementations (not the preview mock)
|
2026-04-08 22:40:43 +02:00
|
|
|
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'];
|
2026-03-17 20:42:55 +01:00
|
|
|
|
2026-04-05 16:52:02 +02:00
|
|
|
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[];
|
2026-04-08 22:40:43 +02:00
|
|
|
verificationStatus?: string;
|
2026-04-05 16:52:02 +02:00
|
|
|
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<RoleKey, string[]> = {
|
|
|
|
|
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'],
|
Wire user dashboard to real APIs — profile, credits, leads, requirements, jobs
- dashboard.tsx: fetch session for real user name/ID/role, fallback to
localStorage; show dashboard with role defaults when runtime config unavailable
- DashboardDesignPreview: add liveData prop; createResource for credits,
marketplace, lead requests, customer requirements, jobs, and profile
- Profile form: inputs now track state via profileFormData signal; pre-filled
from GET /api/${prefix}/profile/me; Save Changes PATCHes real endpoint
- Lead actions: Send Request POSTs to /api/${prefix}/leads/request; Cancel
DELETEs /api/${prefix}/leads/requests/{id}; both refetch after completion
- Requirement submit: POSTs to /api/customers/requirements then submits for approval
- Replace hardcoded "Alex" with real session name; credits from wallet balance API
- Fix launch.json PATH so npm is found in sh shell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 21:13:54 +02:00
|
|
|
CUSTOMER: ['My Dashboard', 'My Profile', 'My Requirements', 'Received Responses', 'Shortlisted Responses', 'Credits', 'Explore Nxtgauge', 'Verification', 'Help Center', 'Settings', 'Switch Services', 'Logout'],
|
2026-04-05 16:52:02 +02:00
|
|
|
};
|
|
|
|
|
|
Wire user dashboard to real APIs — profile, credits, leads, requirements, jobs
- dashboard.tsx: fetch session for real user name/ID/role, fallback to
localStorage; show dashboard with role defaults when runtime config unavailable
- DashboardDesignPreview: add liveData prop; createResource for credits,
marketplace, lead requests, customer requirements, jobs, and profile
- Profile form: inputs now track state via profileFormData signal; pre-filled
from GET /api/${prefix}/profile/me; Save Changes PATCHes real endpoint
- Lead actions: Send Request POSTs to /api/${prefix}/leads/request; Cancel
DELETEs /api/${prefix}/leads/requests/{id}; both refetch after completion
- Requirement submit: POSTs to /api/customers/requirements then submits for approval
- Replace hardcoded "Alex" with real session name; credits from wallet balance API
- Fix launch.json PATH so npm is found in sh shell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 21:13:54 +02:00
|
|
|
const ROLE_PREFIXES: Record<string, string> = {
|
|
|
|
|
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';
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 16:52:02 +02:00
|
|
|
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<any | null> {
|
|
|
|
|
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<RuntimeBundle | null> {
|
|
|
|
|
if (typeof window === 'undefined') {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2026-04-08 22:40:43 +02:00
|
|
|
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',
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 16:52:02 +02:00
|
|
|
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<string, unknown> | 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',
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 22:40:43 +02:00
|
|
|
function mergeSidebar(role: RoleKey, runtimeSidebar: string[], verificationStatus?: string): string[] {
|
2026-04-05 16:52:02 +02:00
|
|
|
const base = ROLE_BASED_SIDEBAR[role] || ['My Dashboard', 'My Profile', 'Switch Services', 'Logout'];
|
|
|
|
|
const fromRuntime = runtimeSidebar.filter(Boolean);
|
|
|
|
|
const map = new Map<string, string>();
|
|
|
|
|
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');
|
|
|
|
|
}
|
2026-04-08 22:40:43 +02:00
|
|
|
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()));
|
|
|
|
|
}
|
2026-04-05 16:52:02 +02:00
|
|
|
return merged;
|
2026-03-17 20:42:55 +01:00
|
|
|
}
|
2026-04-05 16:52:02 +02:00
|
|
|
|
|
|
|
|
export default function RuntimeDashboardPage() {
|
2026-04-06 06:19:23 +02:00
|
|
|
const navigate = useNavigate();
|
|
|
|
|
const auth = useAuth();
|
2026-04-05 16:52:02 +02:00
|
|
|
const [hydrated, setHydrated] = createSignal(false);
|
|
|
|
|
const [role, setRole] = createSignal<RoleKey>('JOB_SEEKER');
|
|
|
|
|
const [activeSidebar, setActiveSidebar] = createSignal('My Dashboard');
|
|
|
|
|
const [activeTab, setActiveTab] = createSignal('overview');
|
Wire user dashboard to real APIs — profile, credits, leads, requirements, jobs
- dashboard.tsx: fetch session for real user name/ID/role, fallback to
localStorage; show dashboard with role defaults when runtime config unavailable
- DashboardDesignPreview: add liveData prop; createResource for credits,
marketplace, lead requests, customer requirements, jobs, and profile
- Profile form: inputs now track state via profileFormData signal; pre-filled
from GET /api/${prefix}/profile/me; Save Changes PATCHes real endpoint
- Lead actions: Send Request POSTs to /api/${prefix}/leads/request; Cancel
DELETEs /api/${prefix}/leads/requests/{id}; both refetch after completion
- Requirement submit: POSTs to /api/customers/requirements then submits for approval
- Replace hardcoded "Alex" with real session name; credits from wallet balance API
- Fix launch.json PATH so npm is found in sh shell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 21:13:54 +02:00
|
|
|
const [userName, setUserName] = createSignal('User');
|
|
|
|
|
const [userId, setUserId] = createSignal('');
|
2026-04-05 16:52:02 +02:00
|
|
|
|
|
|
|
|
onMount(() => {
|
|
|
|
|
setHydrated(true);
|
2026-04-06 06:19:23 +02:00
|
|
|
const storedRole = getInitialRoleFromStorage();
|
|
|
|
|
setRole(storedRole);
|
Wire user dashboard to real APIs — profile, credits, leads, requirements, jobs
- dashboard.tsx: fetch session for real user name/ID/role, fallback to
localStorage; show dashboard with role defaults when runtime config unavailable
- DashboardDesignPreview: add liveData prop; createResource for credits,
marketplace, lead requests, customer requirements, jobs, and profile
- Profile form: inputs now track state via profileFormData signal; pre-filled
from GET /api/${prefix}/profile/me; Save Changes PATCHes real endpoint
- Lead actions: Send Request POSTs to /api/${prefix}/leads/request; Cancel
DELETEs /api/${prefix}/leads/requests/{id}; both refetch after completion
- Requirement submit: POSTs to /api/customers/requirements then submits for approval
- Replace hardcoded "Alex" with real session name; credits from wallet balance API
- Fix launch.json PATH so npm is found in sh shell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 21:13:54 +02:00
|
|
|
setUserName(getNameFromStorage());
|
2026-04-06 06:19:23 +02:00
|
|
|
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));
|
|
|
|
|
}
|
2026-04-05 16:52:02 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const [bundle] = createResource(
|
|
|
|
|
() => role(),
|
|
|
|
|
loadRoleBundle,
|
|
|
|
|
);
|
|
|
|
|
|
2026-04-08 22:40:43 +02:00
|
|
|
const sidebarItems = createMemo(() => mergeSidebar(role(), bundle()?.sidebarItems || [], bundle()?.verificationStatus));
|
|
|
|
|
|
|
|
|
|
createEffect(() => {
|
|
|
|
|
const runtimeRole = bundle()?.role;
|
|
|
|
|
if (runtimeRole && runtimeRole !== role()) setRole(runtimeRole);
|
|
|
|
|
});
|
2026-04-05 16:52:02 +02:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
Wire user dashboard to real APIs — profile, credits, leads, requirements, jobs
- dashboard.tsx: fetch session for real user name/ID/role, fallback to
localStorage; show dashboard with role defaults when runtime config unavailable
- DashboardDesignPreview: add liveData prop; createResource for credits,
marketplace, lead requests, customer requirements, jobs, and profile
- Profile form: inputs now track state via profileFormData signal; pre-filled
from GET /api/${prefix}/profile/me; Save Changes PATCHes real endpoint
- Lead actions: Send Request POSTs to /api/${prefix}/leads/request; Cancel
DELETEs /api/${prefix}/leads/requests/{id}; both refetch after completion
- Requirement submit: POSTs to /api/customers/requirements then submits for approval
- Replace hardcoded "Alex" with real session name; credits from wallet balance API
- Fix launch.json PATH so npm is found in sh shell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 21:13:54 +02:00
|
|
|
const liveData = createMemo(() => {
|
|
|
|
|
const prefix = ROLE_PREFIXES[role()];
|
|
|
|
|
if (!prefix) return undefined;
|
|
|
|
|
return { userName: userName(), userId: userId(), rolePrefix: prefix };
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-08 22:40:43 +02:00
|
|
|
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;
|
|
|
|
|
});
|
2026-04-06 17:20:48 +02:00
|
|
|
|
2026-04-05 16:52:02 +02:00
|
|
|
return (
|
2026-04-06 06:19:23 +02:00
|
|
|
<RequireAuth>
|
2026-04-06 17:20:48 +02:00
|
|
|
<main style={{ 'min-height': '100vh', background: '#F3F4F6' }}>
|
|
|
|
|
|
|
|
|
|
<Show when={loading()}>
|
|
|
|
|
<div style={cardStyle}>Loading dashboard…</div>
|
|
|
|
|
</Show>
|
|
|
|
|
|
|
|
|
|
<Show when={ready()}>
|
2026-04-05 16:52:02 +02:00
|
|
|
|
2026-04-06 17:20:48 +02:00
|
|
|
{/* ── Real pages: DashboardShell + actual components ── */}
|
|
|
|
|
<Show when={isRealPage()}>
|
|
|
|
|
<DashboardShell
|
|
|
|
|
sidebarItems={sidebarItems()}
|
|
|
|
|
activeSidebar={activeSidebar()}
|
|
|
|
|
onSidebarSelect={setActiveSidebar}
|
|
|
|
|
roleKey={role()}
|
|
|
|
|
userName={userName()}
|
|
|
|
|
>
|
|
|
|
|
<Switch>
|
2026-04-08 22:40:43 +02:00
|
|
|
<Match when={activeSidebar().toLowerCase() === 'my dashboard'}>
|
|
|
|
|
<MyDashboardPage roleKey={role()} userName={userName()} />
|
|
|
|
|
</Match>
|
2026-04-06 17:20:48 +02:00
|
|
|
<Match when={activeSidebar().toLowerCase() === 'my profile'}>
|
|
|
|
|
<ProfilePage roleKey={role()} />
|
|
|
|
|
</Match>
|
|
|
|
|
<Match when={activeSidebar().toLowerCase() === 'my portfolio'}>
|
|
|
|
|
<PortfolioPage roleKey={role()} />
|
|
|
|
|
</Match>
|
|
|
|
|
<Match when={activeSidebar().toLowerCase() === 'verification'}>
|
|
|
|
|
<VerificationStatusPage
|
|
|
|
|
roleKey={role()}
|
|
|
|
|
onNavigate={setActiveSidebar}
|
|
|
|
|
/>
|
|
|
|
|
</Match>
|
2026-04-08 22:40:43 +02:00
|
|
|
<Match when={activeSidebar().toLowerCase() === 'settings'}>
|
|
|
|
|
<SettingsPage />
|
|
|
|
|
</Match>
|
|
|
|
|
<Match when={activeSidebar().toLowerCase() === 'credits'}>
|
|
|
|
|
<CreditsPage roleKey={role()} />
|
|
|
|
|
</Match>
|
|
|
|
|
<Match when={activeSidebar().toLowerCase() === 'explore nxtgauge'}>
|
|
|
|
|
<ExploreServicesPage />
|
|
|
|
|
</Match>
|
|
|
|
|
<Match when={activeSidebar().toLowerCase() === 'help center'}>
|
|
|
|
|
<HelpCenterDashboardPage roleKey={role()} />
|
|
|
|
|
</Match>
|
|
|
|
|
<Match when={activeSidebar().toLowerCase() === 'switch services'}>
|
|
|
|
|
<SwitchServicesPage />
|
|
|
|
|
</Match>
|
|
|
|
|
<Match when={activeSidebar().toLowerCase() === 'logout'}>
|
|
|
|
|
<LogoutPage />
|
|
|
|
|
</Match>
|
|
|
|
|
<Match when={role() === 'COMPANY' && activeSidebar().toLowerCase() === 'jobs'}>
|
|
|
|
|
<CompanyJobsPage />
|
|
|
|
|
</Match>
|
|
|
|
|
<Match when={role() === 'COMPANY' && activeSidebar().toLowerCase() === 'applications'}>
|
|
|
|
|
<CompanyApplicationsPage />
|
|
|
|
|
</Match>
|
|
|
|
|
<Match when={role() === 'COMPANY' && activeSidebar().toLowerCase() === 'shortlisted candidates'}>
|
|
|
|
|
<CompanyShortlistedCandidatesPage />
|
|
|
|
|
</Match>
|
|
|
|
|
<Match when={role() === 'CUSTOMER' && activeSidebar().toLowerCase() === 'my requirements'}>
|
|
|
|
|
<CustomerRequirementsPage />
|
|
|
|
|
</Match>
|
|
|
|
|
<Match when={role() === 'CUSTOMER' && activeSidebar().toLowerCase() === 'received responses'}>
|
|
|
|
|
<CustomerResponsesPage mode="received" />
|
|
|
|
|
</Match>
|
|
|
|
|
<Match when={role() === 'CUSTOMER' && activeSidebar().toLowerCase() === 'shortlisted responses'}>
|
|
|
|
|
<CustomerResponsesPage mode="shortlisted" />
|
|
|
|
|
</Match>
|
|
|
|
|
<Match when={role() === 'JOB_SEEKER' && activeSidebar().toLowerCase() === 'my applications'}>
|
|
|
|
|
<JobSeekerApplicationsPage />
|
|
|
|
|
</Match>
|
|
|
|
|
<Match when={role() === 'JOB_SEEKER' && activeSidebar().toLowerCase() === 'jobs'}>
|
|
|
|
|
<JobSeekerJobsPage />
|
|
|
|
|
</Match>
|
|
|
|
|
<Match when={role() === 'JOB_SEEKER' && activeSidebar().toLowerCase() === 'saved jobs'}>
|
|
|
|
|
<JobSeekerSavedJobsPage />
|
|
|
|
|
</Match>
|
|
|
|
|
<Match when={PROFESSIONAL_ROLE_SET.has(role()) && activeSidebar().toLowerCase() === 'leads'}>
|
|
|
|
|
<ProfessionalLeadsPage roleKey={role()} />
|
|
|
|
|
</Match>
|
|
|
|
|
<Match when={PROFESSIONAL_ROLE_SET.has(role()) && activeSidebar().toLowerCase() === 'my responses'}>
|
|
|
|
|
<ProfessionalResponsesPage roleKey={role()} />
|
|
|
|
|
</Match>
|
2026-04-06 17:20:48 +02:00
|
|
|
</Switch>
|
|
|
|
|
</DashboardShell>
|
2026-04-06 06:19:23 +02:00
|
|
|
</Show>
|
2026-04-05 16:52:02 +02:00
|
|
|
|
2026-04-06 17:20:48 +02:00
|
|
|
{/* ── All other views: DashboardDesignPreview mock ── */}
|
|
|
|
|
<Show when={!isRealPage()}>
|
2026-04-06 06:19:23 +02:00
|
|
|
<DashboardDesignPreview
|
|
|
|
|
status={bundle()?.status ?? 'ACTIVE'}
|
|
|
|
|
sidebarItems={sidebarItems()}
|
|
|
|
|
activeSidebar={activeSidebar()}
|
|
|
|
|
onSidebarSelect={setActiveSidebar}
|
|
|
|
|
tabs={tabs()}
|
|
|
|
|
activeTab={activeTab()}
|
|
|
|
|
onTabSelect={setActiveTab}
|
|
|
|
|
widgets={bundle()?.widgets || []}
|
|
|
|
|
fields={bundle()?.fields || []}
|
|
|
|
|
mode="customer_external"
|
|
|
|
|
roleKey={role()}
|
|
|
|
|
exploreRoles={EXPLORE_ROLES}
|
|
|
|
|
hidePreviewHeader
|
|
|
|
|
liveData={liveData()}
|
|
|
|
|
/>
|
|
|
|
|
</Show>
|
2026-04-06 17:20:48 +02:00
|
|
|
|
|
|
|
|
</Show>
|
2026-04-06 06:19:23 +02:00
|
|
|
</main>
|
|
|
|
|
</RequireAuth>
|
2026-04-05 16:52:02 +02:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const cardStyle = {
|
|
|
|
|
border: '1px solid #E5E7EB',
|
|
|
|
|
'border-radius': '12px',
|
|
|
|
|
padding: '16px',
|
|
|
|
|
background: '#fff',
|
|
|
|
|
color: '#111827',
|
|
|
|
|
} as const;
|