From 1df875ed36efa6e4307e9af8be7d3e07938317aa Mon Sep 17 00:00:00 2001 From: Tracewebstudio Dev Date: Wed, 12 Aug 2026 14:56:03 +0200 Subject: [PATCH] feat: role registration wizard + shared identity doc pre-fill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Show RoleWizard inline in SwitchServicesPage immediately after role registration — no separate navigate-to-profile step - Pre-fill shared identity docs (Aadhaar, PAN, selfie, address proof) from the user's active role profile so they don't re-upload on a second role registration - Add identity_shared: boolean to RuntimeOnboardingField type so admins can mark fields in the Onboarding Schema Editor; CONVENTION_IDENTITY_ FIELD_IDS covers common IDs automatically as a fallback - Extract roleKeyToPrefix() into src/lib/role-utils.ts shared utility - FileControl sub-component keeps reactivity clean (no any casts) - Fix solid/reactivity: snapshot form()/docUrls()/portfolioForm() synchronously before the first await in handleSubmit/savePortfolio - Reuse badge (♻ indigo) distinguishes pre-filled docs from new uploads; Change link clears pre-fill so user can re-upload - Pending roles now show 'Under Review' badge; wizard exits via ArrowLeft Co-Authored-By: Claude Sonnet 4.6 --- public/favicon.ico | Bin 664 -> 2054 bytes public/traceworks-logo-color.png | Bin 0 -> 51933 bytes public/traceworks-logo-white.svg | 49 ++++ src/components/dashboard/RoleWizard.tsx | 221 ++++++++++----- .../dashboard/SwitchServicesPage.tsx | 265 ++++++++++++------ src/lib/role-utils.ts | 55 ++++ src/lib/runtime/types.ts | 9 + 7 files changed, 450 insertions(+), 149 deletions(-) create mode 100755 public/traceworks-logo-color.png create mode 100755 public/traceworks-logo-white.svg create mode 100644 src/lib/role-utils.ts diff --git a/public/favicon.ico b/public/favicon.ico index fb282da0719ef6ab4c1732df93be6216b0d85520..b9433c6c2a43cabd542f37636f1c4e41410b9bbf 100644 GIT binary patch delta 2041 zcmV(Kc#f ze?W^UN!m!!u0@;Z$3kf1PLM!T6HC4K-7^z2?@3}di_2x^o9}!x=R1UUD8K~B!UY^S zcD!c@LPw#N_97wZFZ|XoG`qTpT|r*Ie-EZfh>rDMM;;$SFbJZ|X$@jTqzOtZ327?4 zSbEUP0i@#yG>xP;yRRV6-+(zvQx&SpWFQL2O`8GNVGID&dO zi&UM%_V%2g98l2A$}<2k{z;O||R>&2G%=Xm$-m^)|wg%DV}Y zRLhyh5{VcrH*%phBxV7P>GxLpT5hkHB-v5?s@hZv$*!7u$ z%m~dbZLcOXl|n?ex!OEUGaK`+#L~P~-9@unK*lY@1Y#uhD?%ZHvgK+Ae@iuC&F)~d zxXHOkx=y02b`MI!P#ko$n^1`fo5K8+tMUoGT79=;e<#@e9{m3jI?>VSL~4om=&Ehf z^xC@D&5LHKy{HcK~W*W6~vP_j|M?cD#=B2Fe?Vh zk0Jq$ih?M{fC(`kM3BvHX0yBV(?74|z1Q8-(=$s}FS(?crh8t$f2!}Rs#jGFTxX%x zY?6nDZ^n3VFGz0zumJ{a0B~uWwP%zx*~ldr`2npfKhklu|2F~Iv1Yv)txglGJ;byD z%pS&L0hHcz1@V9Fav~7OFBHdbNSp!*L zsR^PKK!h9*0y)B#e`FGUjPUXc^eO7AA|Q_q-y@d3aW?7ZX3r0=srHbB)L`)u|G_T%Zj_E<{8zv&Qf3`$m%(9C^PAtjy`|_iLnPIXDm=$ zH5K)I5mOicw1-AER#8Ux>;d&?Jelw0GQcEkc_K` zyiRQax%vbKA!@8|3K9V+>0(PT@(0aad83a%M{>QLf8$T6VBl5TFW`Y~)co1%ls%MKbn3bo&}R4% zqU5T}yDz_0P1zm-N!sjv>U+9y2e!0G$Cu84Bd*ck+Wc0 zYY)9U)gmFp{0fmoSl>a6wGQ9NJj7fereSE_#xucw8nB99#IxgwZ>0oR+IF`@$(pfBBr1g^9$N7Q*mlI$f_{vk~!y`PWPh(0r7-QX z#97zuSU1UcXt$>6v&A$0aA+k0Igy74Z#N^==a|)_Al@htu9y4^ XH#}K|*W1_b00000NkvXXu0mjfn`FMa delta 640 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/dashboard/RoleWizard.tsx b/src/components/dashboard/RoleWizard.tsx index eadfce3..614c549 100644 --- a/src/components/dashboard/RoleWizard.tsx +++ b/src/components/dashboard/RoleWizard.tsx @@ -10,20 +10,104 @@ * ProfilePage.tsx) since its documents step submits via a dedicated bulk * multipart endpoint (submitCompanyProfileWithDocuments) rather than the * per-file immediate-upload flow every other role's document endpoints use. + * + * Pre-fill (shared identity docs): + * Pass `prefilledDocs` — a map of fieldId → already-uploaded URL — to + * pre-populate identity fields (Aadhaar, PAN, selfie, etc.) that the user + * already submitted for another role. Fields are pre-filled when they are + * marked `identity_shared: true` in the schema OR their ID matches + * CONVENTION_IDENTITY_FIELD_IDS. The user can always click "Change" to + * re-upload. */ import { For, Match, Show, Switch, createMemo, createSignal, onMount } from "solid-js"; import { CARD, BTN_GHOST, INPUT, LABEL, BTN_PRIMARY } from "~/components/DashboardShell"; import { request, uploadDocument } from "~/lib/api"; import { updateJobSeekerCustomData } from "~/lib/job-seeker-custom-data"; import { fetchOnboardingSchemaForRole } from "~/lib/runtime/storage"; +import { isIdentityField } from "~/lib/role-utils"; import type { RuntimeOnboardingConfig, RuntimeOnboardingField, RuntimeOnboardingStep } from "~/lib/runtime/types"; interface Props { roleKey: string; - rolePrefix: string; // e.g. "jobseeker", "photographers" — matches the role's document-upload API path + rolePrefix: string; // e.g. "job-seekers", "photographers" — matches the role's document-upload API path + /** Pre-filled doc URLs from the user's existing role profile. fieldId → url */ + prefilledDocs?: Record; onSubmitted: (status: string) => void; } +/** Props for the reusable file-upload control sub-component. */ +interface FileControlProps { + field: RuntimeOnboardingField; + inputId: string; + isReused: boolean; + hasUrl: boolean; + uploading: boolean; + error: string; + onFileChange: (file: File) => void; +} + +/** + * A self-contained file-upload control rendered as a proper SolidJS component + * so that reactivity from parent signals flows correctly through its props. + */ +function FileControl(props: FileControlProps) { + return ( + <> + { + const file = e.currentTarget.files?.[0]; + if (file) props.onFileChange(file); + e.currentTarget.value = ""; + }} + /> + + + No file chosen + +

{props.error}

+
+ + } + > +
+ + ✓ Uploaded + + } + > + + ♻ Reused from your existing profile + + + +
+
+ + ); +} + function fieldRequired(field: RuntimeOnboardingField): boolean { return Boolean(field.required); } @@ -34,6 +118,8 @@ export default function RoleWizard(props: Props) { const [wizardStep, setWizardStep] = createSignal(0); const [form, setForm] = createSignal>({}); const [docUrls, setDocUrls] = createSignal>({}); + /** Tracks which doc fields were pre-filled from another role (shown with reuse badge). */ + const [reusedDocs, setReusedDocs] = createSignal>(new Set()); const [docErrors, setDocErrors] = createSignal>({}); const [docUploading, setDocUploading] = createSignal>({}); const [portfolioForm, setPortfolioForm] = createSignal>({}); @@ -44,6 +130,30 @@ export default function RoleWizard(props: Props) { onMount(async () => { const cfg = await fetchOnboardingSchemaForRole(props.roleKey); setConfig(cfg); + + // Pre-fill shared identity docs passed in from SwitchServicesPage + if (props.prefilledDocs && cfg) { + const preUrls: Record = {}; + const reused = new Set(); + + for (const step of cfg.steps) { + for (const field of step.fields) { + if (field.type !== "file") continue; + if (!isIdentityField(field.id, Boolean(field.identity_shared))) continue; + const existingUrl = props.prefilledDocs[field.id]; + if (existingUrl) { + preUrls[field.id] = existingUrl; + reused.add(field.id); + } + } + } + + if (Object.keys(preUrls).length > 0) { + setDocUrls(preUrls); + setReusedDocs(reused); + } + } + setLoading(false); }); @@ -74,26 +184,29 @@ export default function RoleWizard(props: Props) { const handleFileSelect = async (field: RuntimeOnboardingField, file: File) => { setDocUploading((prev) => ({ ...prev, [field.id]: true })); setDocErrors((prev) => ({ ...prev, [field.id]: "" })); + // Once the user uploads a new file, it's no longer "reused" + setReusedDocs((prev) => { const next = new Set(prev); next.delete(field.id); return next; }); try { const result = await uploadDocument(props.rolePrefix, file, field.id); const url = result?.url ?? result?.file_url ?? result?.path ?? result?.file_name ?? "uploaded"; setDocUrls((prev) => ({ ...prev, [field.id]: url })); - } catch (err: any) { - setDocErrors((prev) => ({ ...prev, [field.id]: err?.message ?? "Upload failed" })); + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : "Upload failed"; + setDocErrors((prev) => ({ ...prev, [field.id]: msg })); } finally { setDocUploading((prev) => ({ ...prev, [field.id]: false })); } }; - const savePortfolio = async () => { + const savePortfolio = async (portfolioSnapshot: Record) => { const model = config()?.portfolioModel; if (!model || model === "none") return; if (model === "custom_data") { - await updateJobSeekerCustomData((current) => ({ ...current, job_seeker_portfolio: portfolioForm() })); + await updateJobSeekerCustomData((current) => ({ ...current, job_seeker_portfolio: portfolioSnapshot })); } else if (model === "professional") { await request("/api/profile", { method: "PATCH", - body: { roleKey: props.roleKey, profile_data: portfolioForm() }, + body: { roleKey: props.roleKey, profile_data: portfolioSnapshot }, }).catch(() => undefined); } }; @@ -101,9 +214,11 @@ export default function RoleWizard(props: Props) { const handleSubmit = async () => { setSubmitting(true); setSubmitMsg(""); + // Snapshot all reactive values synchronously before the first await + const portfolioSnapshot = portfolioForm(); + const profile_data = { ...form(), ...docUrls() }; try { - await savePortfolio(); - const profile_data = { ...form(), ...docUrls() }; + await savePortfolio(portfolioSnapshot); await request("/api/profile", { method: "PATCH", body: { roleKey: props.roleKey, profile_data } }); const { status } = await request("/api/profile/submit-for-verification", { method: "POST", @@ -113,15 +228,14 @@ export default function RoleWizard(props: Props) { setSubmitSuccess(true); setTimeout(() => props.onSubmitted("PENDING"), 2500); } - } catch (err: any) { - setSubmitMsg(err?.message || "Submission failed. Please try again."); + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : "Submission failed. Please try again."; + setSubmitMsg(msg); } finally { setSubmitting(false); } }; - const fieldNote = (field: RuntimeOnboardingField) => null; - return ( Loading...

}> -
- { - const file = e.currentTarget.files?.[0]; - if (file) void handleFileSelect(field, file); - e.currentTarget.value = ""; - }} - /> - - - ✓ Uploaded - - - No file chosen - - -

{docErrors()[field.id]}

-
-
+ void handleFileSelect(field, file) + } + />
- {fieldNote(field)} )} @@ -262,35 +360,18 @@ export default function RoleWizard(props: Props) {

{field.helperText}

- - { - const file = e.currentTarget.files?.[0]; - if (file) void handleFileSelect(field, file); - e.currentTarget.value = ""; - }} - /> - - No file chosen - + void handleFileSelect(field, file) } - > - - ✓ Uploaded - - - -

