diff --git a/src/components/DashboardShell.tsx b/src/components/DashboardShell.tsx index cffdfbf..683f512 100644 --- a/src/components/DashboardShell.tsx +++ b/src/components/DashboardShell.tsx @@ -87,6 +87,8 @@ interface Props { roleKey: string; userName?: string; isAdmin?: boolean; + /** Shows a checkmark on the Verification nav item once the profile is approved. */ + verificationApproved?: boolean; children: JSX.Element; } @@ -196,6 +198,9 @@ export default function DashboardShell(props: Props) { {titleCase(item)} + + + ); }} diff --git a/src/components/dashboard/ProfilePage.tsx b/src/components/dashboard/ProfilePage.tsx index 1cb50c1..110a388 100644 --- a/src/components/dashboard/ProfilePage.tsx +++ b/src/components/dashboard/ProfilePage.tsx @@ -28,6 +28,8 @@ import { type BasicField, type DocField, } from "~/lib/profile-fields-config"; +import { fetchOnboardingSchemaForRole } from "~/lib/runtime/storage"; +import RoleWizard from "~/components/dashboard/RoleWizard"; // The K8s ingress routes /api/* directly to the Rust gateway service in // production — there is no /api/gateway rewrite layer, so paths below are @@ -566,8 +568,32 @@ export default function ProfilePage(props: Props) { const [wizardSubmitMsg, setWizardSubmitMsg] = createSignal(""); const [wizardSubmitSuccess, setWizardSubmitSuccess] = createSignal(false); + // ── Runtime onboarding config: wizard-vs-tabs + per-field post-approval lock ── + // Admin-configurable via the Onboarding Schema Editor (onboarding_configs + // table) — nothing here is hardcoded per role. + const [wizardEnabled, setWizardEnabled] = createSignal(false); + const [lockedFieldIds, setLockedFieldIds] = createSignal>(new Set()); + const isCompany = () => props.roleKey === "COMPANY"; + const rolePrefix = () => + props.roleKey === "JOB_SEEKER" + ? "jobseeker" + : props.roleKey === "COMPANY" + ? "companies" + : props.roleKey.toLowerCase().replace(/_/g, "-") + "s"; + + // Statuses during which the guided wizard (rather than free-form tabs) + // should be shown: first-time setup, or the admin sent it back for fixes. + const WIZARD_STATUSES = ["NOT_SUBMITTED", "DOCUMENTS_REQUESTED", "REVISION_REQUESTED", "REJECTED"]; + const showWizard = () => wizardEnabled() && WIZARD_STATUSES.includes(verificationStatus()); + + // A field is permanently locked once the profile has ever been approved, + // if the active onboarding schema marked it lockAfterApproval — enforced + // server-side too (apps/users/src/handlers/profile.rs), this just keeps + // the UI honest about it. + const fieldLocked = (key: string) => isLocked() || (verificationStatus() === "APPROVED" && lockedFieldIds().has(key)); + const requiresPortfolio = () => props.roleKey === "JOB_SEEKER" || Boolean(PORTFOLIO_PREFIX[props.roleKey]); const refreshPortfolioSubmission = async (): Promise => { @@ -651,6 +677,18 @@ export default function ProfilePage(props: Props) { // Load saved profile + verification status on mount onMount(async () => { + void fetchOnboardingSchemaForRole(props.roleKey).then((cfg) => { + if (!cfg) return; + setWizardEnabled(Boolean(cfg.enableWizardFlow)); + const locked = new Set(); + for (const step of cfg.steps) { + for (const field of step.fields) { + if (field.lockAfterApproval) locked.add(field.id); + } + } + setLockedFieldIds(locked); + }); + const [profileRes, statusRes] = await Promise.all([ apiFetch(`/api/profile?roleKey=${props.roleKey}`), apiFetch(`/api/me/verification-status?roleKey=${props.roleKey}`), @@ -956,7 +994,7 @@ export default function ProfilePage(props: Props) { return (
- }> + { + setVerificationStatus(status); + props.onVerificationStatusChange?.(status); + }} + /> + } + >