Fix job seeker document submission: hide raw storage URL, fix error masking
All checks were successful
build-and-release / build (push) Successful in 1m48s

Two issues in the job seeker (and shared profile) dashboard:
- The uploaded document's full Backblaze URL was rendered as visible text
  and linked directly; view-document now resolves a short-lived signed
  URL on demand instead of exposing the permanent storage link.
- "Submit for Verification" always showed a generic "Network error"
  message on any failure because request() throws on non-2xx responses,
  making the destructured status/data unreachable in the catch-free path
  (e.g. a 409 "verification already in progress" looked identical to a
  network failure). Now surfaces the real backend error message.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Ashwin Kumar Sivakumar 2026-07-27 18:07:37 +05:30
parent 83b5072011
commit cab192d946
3 changed files with 34 additions and 14 deletions

View file

@ -859,7 +859,7 @@ export default function ProfilePage(props: Props) {
setSubmitting(true);
setSubmitMsg("");
try {
const { data, status } = await request("/api/profile/submit-for-verification", {
const { status } = await request("/api/profile/submit-for-verification", {
method: "POST",
body: { roleKey: props.roleKey, profile_data: { ...form(), ...docUrls() } },
});
@ -867,13 +867,13 @@ export default function ProfilePage(props: Props) {
setVerificationStatus("PENDING");
props.onVerificationStatusChange?.("PENDING");
setSubmitMsg("Submitted! We will review your profile and notify you.");
} else if (status === 409) {
setSubmitMsg(data?.error ?? "A verification is already in progress.");
} else {
setSubmitMsg(data?.error ?? "Submission failed. Please try again.");
}
} catch {
setSubmitMsg("Network error. Please try again.");
} catch (err: any) {
// request() throws on any non-2xx response with the backend's actual error
// message (e.g. "A verification is already in progress...") — surface that
// instead of a generic message, otherwise every failure looks identical and
// unactionable regardless of cause.
setSubmitMsg(err?.message || "Submission failed. Please try again.");
} finally {
setSubmitting(false);
}
@ -1241,7 +1241,7 @@ export default function ProfilePage(props: Props) {
"border-radius": "6px",
}}
>
{docUrls()[doc.key]}
Uploaded
</span>
<Show when={!isLocked()}>
<button

View file

@ -8,6 +8,7 @@ import { For, Show, createMemo, createSignal, onMount } from 'solid-js';
import { ShieldCheck, FileText, Activity } from 'lucide-solid';
import { CARD, BTN_ORANGE, BTN_GHOST } from '~/components/DashboardShell';
import { getDocFields } from '~/lib/profile-fields-config';
import { presignDocumentUrl } from '~/lib/api';
const NAVY = '#0D0D2A';
const ORANGE = '#FF5E13';
@ -478,14 +479,20 @@ export default function VerificationStatusPage(props: Props) {
<p style={{ margin: '4px 0 0', 'font-size': '12px', color: '#9CA3AF' }}>No file uploaded</p>
}
>
<a
href={url()!}
target="_blank"
rel="noopener noreferrer"
style={{ margin: '4px 0 0', 'font-size': '12px', color: '#FF5E13', 'text-decoration': 'underline', display: 'inline-block' }}
<button
type="button"
onClick={async () => {
const signed = await presignDocumentUrl(url()!);
window.open(signed, '_blank', 'noopener,noreferrer');
}}
style={{
margin: '4px 0 0', 'font-size': '12px', color: '#FF5E13',
'text-decoration': 'underline', display: 'inline-block',
background: 'none', border: 'none', padding: '0', cursor: 'pointer',
}}
>
View document
</a>
</button>
</Show>
</div>
<p style={{

View file

@ -90,6 +90,19 @@ export async function fetchDocuments(rolePrefix: string): Promise<any[]> {
return res?.data ?? [];
}
/**
* Resolve a stored document reference into a short-lived, signed view URL.
* Never render a stored document URL directly (e.g. as an <a href> or <img src>)
* always exchange it for a fresh one through this endpoint first.
*/
export async function presignDocumentUrl(url: string): Promise<string> {
const res = await apiFetch('/api/profile/documents/presign', {
method: 'POST',
body: JSON.stringify({ url }),
});
return res?.url ?? url;
}
export async function submitProfileForVerification(rolePrefix: string): Promise<any> {
return apiFetch(`/api/${rolePrefix}/profile/submit`, {
method: 'POST',