fix(profile): persist uploaded document URLs and fill in missing doc config
All checks were successful
build-and-release / build (push) Successful in 1m47s

Uploaded document URLs lived only in a client-side signal and were never
included in the Save or Submit-for-Verification payloads (the latter sent
a document_urls field the backend silently ignored), so every submitted
verification case ended up with no attached documents for every role
except COMPANY. Now merges doc URLs into profile_data on save/submit and
repopulates them from the saved profile on reload.

Also adds the missing DOC_FIELDS entries for Developer, Video Editor,
Graphic Designer, Social Media Manager, and UGC Content Creator, and makes
the verification-status Documents tab role-aware instead of showing 3
hardcoded generic labels.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Ashwin Kumar Sivakumar 2026-07-14 21:23:45 +05:30
parent 629a5eeebc
commit 5290e7c70f
3 changed files with 126 additions and 21 deletions

View file

@ -661,6 +661,17 @@ export default function ProfilePage(props: Props) {
flat[k] = String(v ?? ""); flat[k] = String(v ?? "");
} }
setForm(flat); setForm(flat);
// Documents are saved into the same profile_data blob as basic fields —
// pull out whichever doc keys this role expects so previously uploaded
// files show as already-attached instead of appearing lost on reload.
const docKeys = getDocFields(props.roleKey).map((d) => d.key);
const savedDocs: Record<string, string> = {};
for (const key of docKeys) {
const val = data.profile_data[key];
if (typeof val === "string" && val) savedDocs[key] = val;
}
if (Object.keys(savedDocs).length > 0) setDocUrls((prev) => ({ ...savedDocs, ...prev }));
} }
} }
@ -816,7 +827,7 @@ export default function ProfilePage(props: Props) {
try { try {
const { data, status } = await request("/api/profile", { const { data, status } = await request("/api/profile", {
method: "PATCH", method: "PATCH",
body: { roleKey: props.roleKey, profile_data: form() }, body: { roleKey: props.roleKey, profile_data: { ...form(), ...docUrls() } },
}); });
if (status === 200) setSaveMsg("Saved successfully."); if (status === 200) setSaveMsg("Saved successfully.");
else setSaveMsg((data && (data.error || data.message)) || "Failed to save. Please try again."); else setSaveMsg((data && (data.error || data.message)) || "Failed to save. Please try again.");
@ -847,7 +858,7 @@ export default function ProfilePage(props: Props) {
try { try {
const { data, status } = await request("/api/profile/submit-for-verification", { const { data, status } = await request("/api/profile/submit-for-verification", {
method: "POST", method: "POST",
body: { roleKey: props.roleKey, document_urls: docUrls() }, body: { roleKey: props.roleKey, profile_data: { ...form(), ...docUrls() } },
}); });
if (status === 200) { if (status === 200) {
setVerificationStatus("PENDING"); setVerificationStatus("PENDING");

View file

@ -4,9 +4,10 @@
* REVISION_REQUESTED, APPROVED, REJECTED. * REVISION_REQUESTED, APPROVED, REJECTED.
* Tabs: approval status, documents, activity * Tabs: approval status, documents, activity
*/ */
import { For, Show, createSignal, onMount } from 'solid-js'; import { For, Show, createMemo, createSignal, onMount } from 'solid-js';
import { ShieldCheck, FileText, Activity } from 'lucide-solid'; import { ShieldCheck, FileText, Activity } from 'lucide-solid';
import { CARD, BTN_ORANGE, BTN_GHOST } from '~/components/DashboardShell'; import { CARD, BTN_ORANGE, BTN_GHOST } from '~/components/DashboardShell';
import { getDocFields } from '~/lib/profile-fields-config';
const API = '/api/gateway'; const API = '/api/gateway';
const NAVY = '#0D0D2A'; const NAVY = '#0D0D2A';
@ -159,6 +160,7 @@ export default function VerificationStatusPage(props: Props) {
const cfg = () => STATUS_CONFIG[status()] ?? STATUS_CONFIG.NOT_SUBMITTED; const cfg = () => STATUS_CONFIG[status()] ?? STATUS_CONFIG.NOT_SUBMITTED;
const currentStep = () => stepIndex(status()); const currentStep = () => stepIndex(status());
const docFields = createMemo(() => getDocFields(props.roleKey));
const canResubmit = () => ['DOCUMENTS_REQUESTED', 'REVISION_REQUESTED', 'REJECTED'].includes(status()); const canResubmit = () => ['DOCUMENTS_REQUESTED', 'REVISION_REQUESTED', 'REJECTED'].includes(status());
const handleResubmit = async () => { const handleResubmit = async () => {
@ -422,24 +424,16 @@ export default function VerificationStatusPage(props: Props) {
</Show> </Show>
<Show when={status() !== 'NOT_SUBMITTED'}> <Show when={status() !== 'NOT_SUBMITTED'}>
<div style={{ display: 'flex', 'flex-direction': 'column', gap: '8px' }}> <div style={{ display: 'flex', 'flex-direction': 'column', gap: '8px' }}>
<div style={{ border: '1px solid #E5E7EB', 'border-radius': '10px', padding: '12px', background: '#FCFCFD' }}> <For each={docFields()}>
<p style={{ margin: '0', 'font-size': '13px', 'font-weight': '600', color: '#111827' }}>Identity Proof</p> {(doc) => (
<p style={{ margin: '4px 0 0', 'font-size': '12px', color: '#6B7280' }}> <div style={{ border: '1px solid #E5E7EB', 'border-radius': '10px', padding: '12px', background: '#FCFCFD' }}>
{status() === 'APPROVED' ? '✓ Verified' : status() === 'REJECTED' ? '✕ Rejected' : '◌ Pending review'} <p style={{ margin: '0', 'font-size': '13px', 'font-weight': '600', color: '#111827' }}>{doc.label}</p>
</p> <p style={{ margin: '4px 0 0', 'font-size': '12px', color: '#6B7280' }}>
</div> {status() === 'APPROVED' ? '✓ Verified' : status() === 'REJECTED' ? '✕ Rejected' : '◌ Pending review'}
<div style={{ border: '1px solid #E5E7EB', 'border-radius': '10px', padding: '12px', background: '#FCFCFD' }}> </p>
<p style={{ margin: '0', 'font-size': '13px', 'font-weight': '600', color: '#111827' }}>Address Proof</p> </div>
<p style={{ margin: '4px 0 0', 'font-size': '12px', color: '#6B7280' }}> )}
{status() === 'APPROVED' ? '✓ Verified' : status() === 'REJECTED' ? '✕ Rejected' : '◌ Pending review'} </For>
</p>
</div>
<div style={{ border: '1px solid #E5E7EB', 'border-radius': '10px', padding: '12px', background: '#FCFCFD' }}>
<p style={{ margin: '0', 'font-size': '13px', 'font-weight': '600', color: '#111827' }}>Professional Certifications</p>
<p style={{ margin: '4px 0 0', 'font-size': '12px', color: '#6B7280' }}>
{status() === 'APPROVED' ? '✓ Verified' : status() === 'REJECTED' ? '✕ Rejected' : '◌ Pending review'}
</p>
</div>
</div> </div>
</Show> </Show>
</div> </div>

View file

@ -334,6 +334,106 @@ const DOC_FIELDS: Record<string, DocField[]> = {
hint: 'JPG, PNG or PDF · Max 10MB', hint: 'JPG, PNG or PDF · Max 10MB',
}, },
], ],
DEVELOPER: [
{
key: 'aadhar_doc',
label: 'Identity Proof',
required: true,
hint: 'JPG, PNG or PDF · Max 10MB',
},
{
key: 'address_proof',
label: 'Address Proof',
required: true,
hint: 'JPG, PNG or PDF · Max 10MB',
},
{
key: 'tax_document',
label: 'Tax Document',
required: true,
hint: 'JPG, PNG or PDF · Max 10MB',
},
],
VIDEO_EDITOR: [
{
key: 'aadhar_doc',
label: 'Identity Proof',
required: true,
hint: 'JPG, PNG or PDF · Max 10MB',
},
{
key: 'address_proof',
label: 'Address Proof',
required: true,
hint: 'JPG, PNG or PDF · Max 10MB',
},
{
key: 'tax_document',
label: 'Tax Document',
required: true,
hint: 'JPG, PNG or PDF · Max 10MB',
},
],
GRAPHIC_DESIGNER: [
{
key: 'aadhar_doc',
label: 'Identity Proof',
required: true,
hint: 'JPG, PNG or PDF · Max 10MB',
},
{
key: 'address_proof',
label: 'Address Proof',
required: true,
hint: 'JPG, PNG or PDF · Max 10MB',
},
{
key: 'tax_document',
label: 'Tax Document',
required: true,
hint: 'JPG, PNG or PDF · Max 10MB',
},
],
SOCIAL_MEDIA_MANAGER: [
{
key: 'aadhar_doc',
label: 'Identity Proof',
required: true,
hint: 'JPG, PNG or PDF · Max 10MB',
},
{
key: 'address_proof',
label: 'Address Proof',
required: true,
hint: 'JPG, PNG or PDF · Max 10MB',
},
{
key: 'tax_document',
label: 'Tax Document',
required: true,
hint: 'JPG, PNG or PDF · Max 10MB',
},
],
UGC_CONTENT_CREATOR: [
{
key: 'aadhar_doc',
label: 'Identity Proof',
required: true,
hint: 'JPG, PNG or PDF · Max 10MB',
},
{
key: 'address_proof',
label: 'Address Proof',
required: true,
hint: 'JPG, PNG or PDF · Max 10MB',
},
{
key: 'tax_document',
label: 'Tax Document',
required: true,
hint: 'JPG, PNG or PDF · Max 10MB',
},
],
}; };
// ── Portfolio sections per role ─────────────────────────────────────────────── // ── Portfolio sections per role ───────────────────────────────────────────────