Fix "View" showing the nxtgauge logo instead of the submitted document
All checks were successful
build-and-release / build (push) Successful in 1m1s
All checks were successful
build-and-release / build (push) Successful in 1m1s
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 <noreply@anthropic.com>
This commit is contained in:
parent
69479291c9
commit
cb99b90774
1 changed files with 51 additions and 14 deletions
|
|
@ -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<string> {
|
||||
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: "<field key>", value: "<stored file url>", 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() {
|
|||
<td style="padding:12px 16px">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setViewer({ open: true, title: doc.title, type: doc.type, url: doc.url })}
|
||||
onClick={async () => {
|
||||
const signed = doc.url ? await presignDocUrl(doc.url) : doc.url;
|
||||
setViewer({ open: true, title: doc.title, type: doc.type, url: signed });
|
||||
}}
|
||||
style="height:30px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 10px;font-size:12px;font-weight:700;color:#374151;cursor:pointer"
|
||||
>
|
||||
View
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue