fix: persist verification docs and unlock dashboard
All checks were successful
build-and-release / build (push) Successful in 1m45s

This commit is contained in:
Tracewebstudio Dev 2026-07-29 15:14:37 +02:00
parent 012a6a6072
commit ddc6a95771
4 changed files with 41 additions and 7 deletions

View file

@ -251,7 +251,7 @@ export default function MyDashboardPage(props: Props) {
description: 'Fill profile and portfolio sections, upload required documents, then submit for verification.', description: 'Fill profile and portfolio sections, upload required documents, then submit for verification.',
}; };
}); });
const showVerificationPrompt = createMemo(() => verificationStatus() !== 'APPROVED'); const showVerificationPrompt = createMemo(() => !['APPROVED', 'COMPLETED'].includes(verificationStatus()));
const showPortfolioCta = createMemo(() => PROFESSIONAL_ROLE_SET.has(props.roleKey) || props.roleKey === 'JOB_SEEKER'); const showPortfolioCta = createMemo(() => PROFESSIONAL_ROLE_SET.has(props.roleKey) || props.roleKey === 'JOB_SEEKER');
const getEffectiveRole = (): RoleKey => { const getEffectiveRole = (): RoleKey => {
@ -489,7 +489,7 @@ export default function MyDashboardPage(props: Props) {
statusLabel={verificationStatus().replace(/_/g, ' ')} statusLabel={verificationStatus().replace(/_/g, ' ')}
statusColor="#B7791F" statusColor="#B7791F"
locked={verificationStatus() === 'UNDER_REVIEW' || verificationStatus() === 'PENDING'} locked={verificationStatus() === 'UNDER_REVIEW' || verificationStatus() === 'PENDING'}
approved={verificationStatus() === 'APPROVED'} approved={['APPROVED', 'COMPLETED'].includes(verificationStatus())}
missingBasicLabels={missingBasicLabels()} missingBasicLabels={missingBasicLabels()}
missingDocLabels={missingDocLabels()} missingDocLabels={missingDocLabels()}
missingPortfolioLabels={missingPortfolioLabels()} missingPortfolioLabels={missingPortfolioLabels()}

View file

@ -716,6 +716,27 @@ export default function ProfilePage(props: Props) {
} }
} }
if (props.roleKey === "JOB_SEEKER") {
try {
const docsRes = await apiFetch("/api/jobseeker/profile/documents");
if (docsRes.ok) {
const docsPayload = await docsRes.json().catch(() => ({}));
const docs = Array.isArray(docsPayload?.data) ? docsPayload.data : [];
const savedDocs: Record<string, string> = {};
for (const doc of docs) {
const key = String(doc?.document_type || "").trim();
const url = String(doc?.file_url || "").trim();
if (key && url) savedDocs[key] = url;
}
if (Object.keys(savedDocs).length > 0) {
setDocUrls((prev) => ({ ...savedDocs, ...prev }));
}
}
} catch {
// Best-effort rehydration only.
}
}
if (statusRes.ok) { if (statusRes.ok) {
const s = await statusRes.json(); const s = await statusRes.json();
const nextStatus = String(s.status ?? "NOT_SUBMITTED"); const nextStatus = String(s.status ?? "NOT_SUBMITTED");
@ -1262,6 +1283,10 @@ export default function ProfilePage(props: Props) {
const url = result?.url ?? result?.file_url ?? result?.path ?? String(result); const url = result?.url ?? result?.file_url ?? result?.path ?? String(result);
setDocUrls(prev => ({ ...prev, [doc.key]: url })); setDocUrls(prev => ({ ...prev, [doc.key]: url }));
setDocUploadErrors(prev => ({ ...prev, [doc.key]: "" })); setDocUploadErrors(prev => ({ ...prev, [doc.key]: "" }));
await request("/api/profile", {
method: "PATCH",
body: { roleKey: props.roleKey, profile_data: { ...form(), ...docUrls(), [doc.key]: url } },
});
} catch (err: any) { } catch (err: any) {
setDocUploadErrors(prev => ({ ...prev, [doc.key]: err.message ?? "Upload failed" })); setDocUploadErrors(prev => ({ ...prev, [doc.key]: err.message ?? "Upload failed" }));
} }

View file

@ -92,6 +92,14 @@ const STATUS_CONFIG: Record<string, {
border: '#6EE7B7', border: '#6EE7B7',
description: 'Your profile has been verified and approved. You now have full access to the platform.', description: 'Your profile has been verified and approved. You now have full access to the platform.',
}, },
COMPLETED: {
emoji: '✅',
label: 'Approved',
color: '#065F46',
bg: '#ECFDF5',
border: '#6EE7B7',
description: 'Your profile has been verified and approved. You now have full access to the platform.',
},
REJECTED: { REJECTED: {
emoji: '❌', emoji: '❌',
label: 'Rejected', label: 'Rejected',
@ -116,6 +124,7 @@ function stepIndex(status: string): number {
case 'UNDER_REVIEW': return 2; case 'UNDER_REVIEW': return 2;
case 'DOCUMENTS_REQUESTED': case 'DOCUMENTS_REQUESTED':
case 'REVISION_REQUESTED': return 2; case 'REVISION_REQUESTED': return 2;
case 'COMPLETED':
case 'APPROVED': return 4; case 'APPROVED': return 4;
default: return 1; default: return 1;
} }
@ -353,7 +362,7 @@ export default function VerificationStatusPage(props: Props) {
</Show> </Show>
{/* Progress timeline */} {/* Progress timeline */}
<Show when={status() !== 'APPROVED'}> <Show when={!['APPROVED', 'COMPLETED'].includes(status())}>
<div style={{ ...CARD, 'margin-bottom': '16px' }}> <div style={{ ...CARD, 'margin-bottom': '16px' }}>
<p style={{ margin: '0 0 14px', 'font-size': '14px', 'font-weight': '700', color: '#111827' }}> <p style={{ margin: '0 0 14px', 'font-size': '14px', 'font-weight': '700', color: '#111827' }}>
Verification Progress Verification Progress
@ -422,7 +431,7 @@ export default function VerificationStatusPage(props: Props) {
</p> </p>
</Show> </Show>
<Show when={status() === 'APPROVED'}> <Show when={['APPROVED', 'COMPLETED'].includes(status())}>
<div style={{ ...CARD, background: '#ECFDF5', border: '1px solid #6EE7B7', 'text-align': 'center', padding: '32px' }}> <div style={{ ...CARD, background: '#ECFDF5', border: '1px solid #6EE7B7', 'text-align': 'center', padding: '32px' }}>
<p style={{ margin: '0', 'font-size': '48px' }}>🎉</p> <p style={{ margin: '0', 'font-size': '48px' }}>🎉</p>
<p style={{ margin: '12px 0 4px', 'font-size': '20px', 'font-weight': '800', color: '#065F46' }}> <p style={{ margin: '12px 0 4px', 'font-size': '20px', 'font-weight': '800', color: '#065F46' }}>
@ -539,7 +548,7 @@ export default function VerificationStatusPage(props: Props) {
</div> </div>
</div> </div>
</Show> </Show>
<Show when={status() === 'APPROVED'}> <Show when={['APPROVED', 'COMPLETED'].includes(status())}>
<div style={{ display: 'flex', gap: '12px', 'align-items': 'flex-start' }}> <div style={{ display: 'flex', gap: '12px', 'align-items': 'flex-start' }}>
<div style={{ width: '8px', height: '8px', 'border-radius': '50%', background: '#10B981', 'margin-top': '6px', 'flex-shrink': '0' }} /> <div style={{ width: '8px', height: '8px', 'border-radius': '50%', background: '#10B981', 'margin-top': '6px', 'flex-shrink': '0' }} />
<div> <div>

View file

@ -580,7 +580,7 @@ function mergeSidebar(
} }
const status = String(verificationStatus || "").toUpperCase(); const status = String(verificationStatus || "").toUpperCase();
const approved = status === "APPROVED"; const approved = status === "APPROVED" || status === "COMPLETED";
if (!approved && status) { if (!approved && status) {
// Once a role's verification wizard is enabled (admin-configurable, see // Once a role's verification wizard is enabled (admin-configurable, see
// OnboardingSchemaEditor), My Profile / My Portfolio move inside the // OnboardingSchemaEditor), My Profile / My Portfolio move inside the
@ -852,7 +852,7 @@ export default function RuntimeDashboardPage() {
roleKey={role()} roleKey={role()}
userName={userName()} userName={userName()}
isAdmin={isAdmin()} isAdmin={isAdmin()}
verificationApproved={String(effectiveVerificationStatus() || '').toUpperCase() === 'APPROVED'} verificationApproved={["APPROVED", "COMPLETED"].includes(String(effectiveVerificationStatus() || '').toUpperCase())}
> >
<Switch> <Switch>
<Match when={activeSidebarKey() === "my dashboard"}> <Match when={activeSidebarKey() === "my dashboard"}>