{docErrors()[field.id]}

-
+ /> )} @@ -332,7 +413,9 @@ export default function RoleWizard(props: Props) { {(field) => { const value = step.type === "documents" - ? (docUrls()[field.id] ? "Uploaded" : "—") + ? (docUrls()[field.id] + ? (reusedDocs().has(field.id) ? "Reused from existing profile" : "Uploaded") + : "—") : step.type === "portfolio" ? (portfolioForm()[field.id] || "—") : (form()[field.id] || "—"); diff --git a/src/components/dashboard/SwitchServicesPage.tsx b/src/components/dashboard/SwitchServicesPage.tsx index 1cbaa7e..8a6f5ee 100644 --- a/src/components/dashboard/SwitchServicesPage.tsx +++ b/src/components/dashboard/SwitchServicesPage.tsx @@ -1,6 +1,8 @@ import { For, Show, createSignal, onMount } from 'solid-js'; -import { RefreshCw } from 'lucide-solid'; +import { RefreshCw, ArrowLeft } from 'lucide-solid'; import { BTN_GHOST, BTN_PRIMARY, CARD } from '~/components/DashboardShell'; +import RoleWizard from '~/components/dashboard/RoleWizard'; +import { roleKeyToPrefix, isIdentityField } from '~/lib/role-utils'; const API = ''; const NAVY = '#0D0D2A'; @@ -30,21 +32,50 @@ async function apiFetch(path: string, opts?: RequestInit) { } const REGISTER_OPTIONS = [ - { key: 'PHOTOGRAPHER', label: 'Photographer' }, - { key: 'MAKEUP_ARTIST', label: 'Makeup Artist' }, - { key: 'TUTOR', label: 'Tutor' }, - { key: 'DEVELOPER', label: 'Developer' }, - { key: 'VIDEO_EDITOR', label: 'Video Editor' }, - { key: 'UGC_CONTENT_CREATOR', label: 'UGC Content Creator' }, - { key: 'GRAPHIC_DESIGNER', label: 'Graphic Designer' }, - { key: 'SOCIAL_MEDIA_MANAGER', label: 'Social Media Manager' }, - { key: 'FITNESS_TRAINER', label: 'Fitness Trainer' }, - { key: 'CATERING_SERVICES', label: 'Catering Services' }, - { key: 'COMPANY', label: 'Company' }, - { key: 'CUSTOMER', label: 'Customer' }, - { key: 'JOB_SEEKER', label: 'Job Seeker' }, + { key: 'PHOTOGRAPHER', label: 'Photographer' }, + { key: 'MAKEUP_ARTIST', label: 'Makeup Artist' }, + { key: 'TUTOR', label: 'Tutor' }, + { key: 'DEVELOPER', label: 'Developer' }, + { key: 'VIDEO_EDITOR', label: 'Video Editor' }, + { key: 'UGC_CONTENT_CREATOR', label: 'UGC Content Creator' }, + { key: 'GRAPHIC_DESIGNER', label: 'Graphic Designer' }, + { key: 'SOCIAL_MEDIA_MANAGER', label: 'Social Media Manager' }, + { key: 'FITNESS_TRAINER', label: 'Fitness Trainer' }, + { key: 'CATERING_SERVICES', label: 'Catering Services' }, + { key: 'COMPANY', label: 'Company' }, + { key: 'CUSTOMER', label: 'Customer' }, + { key: 'JOB_SEEKER', label: 'Job Seeker' }, ]; +/** + * Fetches the user's active role profile and extracts doc URLs for fields + * that are identity fields (shared across roles). These are passed as + * `prefilledDocs` to RoleWizard so the user doesn't have to re-upload + * Aadhaar / PAN / selfie etc. when registering a second role. + */ +async function fetchSharedIdentityDocs(activeRoleKey: string): Promise> { + if (!activeRoleKey) return {}; + try { + const res = await apiFetch(`/api/profile?roleKey=${activeRoleKey}`); + if (!res.ok) return {}; + const data = await res.json().catch(() => ({})); + const profile: Record = data?.profile_data ?? data ?? {}; + + // Keep only string values (URLs / field values) for fields whose key + // looks like an identity document — we don't know the schema here, so + // we use the convention-based check (adminFlagged = false). + const shared: Record = {}; + for (const [key, val] of Object.entries(profile)) { + if (typeof val === 'string' && val && isIdentityField(key, false)) { + shared[key] = val; + } + } + return shared; + } catch { + return {}; + } +} + export default function SwitchServicesPage() { const [roles, setRoles] = createSignal([]); const [activeRole, setActiveRole] = createSignal(''); @@ -53,6 +84,12 @@ export default function SwitchServicesPage() { const [msg, setMsg] = createSignal(''); const [err, setErr] = createSignal(''); + // ── wizard state ────────────────────────────────────────────────────────── + /** When set, the wizard is shown inline for this role key. */ + const [wizardRole, setWizardRole] = createSignal(null); + const [wizardLabel, setWizardLabel] = createSignal(''); + const [sharedDocs, setSharedDocs] = createSignal>({}); + const loadRoles = async () => { setLoading(true); setErr(''); @@ -84,7 +121,7 @@ export default function SwitchServicesPage() { onMount(loadRoles); - const registerRole = async (roleKey: string) => { + const registerRole = async (roleKey: string, label: string) => { setBusyRole(roleKey); setMsg(''); setErr(''); @@ -98,8 +135,16 @@ export default function SwitchServicesPage() { setErr(data.error || data.message || 'Failed to register role.'); return; } - setMsg(`${roleKey} registered. You can switch after refresh/login.`); + + // Fetch identity docs from the user's current active role to pre-fill + const shared = await fetchSharedIdentityDocs(activeRole()); + setSharedDocs(shared); + await loadRoles(); + + // Launch the verification wizard inline + setWizardLabel(label); + setWizardRole(roleKey); } catch { setErr('Network error while registering role.'); } finally { @@ -163,8 +208,22 @@ export default function SwitchServicesPage() { const hasRole = (roleKey: string) => roles().some((r) => String(r.role_key || '').toUpperCase() === roleKey); const isActive = (roleKey: string) => activeRole() === roleKey; + const exitWizard = () => { + setWizardRole(null); + setSharedDocs({}); + setWizardLabel(''); + }; + + const onWizardSubmitted = async (_status: string) => { + exitWizard(); + await loadRoles(); + setMsg('Your verification has been submitted. We\'ll review it shortly.'); + }; + + // ── wizard view ─────────────────────────────────────────────────────────── return (
+ {/* Header */}
- - - + } + > + +

- Switch Services + + {wizardLabel()} — Verification +

- Manage approved roles and register additional services. + + Complete your profile to submit for admin approval. +

- -
{msg()}
-
- -
{err()}
+ {/* Inline wizard — shown after successful registration */} + + -
-
-

My Roles

- + {/* Main list + registration — hidden while wizard is open */} + + +
{msg()}
+
+ +
{err()}
+
+ + {/* My Roles */} +
+
+

My Roles

+ +
+ +

Loading roles...

+
+ +

No roles found for this user.

+
+ 0}> +
+ + {(r) => { + const rk = () => String(r.role_key || '').toUpperCase(); + const isPending = () => String(r.status || '').toUpperCase() === 'PENDING'; + return ( +
+
+

{r.role_name || r.role_key}

+

+ Status: {String(r.status || 'APPROVED').replace(/_/g, ' ')} {r.approved_at ? `• Approved ${new Date(r.approved_at).toLocaleString('en-IN')}` : ''} +

+
+
+ + Under Review + + } + > + + Approved + + + + + +
+
+ ); + }} +
+
+
- -

Loading roles...

-
- -

No roles found for this user.

-
- 0}> -
- - {(r) => ( -
-
-

{r.role_name || r.role_key}

-

- Status: {String(r.status || 'APPROVED').replace(/_/g, ' ')} {r.approved_at ? `• Approved ${new Date(r.approved_at).toLocaleString('en-IN')}` : ''} -

-
-
- Approved - -
+ + {/* Register Another Service */} +
+

Register Another Service

+
+ + {(opt) => ( +
+

{opt.label}

+
)}
- -
- -
-

Register Another Service

-
- - {(opt) => ( -
-

{opt.label}

- -
- )} -
-
+
); } diff --git a/src/lib/role-utils.ts b/src/lib/role-utils.ts new file mode 100644 index 0000000..b015e97 --- /dev/null +++ b/src/lib/role-utils.ts @@ -0,0 +1,55 @@ +/** + * Shared role utilities used by ProfilePage, SwitchServicesPage, and any + * other page that needs to map a role key to an API path prefix. + */ + +/** + * Maps a role key (e.g. "PHOTOGRAPHER") to the API path prefix used by that + * role's backend service (e.g. "photographers"). + * + * Must stay in sync with the Axum router mounts in + * nxtgauge-backend-rust/apps/gateway/src/main.rs. + */ +export function roleKeyToPrefix(roleKey: string): string { + switch (roleKey) { + case "JOB_SEEKER": return "job-seekers"; + case "COMPANY": return "company"; + case "CATERING_SERVICES": return "catering-services"; + case "MAKEUP_ARTIST": return "makeup-artists"; + default: + // PHOTOGRAPHER → photographers, DEVELOPER → developers, etc. + return roleKey.toLowerCase().replace(/_/g, "-") + "s"; + } +} + +/** + * Field IDs that are considered "shared identity" documents by convention + * when the onboarding schema has not explicitly marked them with + * `identity_shared: true`. Admins can use these IDs to get automatic + * pre-fill from the user's active role — or they can mark any field with + * `identity_shared: true` to opt into pre-fill explicitly. + */ +export const CONVENTION_IDENTITY_FIELD_IDS = new Set([ + "aadhaar", + "aadhaar_card", + "aadhaar_number", + "aadhar", + "pan", + "pan_card", + "pan_number", + "passport", + "passport_number", + "selfie", + "profile_photo", + "photo_id", + "address_proof", + "government_id", +]); + +/** + * Returns true when a field should be pre-filled from the user's existing + * profile across roles (either admin-flagged or matched by convention). + */ +export function isIdentityField(fieldId: string, adminFlagged: boolean): boolean { + return adminFlagged || CONVENTION_IDENTITY_FIELD_IDS.has(fieldId); +} diff --git a/src/lib/runtime/types.ts b/src/lib/runtime/types.ts index 8b3cf83..f67ceec 100644 --- a/src/lib/runtime/types.ts +++ b/src/lib/runtime/types.ts @@ -46,6 +46,15 @@ export type RuntimeOnboardingField = { * apps/users/src/handlers/profile.rs's save_profile, not just in this UI. */ lockAfterApproval?: boolean; + /** + * When true, this field's value (or uploaded document URL) is considered a + * shared identity credential (e.g. Aadhaar, PAN, selfie) that can be + * pre-filled from the user's existing role profile when they register a + * second role. The user can always replace it. Admins set this in the + * Onboarding Schema Editor; the frontend also pre-fills fields whose IDs + * match CONVENTION_IDENTITY_FIELD_IDS in lib/role-utils.ts as a fallback. + */ + identity_shared?: boolean; multiple?: boolean; options?: RuntimeOption[]; accept?: string;