From cb99b9077479ad34fb1308eb8213941aa8ad6be6 Mon Sep 17 00:00:00 2001 From: Ashwin Kumar Sivakumar Date: Mon, 27 Jul 2026 23:02:01 +0530 Subject: [PATCH] Fix "View" showing the nxtgauge logo instead of the submitted document MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The verification queue read row.payload?.documents (never populated — the real per-document array is a separate top-level `documents` field that was dropped during row mapping) and expected a {title, url} shape that doesn't match what the backend actually sends ({type, value, status}). Both mismatches meant every submission fell through to the hardcoded placeholder-logo stub. Capture v.documents into each row, read the real fields, and resolve the stored URL through the presign endpoint before displaying it. Co-Authored-By: Claude Sonnet 5 --- src/routes/admin/verification/index.tsx | 65 +++++++++++++++++++------ 1 file changed, 51 insertions(+), 14 deletions(-) diff --git a/src/routes/admin/verification/index.tsx b/src/routes/admin/verification/index.tsx index 1d0e8a6..0294dc5 100644 --- a/src/routes/admin/verification/index.tsx +++ b/src/routes/admin/verification/index.tsx @@ -27,6 +27,7 @@ type VerificationRow = { userId: string; roleKey: string; payload?: any; + documents?: any[]; }; type SubmittedDocument = { @@ -97,6 +98,31 @@ const API = ''; const toTitle = (value: string) => String(value || '').replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()); +// Submitted documents are stored as permanent Backblaze URLs. Never render one +// directly (img src / iframe src) — always exchange it for a short-lived signed +// URL through this endpoint first, on demand. +async function presignDocUrl(url: string): Promise { + try { + const accessToken = typeof sessionStorage !== 'undefined' + ? sessionStorage.getItem('nxtgauge_admin_access_token') || '' + : ''; + const res = await fetch(`${API}/api/admin/verifications/presign`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + ...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}), + }, + credentials: 'include', + body: JSON.stringify({ url }), + }); + if (!res.ok) return url; + const data = await res.json().catch(() => ({})); + return data?.url ?? url; + } catch { + return url; + } +} + const statusUi = (status: VerificationStatus) => { if (status === 'APPROVED') return { bg: '#ECFDF3', border: '#BBF7D0', text: '#166534', label: 'Approved' }; if (status === 'UNDER_REVIEW') return { bg: '#EEF2FF', border: '#C7D2FE', text: '#3730A3', label: 'Under Review' }; @@ -213,6 +239,7 @@ export default function VerificationManagementPage() { userId: v.user_id, roleKey: v.role_key, payload, + documents: Array.isArray(v.documents) ? v.documents : [], }; }); setRows(mergedRows); @@ -354,19 +381,26 @@ export default function VerificationManagementPage() { ]; } const roleSpecKey = normalizeRoleSpecKey(row.roleKey || row.userType); - const fromPayload = Array.isArray(row.payload?.documents) ? row.payload.documents : []; - if (fromPayload.length) { - return fromPayload.slice(0, 8).map((doc: any, idx: number) => ({ - id: String(doc.id || `doc-${idx + 1}`), - title: String(doc.title || doc.name || `Document ${idx + 1}`), - type: String(doc.type || '').toUpperCase().includes('PDF') ? 'PDF' : 'IMAGE', - url: String(doc.url || '/nxtgauge-logo.png'), - status: String(doc.status || '').toUpperCase() === 'MISSING' - ? 'MISSING' - : String(doc.status || '').toUpperCase() === 'INVALID' - ? 'INVALID' - : 'SUBMITTED', - })); + // Real submissions: verifications.documents, built server-side as + // [{ type: "", value: "", status }] — not the + // {title, url} shape this UI used to assume, which is why "View" always + // fell through to the ROLE_DOCUMENTS placeholder stub below. + const fromDocuments = Array.isArray(row.documents) ? row.documents : []; + if (fromDocuments.length) { + return fromDocuments.slice(0, 8).map((doc: any, idx: number) => { + const value = String(doc.value ?? doc.url ?? ''); + return { + id: String(doc.id || doc.type || `doc-${idx + 1}`), + title: String(doc.title || toTitle(doc.type) || `Document ${idx + 1}`), + type: /\.(jpg|jpeg|png|gif|webp)(\?|$)/i.test(value) ? 'IMAGE' : 'PDF', + url: value, + status: String(doc.status || '').toUpperCase() === 'MISSING' + ? 'MISSING' + : String(doc.status || '').toUpperCase() === 'INVALID' + ? 'INVALID' + : 'SUBMITTED', + }; + }); } const docs = ROLE_DOCUMENTS[roleSpecKey] || ROLE_DOCUMENTS.PROFESSIONAL; return docs.map((title, idx) => ({ @@ -913,7 +947,10 @@ export default function VerificationManagementPage() {