From 142e185fc6a6ac50c56ce347d2a9f32323d84eaf Mon Sep 17 00:00:00 2001 From: Ashwin Kumar Sivakumar Date: Sun, 19 Jul 2026 16:35:30 +0530 Subject: [PATCH] feat: render human-readable reference numbers and show submitted documents Replaces raw/truncated UUID labels with the backend's new reference_number field across verification, application, and lead request views. Verification status page now renders actual uploaded documents (previously invisible after submission since the API never returned them). Co-Authored-By: Claude Sonnet 5 --- .../dashboard/CompanyApplicationsPage.tsx | 3 +- .../CompanyShortlistedCandidatesPage.tsx | 4 + .../dashboard/JobSeekerApplicationsPage.tsx | 3 +- .../dashboard/ProfessionalResponsesPage.tsx | 3 +- .../dashboard/VerificationStatusPage.tsx | 86 ++++++++++++++++--- 5 files changed, 86 insertions(+), 13 deletions(-) diff --git a/src/components/dashboard/CompanyApplicationsPage.tsx b/src/components/dashboard/CompanyApplicationsPage.tsx index 8a10678..478525e 100644 --- a/src/components/dashboard/CompanyApplicationsPage.tsx +++ b/src/components/dashboard/CompanyApplicationsPage.tsx @@ -25,6 +25,7 @@ interface ApplicationItem { cover_note?: string | null; resume_url?: string | null; applied_at?: string; + reference_number: string; // Profile snapshot — contact fields excluded by backend applicant_name?: string | null; avatar_url?: string | null; @@ -304,7 +305,7 @@ export default function CompanyApplicationsPage() {

- {app.applicant_name || `Applicant #${app.id.slice(0, 8)}`} + {app.applicant_name || app.reference_number}

{app.headline}

diff --git a/src/components/dashboard/CompanyShortlistedCandidatesPage.tsx b/src/components/dashboard/CompanyShortlistedCandidatesPage.tsx index 92e78c1..e8f978f 100644 --- a/src/components/dashboard/CompanyShortlistedCandidatesPage.tsx +++ b/src/components/dashboard/CompanyShortlistedCandidatesPage.tsx @@ -93,6 +93,10 @@ export default function CompanyShortlistedCandidatesPage() {
+ {/* This label identifies the candidate (job_seeker_id), not the application + record, so it is not a candidate for the new application reference_number + field. Leaving the truncated id as-is pending backend confirmation of + whether a human-readable candidate identifier should be shown instead. */}

Candidate #{row.job_seeker_id?.slice(0, 8) || row.id.slice(0, 8)}

diff --git a/src/components/dashboard/JobSeekerApplicationsPage.tsx b/src/components/dashboard/JobSeekerApplicationsPage.tsx index 09a4d3a..7dfc43c 100644 --- a/src/components/dashboard/JobSeekerApplicationsPage.tsx +++ b/src/components/dashboard/JobSeekerApplicationsPage.tsx @@ -10,6 +10,7 @@ type ApplicationItem = { applied_at?: string; resume_url?: string | null; cover_letter?: string | null; + reference_number: string; }; async function apiFetch(path: string, opts?: RequestInit) { @@ -138,7 +139,7 @@ export default function JobSeekerApplicationsPage() {
-

Application #{row.id.slice(0, 8)}

+

{row.reference_number}

Job: {row.job_id?.slice(0, 8) || '—'} {row.applied_at ? `• ${new Date(row.applied_at).toLocaleString('en-IN')}` : ''}

diff --git a/src/components/dashboard/ProfessionalResponsesPage.tsx b/src/components/dashboard/ProfessionalResponsesPage.tsx index 450a148..9149a44 100644 --- a/src/components/dashboard/ProfessionalResponsesPage.tsx +++ b/src/components/dashboard/ProfessionalResponsesPage.tsx @@ -13,6 +13,7 @@ type LeadRequestItem = { requested_at?: string; expires_at?: string; decision_at?: string; + reference_number: string; }; async function apiFetch(path: string, opts?: RequestInit) { @@ -91,7 +92,7 @@ export default function ProfessionalResponsesPage(props: Props) {
-

Lead Request #{row.id.slice(0, 8)}

+

{row.reference_number}

Requirement: {row.requirement_id?.slice(0, 8) || '—'} {row.requested_at ? `• ${new Date(row.requested_at).toLocaleString('en-IN')}` : ''}

diff --git a/src/components/dashboard/VerificationStatusPage.tsx b/src/components/dashboard/VerificationStatusPage.tsx index 721fc69..c739c47 100644 --- a/src/components/dashboard/VerificationStatusPage.tsx +++ b/src/components/dashboard/VerificationStatusPage.tsx @@ -128,9 +128,37 @@ interface Props { onVerificationStatusChange?: (status: string) => void; } +interface VerificationDocument { + type: string; + value: any; + status: string; +} + +interface VerificationStatusResponse { + status: string; + reference_number: string; + document_request?: string | null; + rejection_reason?: string | null; + updated_at?: string | null; + activity_log?: Array<{ date: string; action: string; details: string }>; + documents: VerificationDocument[]; +} + +// Best-effort extraction of a viewable URL from a document's `value` payload, +// which may be a raw URL string or an upload-response object. +function docUrl(value: any): string | null { + if (!value) return null; + if (typeof value === 'string') return value; + if (typeof value === 'object') { + return value.url ?? value.file_url ?? value.fileUrl ?? value.path ?? null; + } + return null; +} + export default function VerificationStatusPage(props: Props) { const [activeTab, setActiveTab] = createSignal('approval_status'); const [status, setStatus] = createSignal('NOT_SUBMITTED'); + const [referenceNumber, setReferenceNumber] = createSignal(null); const [docRequest, setDocRequest] = createSignal(null); const [rejectionReason, setRejectionReason] = createSignal(null); const [updatedAt, setUpdatedAt] = createSignal(null); @@ -138,21 +166,26 @@ export default function VerificationStatusPage(props: Props) { const [resubmitting, setResubmitting] = createSignal(false); const [resubmitMsg, setResubmitMsg] = createSignal(''); const [activityLog, setActivityLog] = createSignal>([]); + const [documents, setDocuments] = createSignal([]); onMount(async () => { try { const res = await apiFetch(`/api/me/verification-status?roleKey=${props.roleKey}`); if (res.ok) { - const d = await res.json(); + const d: VerificationStatusResponse = await res.json(); const nextStatus = String(d.status ?? 'NOT_SUBMITTED'); setStatus(nextStatus); props.onVerificationStatusChange?.(nextStatus); + setReferenceNumber(d.reference_number ?? null); setDocRequest(d.document_request ?? null); setRejectionReason(d.rejection_reason ?? null); setUpdatedAt(d.updated_at ?? null); if (d.activity_log) { setActivityLog(d.activity_log); } + if (d.documents) { + setDocuments(d.documents); + } } } finally { setLoading(false); @@ -220,6 +253,11 @@ export default function VerificationStatusPage(props: Props) {

Track verification progress, documents, and updates.

+ +

+ {referenceNumber()} +

+
@@ -425,15 +463,43 @@ export default function VerificationStatusPage(props: Props) {
- - {(doc) => ( -
-

{doc.label}

-

- {status() === 'APPROVED' ? '✓ Verified' : status() === 'REJECTED' ? '✕ Rejected' : '◌ Pending review'} -

-
- )} + 0 ? documents() : docFields().map((f) => ({ type: f.key, value: null, status: status() }))}> + {(doc) => { + const fieldLabel = () => docFields().find((f) => f.key === doc.type)?.label ?? doc.type; + const url = () => docUrl(doc.value); + return ( +
+
+
+

{fieldLabel()}

+ No file uploaded

+ } + > + + View document + +
+
+

+ {doc.status === 'APPROVED' ? '✓ Verified' : doc.status === 'REJECTED' ? '✕ Rejected' : doc.status === 'SUBMITTED' ? '◌ Submitted' : '◌ Pending review'} +

+
+
+ ); + }}