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>
This commit is contained in:
parent
a8ad2b0620
commit
564a383a10
2 changed files with 310 additions and 18 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { For, Show, createEffect, createMemo, createSignal } from 'solid-js';
|
||||
import { For, Show, createEffect, createMemo, createResource, createSignal } from 'solid-js';
|
||||
import {
|
||||
Award,
|
||||
Bell,
|
||||
|
|
@ -539,6 +539,8 @@ function portfolioMediaConfig(roleKey: string): {
|
|||
|| normalized === 'GRAPHIC_DESIGNER'
|
||||
|| normalized === 'SOCIAL_MEDIA_MANAGER'
|
||||
|| normalized === 'CATERING_SERVICES'
|
||||
|| normalized === 'DEVELOPER'
|
||||
|| normalized === 'FITNESS_TRAINER'
|
||||
) {
|
||||
return {
|
||||
mode: 'visual',
|
||||
|
|
@ -576,6 +578,10 @@ function customerViewFor(sidebar: string, roleKey: string): CustomerView {
|
|||
return { title: 'Received Professional Responses', subtitle: 'Review and manage professional applications for active requirements.', tabs: ['all responses', 'new', 'shortlisted', 'rejected'] };
|
||||
}
|
||||
if (key === 'shortlisted responses') return { title: 'Shortlisted Responses', subtitle: 'Focus on high-intent responses and convert them to confirmed engagements.', tabs: ['all shortlisted', 'interview scheduled', 'finalized'] };
|
||||
if (key === 'applications') return { title: 'Applications', subtitle: 'Review all candidate applications received for your active job postings.', tabs: ['all applications', 'shortlisted', 'under review', 'rejected'], cta: 'View Job Postings' };
|
||||
if (key === 'shortlisted candidates') return { title: 'Shortlisted Candidates', subtitle: 'Manage candidates you have shortlisted across all job postings.', tabs: ['shortlisted', 'interview scheduled', 'offer extended'] };
|
||||
if (key === 'my applications') return { title: 'My Applications', subtitle: 'Track the status of all jobs you have applied to.', tabs: ['all', 'under review', 'shortlisted', 'rejected'], cta: 'Browse Jobs' };
|
||||
if (key === 'saved jobs') return { title: 'Saved Jobs', subtitle: 'Jobs you have bookmarked. Apply before they expire.', tabs: ['saved', 'expiring soon'], cta: 'Browse Jobs' };
|
||||
if (key.includes('profile')) {
|
||||
const spec = profileSpecForRole(roleKey);
|
||||
return { title: spec.title, subtitle: spec.subtitle, tabs: spec.tabs, cta: 'Save Changes' };
|
||||
|
|
@ -1046,6 +1052,7 @@ export default function DashboardDesignPreview(props: {
|
|||
exploreRoles?: Array<{ key: string; name: string }>;
|
||||
onOpenFullscreen?: () => void;
|
||||
hidePreviewHeader?: boolean;
|
||||
liveData?: { userName: string; userId: string; rolePrefix: string };
|
||||
}) {
|
||||
const isProfessionalRoleKey = (roleKey: string) => {
|
||||
const role = normalizeRoleKey(roleKey);
|
||||
|
|
@ -1129,6 +1136,9 @@ export default function DashboardDesignPreview(props: {
|
|||
const [ticketMessage, setTicketMessage] = createSignal('');
|
||||
const [createTicketFiles, setCreateTicketFiles] = createSignal<string[]>([]);
|
||||
const [viewTicketFiles, setViewTicketFiles] = createSignal<string[]>([]);
|
||||
const [profileFormData, setProfileFormData] = createSignal<Record<string, string>>({});
|
||||
const [profileSaving, setProfileSaving] = createSignal(false);
|
||||
const [profileSaveStatus, setProfileSaveStatus] = createSignal<'idle' | 'saved' | 'error'>('idle');
|
||||
const [profileSettingsTab, setProfileSettingsTab] = createSignal<'change_password' | 'notifications' | 'privacy'>('change_password');
|
||||
const [showDeleteAccountModal, setShowDeleteAccountModal] = createSignal(false);
|
||||
const [portfolioEditMode, setPortfolioEditMode] = createSignal(false);
|
||||
|
|
@ -1234,6 +1244,19 @@ export default function DashboardDesignPreview(props: {
|
|||
const submitRequirementForReview = () => {
|
||||
const roleKey = selectedRequirementRole();
|
||||
const roleLabel = titleCase(roleKey.replace(/_/g, ' ').toLowerCase());
|
||||
if (hasLive()) {
|
||||
apiPost('/api/customers/requirements', {
|
||||
profession_key: roleKey,
|
||||
title: `${roleLabel} Requirement`,
|
||||
description: 'Submitted via dashboard',
|
||||
location: 'Chennai',
|
||||
budget_min: 10000000,
|
||||
budget_max: 20000000,
|
||||
}).then((res: any) => res?.json?.().then((r: any) => {
|
||||
// After creating, submit for approval
|
||||
if (r?.id) apiPost(`/api/customers/requirements/${r.id}/submit`, {});
|
||||
})).then(() => refetchRequirementsLive());
|
||||
}
|
||||
const id = `#REQ-${Math.floor(9200 + Math.random() * 899)}`;
|
||||
const submission = new Date().toLocaleDateString('en-US', { month: 'short', day: '2-digit', year: 'numeric' });
|
||||
const newRequirement: RequirementRow = {
|
||||
|
|
@ -1294,6 +1317,189 @@ export default function DashboardDesignPreview(props: {
|
|||
if (state === 'REJECTED') return { border: '#E5E7EB', bg: '#F9FAFB', text: '#374151', label: 'Rejected' };
|
||||
return { border: '#E5E7EB', bg: '#F9FAFB', text: '#374151', label: 'Draft' };
|
||||
};
|
||||
|
||||
// ─── Live API integration (customer_external mode with liveData) ──────────
|
||||
const hasLive = () => isCustomerExternalMode() && !!props.liveData;
|
||||
const livePrefix = () => props.liveData?.rolePrefix ?? '';
|
||||
const GW = '/api/gateway';
|
||||
const apiFetch = (path: string) =>
|
||||
fetch(`${GW}${path}`, { credentials: 'include' })
|
||||
.then((r) => (r.ok ? r.json() : null))
|
||||
.catch(() => null);
|
||||
const apiPost = (path: string, body: unknown) =>
|
||||
fetch(`${GW}${path}`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
}).catch(() => null);
|
||||
const apiDelete = (path: string) =>
|
||||
fetch(`${GW}${path}`, { method: 'DELETE', credentials: 'include' }).catch(() => null);
|
||||
|
||||
// Credits balance
|
||||
const [creditsResource] = createResource(
|
||||
() => (hasLive() ? livePrefix() : null),
|
||||
(prefix) => apiFetch(`/api/${prefix}/wallet/balance`),
|
||||
);
|
||||
// Marketplace requirements (professionals)
|
||||
const [marketplaceResource] = createResource(
|
||||
() => (hasLive() && isProfessionalRole() ? livePrefix() : null),
|
||||
(prefix) => apiFetch(`/api/${prefix}/marketplace?limit=50`),
|
||||
);
|
||||
// My lead requests (professionals)
|
||||
const [leadRequestsResource, { refetch: refetchLeadRequestsLive }] = createResource(
|
||||
() => (hasLive() && isProfessionalRole() ? livePrefix() : null),
|
||||
(prefix) => apiFetch(`/api/${prefix}/leads/requests/me?limit=50`),
|
||||
);
|
||||
// Customer requirements
|
||||
const [requirementsResource, { refetch: refetchRequirementsLive }] = createResource(
|
||||
() => (hasLive() && normalizeRoleKey(props.roleKey ?? '') === 'CUSTOMER' ? 'yes' : null),
|
||||
() => apiFetch('/api/customers/requirements?limit=50'),
|
||||
);
|
||||
// Jobs board
|
||||
const [jobsResource] = createResource(
|
||||
() => {
|
||||
const r = normalizeRoleKey(props.roleKey ?? '');
|
||||
return hasLive() && (r === 'JOB_SEEKER' || r === 'COMPANY') ? r : null;
|
||||
},
|
||||
() => apiFetch('/api/jobs?limit=50'),
|
||||
);
|
||||
// User profile (all roles)
|
||||
const [profileResource] = createResource(
|
||||
() => (hasLive() ? livePrefix() : null),
|
||||
(prefix) => apiFetch(`/api/${prefix}/profile/me`),
|
||||
);
|
||||
|
||||
// Sync resources → local signals
|
||||
createEffect(() => {
|
||||
const d = creditsResource();
|
||||
if (d != null && typeof d.balance === 'number') setLeadCredits(d.balance);
|
||||
});
|
||||
createEffect(() => {
|
||||
const d = marketplaceResource();
|
||||
if (!d) return;
|
||||
const items: any[] = Array.isArray(d.items) ? d.items : Array.isArray(d) ? d : [];
|
||||
if (!items.length) return;
|
||||
setLeadCards(items.map((item: any) => ({
|
||||
id: String(item.id ?? item.requirement_id ?? ''),
|
||||
title: String(item.title ?? item.description ?? 'Requirement'),
|
||||
category: String(item.category ?? item.profession_key ?? props.roleKey ?? ''),
|
||||
location: String(item.location ?? item.city ?? 'India'),
|
||||
area: String(item.area ?? item.locality ?? ''),
|
||||
dateRequired: item.required_by
|
||||
? new Date(item.required_by).toLocaleDateString('en-US', { month: 'short', day: '2-digit', year: 'numeric' })
|
||||
: 'TBD',
|
||||
urgency: item.urgency === 'HIGH' ? 'High' : item.urgency === 'MEDIUM' ? 'Medium' : 'Low',
|
||||
budget: item.budget_min != null
|
||||
? `₹${Math.round(item.budget_min / 100).toLocaleString('en-IN')} - ₹${Math.round((item.budget_max ?? item.budget_min) / 100).toLocaleString('en-IN')}`
|
||||
: '₹0',
|
||||
budgetValue: Number(item.budget_max ?? item.budget_min ?? 0) / 100,
|
||||
priceRange: item.budget_min != null
|
||||
? `₹${Math.round(item.budget_min / 100).toLocaleString('en-IN')} - ₹${Math.round((item.budget_max ?? item.budget_min) / 100).toLocaleString('en-IN')}`
|
||||
: '₹0',
|
||||
cost: 25,
|
||||
status: 'open' as const,
|
||||
match: '80% match',
|
||||
contactCount: Number(item.contact_count ?? 0),
|
||||
maxContacts: Number(item.max_contacts ?? 10),
|
||||
})));
|
||||
});
|
||||
createEffect(() => {
|
||||
const d = leadRequestsResource();
|
||||
if (!d) return;
|
||||
const items: any[] = Array.isArray(d.items) ? d.items : Array.isArray(d) ? d : [];
|
||||
if (!items.length) return;
|
||||
const statusMap: Record<string, any> = {
|
||||
PENDING: 'request_sent', CONTACT_UNLOCKED: 'contact_unlocked',
|
||||
REJECTED: 'rejected', CANCELLED: 'cancelled_by_professional', EXPIRED: 'expired_refunded',
|
||||
};
|
||||
setLeadRequestRows(items.map((item: any) => ({
|
||||
id: String(item.id ?? ''),
|
||||
title: String(item.title ?? item.requirement_title ?? 'Lead Request'),
|
||||
city: String(item.location ?? item.city ?? 'India'),
|
||||
requestDate: item.created_at
|
||||
? new Date(item.created_at).toLocaleDateString('en-US', { month: 'short', day: '2-digit', year: 'numeric' })
|
||||
: '--',
|
||||
status: statusMap[String(item.status ?? '')] ?? 'request_sent',
|
||||
decisionDate: item.decision_date
|
||||
? new Date(item.decision_date).toLocaleDateString('en-US', { month: 'short', day: '2-digit', year: 'numeric' })
|
||||
: '--',
|
||||
})));
|
||||
});
|
||||
createEffect(() => {
|
||||
const d = requirementsResource();
|
||||
if (!d) return;
|
||||
const items: any[] = Array.isArray(d.items) ? d.items : Array.isArray(d) ? d : [];
|
||||
if (!items.length) return;
|
||||
const statusMap: Record<string, string> = {
|
||||
PENDING: 'under review', APPROVED: 'approved', ACTIVE: 'active',
|
||||
REJECTED: 'closed', CLOSED: 'closed',
|
||||
};
|
||||
setRequirementRows(items.map((item: any) => ({
|
||||
id: String(item.id ?? ''),
|
||||
title: String(item.title ?? item.description ?? 'Requirement'),
|
||||
summary: String(item.description ?? ''),
|
||||
category: String(item.category ?? item.profession_key ?? ''),
|
||||
budget: item.budget_min != null
|
||||
? `₹${Math.round(item.budget_min / 100).toLocaleString('en-IN')} - ₹${Math.round((item.budget_max ?? item.budget_min) / 100).toLocaleString('en-IN')}`
|
||||
: '₹0',
|
||||
location: String(item.location ?? 'India'),
|
||||
submission: item.created_at
|
||||
? new Date(item.created_at).toLocaleDateString('en-US', { month: 'short', day: '2-digit', year: 'numeric' })
|
||||
: '--',
|
||||
status: statusMap[String(item.status ?? '')] ?? 'under review',
|
||||
responseTag: `Active (${Number(item.response_count ?? 0)} Responses)`,
|
||||
})));
|
||||
});
|
||||
createEffect(() => {
|
||||
const d = jobsResource();
|
||||
if (!d) return;
|
||||
const items: any[] = Array.isArray(d.items) ? d.items : Array.isArray(d) ? d : [];
|
||||
if (!items.length) return;
|
||||
setJobBoardJobs(items.map((item: any) => ({
|
||||
id: String(item.id ?? ''),
|
||||
title: String(item.title ?? 'Position'),
|
||||
company: String(item.company_name ?? item.company ?? 'Company'),
|
||||
location: String(item.location ?? 'India'),
|
||||
salary: item.salary_min != null
|
||||
? `₹${Math.round(item.salary_min / 100).toLocaleString('en-IN')}+`
|
||||
: 'Negotiable',
|
||||
exp: String(item.experience_required ?? item.experience ?? '0-2 yrs'),
|
||||
type: String(item.employment_type ?? item.type ?? 'Full-Time'),
|
||||
tags: Array.isArray(item.tags) ? item.tags : [],
|
||||
match: '',
|
||||
posted: item.created_at
|
||||
? new Date(item.created_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })
|
||||
: '--',
|
||||
})));
|
||||
});
|
||||
createEffect(() => {
|
||||
const d = profileResource();
|
||||
if (!d) return;
|
||||
const parts = String(d.display_name || d.full_name || d.business_name || '').split(' ');
|
||||
const map: Record<string, string> = {};
|
||||
map['First Name'] = d.first_name || parts[0] || '';
|
||||
map['Last Name'] = d.last_name || parts.slice(1).join(' ') || '';
|
||||
map['Business Name'] = d.business_name || d.display_name || '';
|
||||
map['Contact Person Name'] = d.contact_person || d.display_name || '';
|
||||
map['Company Name'] = d.company_name || d.display_name || '';
|
||||
map['Email Address'] = d.email || '';
|
||||
map['Mobile Number'] = d.phone || d.mobile || '';
|
||||
if (d.location) map['City'] = d.location;
|
||||
if (d.area) map['Area'] = d.area;
|
||||
if (d.state) map['State'] = d.state;
|
||||
if (d.pin_code) map['PIN Code'] = d.pin_code;
|
||||
if (d.bio) map['Bio'] = d.bio;
|
||||
if (d.status) setProfileApprovalState(
|
||||
d.status === 'APPROVED' ? 'APPROVED'
|
||||
: d.status === 'REJECTED' ? 'REJECTED'
|
||||
: d.status === 'PENDING' ? 'IN_REVIEW'
|
||||
: 'DRAFT',
|
||||
);
|
||||
setProfileFormData((prev) => ({ ...prev, ...map }));
|
||||
});
|
||||
// ─── End live API integration ─────────────────────────────────────────────
|
||||
|
||||
const submitProfileForApproval = () => {
|
||||
setProfileApprovalState('SUBMITTED');
|
||||
setTimeout(() => setProfileApprovalState('IN_REVIEW'), 250);
|
||||
|
|
@ -1568,6 +1774,10 @@ export default function DashboardDesignPreview(props: {
|
|||
|
||||
const requestLeadContact = (leadId: string) => {
|
||||
if (usableLeadCredits() < leadCostPerContact) return;
|
||||
if (hasLive()) {
|
||||
apiPost(`/api/${livePrefix()}/leads/request`, { requirement_id: leadId })
|
||||
.then(() => refetchLeadRequestsLive());
|
||||
}
|
||||
let changed = false;
|
||||
setLeadCards((prev) => prev.map((card) => {
|
||||
if (card.id !== leadId || card.status !== 'open' || card.contactCount >= card.maxContacts) return card;
|
||||
|
|
@ -1643,6 +1853,10 @@ export default function DashboardDesignPreview(props: {
|
|||
};
|
||||
|
||||
const cancelLeadRequest = (leadId: string) => {
|
||||
if (hasLive()) {
|
||||
apiDelete(`/api/${livePrefix()}/leads/requests/${leadId}`)
|
||||
.then(() => refetchLeadRequestsLive());
|
||||
}
|
||||
setLeadCards((prev) => prev.map((card) => {
|
||||
if (card.id !== leadId) return card;
|
||||
if (card.status !== 'requested' && card.status !== 'closed') return card;
|
||||
|
|
@ -2613,7 +2827,7 @@ export default function DashboardDesignPreview(props: {
|
|||
</div>
|
||||
<div style="display:grid;grid-template-columns:2fr 1fr 1fr;gap:10px">
|
||||
<div style="border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)">
|
||||
<p style="margin:0;font-size:32px;font-weight:800;color:#111827;line-height:1.1">Welcome back, Alex</p>
|
||||
<p style="margin:0;font-size:32px;font-weight:800;color:#111827;line-height:1.1">Welcome back, {props.liveData?.userName ?? 'Alex'}</p>
|
||||
<p style="margin:6px 0 0;font-size:13px;color:#6B7280">To start receiving opportunities, complete My Profile and My Portfolio, then submit both for approval.</p>
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:10px">
|
||||
<button type="button" onClick={() => { props.onSidebarSelect('My Profile'); props.onTabSelect('basic information'); }} style="height:30px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 10px;font-size:11px;font-weight:700;color:#374151">Fill My Profile</button>
|
||||
|
|
@ -2629,7 +2843,7 @@ export default function DashboardDesignPreview(props: {
|
|||
</div>
|
||||
<div style="border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)">
|
||||
<p style="margin:0;font-size:11px;letter-spacing:0.04em;text-transform:uppercase;color:#6B7280">Credits Balance</p>
|
||||
<p style="margin:8px 0 0;font-size:34px;font-weight:800;line-height:1;color:#111827">2,500</p>
|
||||
<p style="margin:8px 0 0;font-size:34px;font-weight:800;line-height:1;color:#111827">{leadCredits().toLocaleString('en-IN')}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:10px">
|
||||
|
|
@ -2708,8 +2922,9 @@ export default function DashboardDesignPreview(props: {
|
|||
<div style="position:relative">
|
||||
<input
|
||||
type="text"
|
||||
value={isCityField ? 'Chennai' : ''}
|
||||
value={profileFormData()[field] ?? (isCityField ? 'Chennai' : '')}
|
||||
readOnly={isCityField}
|
||||
onInput={(e) => !isCityField && setProfileFormData((prev) => ({ ...prev, [field]: e.currentTarget.value }))}
|
||||
placeholder={placeholderText}
|
||||
style="width:100%;height:36px;border:1px solid #E5E7EB;border-radius:8px;background:white;padding:0 30px 0 10px;font-size:12px;color:#111827;outline:none"
|
||||
/>
|
||||
|
|
@ -2909,8 +3124,38 @@ export default function DashboardDesignPreview(props: {
|
|||
</div>
|
||||
</Show>
|
||||
<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:12px;padding-top:12px;border-top:1px solid #E5E7EB">
|
||||
<button type="button" style="height:34px;border-radius:8px;border:1px solid #D1D5DB;background:white;color:#374151;padding:0 14px;font-size:12px;font-weight:700">Cancel</button>
|
||||
<button type="button" style="height:34px;border-radius:8px;border:none;background:#03004E;color:white;padding:0 14px;font-size:12px;font-weight:700">Save Changes</button>
|
||||
<button type="button" onClick={() => setProfileFormData({})} style="height:34px;border-radius:8px;border:1px solid #D1D5DB;background:white;color:#374151;padding:0 14px;font-size:12px;font-weight:700">Cancel</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
if (!hasLive()) return;
|
||||
const data = profileFormData();
|
||||
setProfileSaving(true);
|
||||
const fn = data['First Name'] || '';
|
||||
const ln = data['Last Name'] || '';
|
||||
const display = data['Business Name'] || data['Company Name'] || data['Contact Person Name'] || [fn, ln].filter(Boolean).join(' ');
|
||||
fetch(`${GW}/api/${livePrefix()}/profile/me`, {
|
||||
method: 'PATCH',
|
||||
credentials: 'include',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
display_name: display || undefined,
|
||||
bio: data['Bio'] || undefined,
|
||||
location: data['City'] || undefined,
|
||||
area: data['Area'] || undefined,
|
||||
state: data['State'] || undefined,
|
||||
pin_code: data['PIN Code'] || undefined,
|
||||
}),
|
||||
}).then((r) => {
|
||||
setProfileSaving(false);
|
||||
setProfileSaveStatus(r.ok ? 'saved' : 'error');
|
||||
setTimeout(() => setProfileSaveStatus('idle'), 2500);
|
||||
}).catch(() => { setProfileSaving(false); setProfileSaveStatus('error'); });
|
||||
}}
|
||||
style={`height:34px;border-radius:8px;border:none;background:${profileSaveStatus() === 'error' ? '#DC2626' : profileSaveStatus() === 'saved' ? '#16A34A' : '#03004E'};color:white;padding:0 14px;font-size:12px;font-weight:700`}
|
||||
>
|
||||
{profileSaving() ? 'Saving…' : profileSaveStatus() === 'saved' ? 'Saved ✓' : profileSaveStatus() === 'error' ? 'Error — Retry' : 'Save Changes'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div style="border:1px solid #E5E7EB;background:white;border-radius:16px;padding:14px;box-shadow:0 1px 4px rgba(0,0,0,0.06)">
|
||||
|
|
|
|||
|
|
@ -58,9 +58,43 @@ const ROLE_BASED_SIDEBAR: Record<RoleKey, string[]> = {
|
|||
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', '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<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';
|
||||
}
|
||||
|
||||
const EXPLORE_ROLES = [
|
||||
{ key: 'PHOTOGRAPHER', name: 'Photographer' },
|
||||
{ key: 'MAKEUP_ARTIST', name: 'Makeup Artist' },
|
||||
|
|
@ -182,10 +216,25 @@ export default function RuntimeDashboardPage() {
|
|||
const [role, setRole] = createSignal<RoleKey>('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);
|
||||
setRole(getInitialRoleFromStorage());
|
||||
setUserName(getNameFromStorage());
|
||||
// Fetch fresh session data
|
||||
fetchJson('/api/auth/session').then((data) => {
|
||||
if (!data) return;
|
||||
const name = data.full_name
|
||||
|| data.name
|
||||
|| (data.first_name ? `${data.first_name} ${data.last_name ?? ''}`.trim() : '')
|
||||
|| data.email?.split('@')[0]
|
||||
|| '';
|
||||
if (name) setUserName(name);
|
||||
if (data.id || data.user_id) setUserId(String(data.id || data.user_id));
|
||||
if (data.active_role) setRole(normalizeRole(data.active_role));
|
||||
});
|
||||
});
|
||||
|
||||
const [bundle] = createResource(
|
||||
|
|
@ -216,6 +265,12 @@ export default function RuntimeDashboardPage() {
|
|||
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 };
|
||||
});
|
||||
|
||||
return (
|
||||
<main style={{ 'min-height': '100vh', background: '#f3f4f6' }}>
|
||||
<div>
|
||||
|
|
@ -224,18 +279,9 @@ export default function RuntimeDashboardPage() {
|
|||
<div style={cardStyle}>Loading dashboard...</div>
|
||||
</Show>
|
||||
|
||||
<Show when={ready() && !bundle()}>
|
||||
<div style={cardStyle}>
|
||||
<h2 style={{ margin: 0, 'font-size': '18px' }}>Dashboard Unavailable</h2>
|
||||
<p style={{ margin: '8px 0 0', color: '#6B7280' }}>
|
||||
Dashboard configuration was not found for your role. Please contact support.
|
||||
</p>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={ready() && bundle()}>
|
||||
<Show when={ready()}>
|
||||
<DashboardDesignPreview
|
||||
status={bundle()!.status}
|
||||
status={bundle()?.status ?? 'ACTIVE'}
|
||||
sidebarItems={sidebarItems()}
|
||||
activeSidebar={activeSidebar()}
|
||||
onSidebarSelect={setActiveSidebar}
|
||||
|
|
@ -248,6 +294,7 @@ export default function RuntimeDashboardPage() {
|
|||
roleKey={role()}
|
||||
exploreRoles={EXPLORE_ROLES}
|
||||
hidePreviewHeader
|
||||
liveData={liveData()}
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue