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;
|
userId: string;
|
||||||
roleKey: string;
|
roleKey: string;
|
||||||
payload?: any;
|
payload?: any;
|
||||||
|
documents?: any[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type SubmittedDocument = {
|
type SubmittedDocument = {
|
||||||
|
|
@ -97,6 +98,31 @@ const API = '';
|
||||||
|
|
||||||
const toTitle = (value: string) => String(value || '').replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
|
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) => {
|
const statusUi = (status: VerificationStatus) => {
|
||||||
if (status === 'APPROVED') return { bg: '#ECFDF3', border: '#BBF7D0', text: '#166534', label: 'Approved' };
|
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' };
|
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,
|
userId: v.user_id,
|
||||||
roleKey: v.role_key,
|
roleKey: v.role_key,
|
||||||
payload,
|
payload,
|
||||||
|
documents: Array.isArray(v.documents) ? v.documents : [],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
setRows(mergedRows);
|
setRows(mergedRows);
|
||||||
|
|
@ -354,19 +381,26 @@ export default function VerificationManagementPage() {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
const roleSpecKey = normalizeRoleSpecKey(row.roleKey || row.userType);
|
const roleSpecKey = normalizeRoleSpecKey(row.roleKey || row.userType);
|
||||||
const fromPayload = Array.isArray(row.payload?.documents) ? row.payload.documents : [];
|
// Real submissions: verifications.documents, built server-side as
|
||||||
if (fromPayload.length) {
|
// [{ type: "<field key>", value: "<stored file url>", status }] — not the
|
||||||
return fromPayload.slice(0, 8).map((doc: any, idx: number) => ({
|
// {title, url} shape this UI used to assume, which is why "View" always
|
||||||
id: String(doc.id || `doc-${idx + 1}`),
|
// fell through to the ROLE_DOCUMENTS placeholder stub below.
|
||||||
title: String(doc.title || doc.name || `Document ${idx + 1}`),
|
const fromDocuments = Array.isArray(row.documents) ? row.documents : [];
|
||||||
type: String(doc.type || '').toUpperCase().includes('PDF') ? 'PDF' : 'IMAGE',
|
if (fromDocuments.length) {
|
||||||
url: String(doc.url || '/nxtgauge-logo.png'),
|
return fromDocuments.slice(0, 8).map((doc: any, idx: number) => {
|
||||||
status: String(doc.status || '').toUpperCase() === 'MISSING'
|
const value = String(doc.value ?? doc.url ?? '');
|
||||||
? 'MISSING'
|
return {
|
||||||
: String(doc.status || '').toUpperCase() === 'INVALID'
|
id: String(doc.id || doc.type || `doc-${idx + 1}`),
|
||||||
? 'INVALID'
|
title: String(doc.title || toTitle(doc.type) || `Document ${idx + 1}`),
|
||||||
: 'SUBMITTED',
|
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;
|
const docs = ROLE_DOCUMENTS[roleSpecKey] || ROLE_DOCUMENTS.PROFESSIONAL;
|
||||||
return docs.map((title, idx) => ({
|
return docs.map((title, idx) => ({
|
||||||
|
|
@ -913,7 +947,10 @@ export default function VerificationManagementPage() {
|
||||||
<td style="padding:12px 16px">
|
<td style="padding:12px 16px">
|
||||||
<button
|
<button
|
||||||
type="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"
|
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
|
View
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue