Fix job seeker document submission: hide raw storage URL, fix error masking
All checks were successful
build-and-release / build (push) Successful in 1m48s
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:
parent
83b5072011
commit
cab192d946
3 changed files with 34 additions and 14 deletions
|
|
@ -859,7 +859,7 @@ export default function ProfilePage(props: Props) {
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
setSubmitMsg("");
|
setSubmitMsg("");
|
||||||
try {
|
try {
|
||||||
const { data, status } = await request("/api/profile/submit-for-verification", {
|
const { status } = await request("/api/profile/submit-for-verification", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: { roleKey: props.roleKey, profile_data: { ...form(), ...docUrls() } },
|
body: { roleKey: props.roleKey, profile_data: { ...form(), ...docUrls() } },
|
||||||
});
|
});
|
||||||
|
|
@ -867,13 +867,13 @@ export default function ProfilePage(props: Props) {
|
||||||
setVerificationStatus("PENDING");
|
setVerificationStatus("PENDING");
|
||||||
props.onVerificationStatusChange?.("PENDING");
|
props.onVerificationStatusChange?.("PENDING");
|
||||||
setSubmitMsg("Submitted! We will review your profile and notify you.");
|
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 {
|
} catch (err: any) {
|
||||||
setSubmitMsg("Network error. Please try again.");
|
// 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 {
|
} finally {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
}
|
}
|
||||||
|
|
@ -1241,7 +1241,7 @@ export default function ProfilePage(props: Props) {
|
||||||
"border-radius": "6px",
|
"border-radius": "6px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
✓ {docUrls()[doc.key]}
|
✓ Uploaded
|
||||||
</span>
|
</span>
|
||||||
<Show when={!isLocked()}>
|
<Show when={!isLocked()}>
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ 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';
|
import { getDocFields } from '~/lib/profile-fields-config';
|
||||||
|
import { presignDocumentUrl } from '~/lib/api';
|
||||||
|
|
||||||
const NAVY = '#0D0D2A';
|
const NAVY = '#0D0D2A';
|
||||||
const ORANGE = '#FF5E13';
|
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>
|
<p style={{ margin: '4px 0 0', 'font-size': '12px', color: '#9CA3AF' }}>No file uploaded</p>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<a
|
<button
|
||||||
href={url()!}
|
type="button"
|
||||||
target="_blank"
|
onClick={async () => {
|
||||||
rel="noopener noreferrer"
|
const signed = await presignDocumentUrl(url()!);
|
||||||
style={{ margin: '4px 0 0', 'font-size': '12px', color: '#FF5E13', 'text-decoration': 'underline', display: 'inline-block' }}
|
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
|
View document
|
||||||
</a>
|
</button>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<p style={{
|
<p style={{
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,19 @@ export async function fetchDocuments(rolePrefix: string): Promise<any[]> {
|
||||||
return res?.data ?? [];
|
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> {
|
export async function submitProfileForVerification(rolePrefix: string): Promise<any> {
|
||||||
return apiFetch(`/api/${rolePrefix}/profile/submit`, {
|
return apiFetch(`/api/${rolePrefix}/profile/submit`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue