From 30b022356cb082d2aaa54ae1ad0e763a07d96e20 Mon Sep 17 00:00:00 2001 From: Marshal Date: Mon, 22 Jun 2026 12:30:44 +0000 Subject: [PATCH] feat: update onboarding and company role handling to support multiple service types --- .../onboarding/OnboardingWizardDialog.tsx | 37 +++++++++++----- .../src/pages/accounts/CompanyProfileForm.tsx | 10 +++-- .../src/pages/settings/CompanyRolesCard.tsx | 2 +- .../pages/settings/OnboardingRoleSelect.tsx | 44 +++++-------------- .../src/pages/settings/companyRoles.tsx | 8 +++- 5 files changed, 52 insertions(+), 49 deletions(-) diff --git a/apps/edr-freight-web/portal/src/components/onboarding/OnboardingWizardDialog.tsx b/apps/edr-freight-web/portal/src/components/onboarding/OnboardingWizardDialog.tsx index 584735b3c..17fde6528 100644 --- a/apps/edr-freight-web/portal/src/components/onboarding/OnboardingWizardDialog.tsx +++ b/apps/edr-freight-web/portal/src/components/onboarding/OnboardingWizardDialog.tsx @@ -1,6 +1,6 @@ import { Modal, ScrollArea, Stack, Text } from "@mantine/core"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { useCallback, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import useAuth from "@/hooks/useAuth"; import { api } from "@/services/api"; @@ -13,9 +13,7 @@ import { companiesService } from "@/services/companies.service"; import type { UpdateProfilePayload } from "@/types/profile"; import { extractApiError } from "@/utils/result"; import CompanyProfileForm from "@/pages/accounts/CompanyProfileForm"; -import ForwarderForm from "@/pages/accounts/ForwarderForm"; import type { RoleLicenseProfile } from "@/components/onboarding/RoleLicenseStep"; -import { FREIGHT_FORWARDER } from "@/pages/settings/companyRoles"; import NationalitySelect from "@/pages/settings/NationalitySelect"; import OnboardingRoleSelect from "@/pages/settings/OnboardingRoleSelect"; @@ -42,9 +40,14 @@ interface OnboardingWizardDialogProps { onClose: () => void; } -/** Map the chosen operational roles to the company type they belong to. */ -function companyTypeForRoles(roles: string[]): string { - return roles.includes(FREIGHT_FORWARDER.type) ? "forwarder" : "customer"; +/** + * The company type for the onboarding selection. Importer / Exporter / Freight + * Forwarder are all services a single "customer" company can hold (in any + * combination), each with its own business license — so the company is always + * registered as a "customer". + */ +function companyTypeForRoles(_roles: string[]): string { + return "customer"; } /** Document upload setting code per company nationality. */ @@ -161,6 +164,23 @@ export default function OnboardingWizardDialog({ api.companies.setOnboardingStep.call({ step }).catch(() => {}); }, []); + // The company query may resolve AFTER this dialog mounts (it's kept mounted by + // the gate), so the phase/roles/nationality initial state can be stale — a + // draft that already exists would otherwise leave us stuck on the first + // (nationality) phase. Once a draft loads, jump straight into the form with + // the persisted roles/nationality. Runs once per resumed draft. + const resumedRef = useRef(false); + useEffect(() => { + if (!companyAlreadyStarted || resumedRef.current) return; + resumedRef.current = true; + setRoles(existingProfiles.map((p) => p.type)); + setNationality(savedNationality); + setPhase("form"); + const idx = FORM_STEPS.indexOf(resumeFormStep); + if (idx > furthestIdxRef.current) furthestIdxRef.current = idx; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [companyAlreadyStarted, resumeFormStep]); + const handleNationalityContinue = useCallback(() => { if (nationality) setPhase("role"); }, [nationality]); @@ -205,8 +225,7 @@ export default function OnboardingWizardDialog({ if (!user) return null; - const isForwarder = roles.includes(FREIGHT_FORWARDER.type); - // Importer+Exporter (or either alone) is a valid customer selection. + // Any non-empty combination of importer/exporter/freight-forwarder is valid. const rolesValid = roles.length > 0; // Documents depend on nationality; fall back to the saved one (resume) then ethiopian. const effectiveNationality: CompanyNationality = @@ -293,8 +312,6 @@ export default function OnboardingWizardDialog({ onClick={handleRolesContinue} /> - ) : isForwarder ? ( - ) : ( )} diff --git a/apps/edr-freight-web/portal/src/pages/accounts/CompanyProfileForm.tsx b/apps/edr-freight-web/portal/src/pages/accounts/CompanyProfileForm.tsx index f64fd7efd..fca6ecb44 100644 --- a/apps/edr-freight-web/portal/src/pages/accounts/CompanyProfileForm.tsx +++ b/apps/edr-freight-web/portal/src/pages/accounts/CompanyProfileForm.tsx @@ -406,6 +406,10 @@ export default function CompanyProfileForm({ const [contactIsPoa, setContactIsPoa] = useState(false); const handleETradeDataLoaded = (data: CompanyRegistrationData) => { + // Company name comes from the eTrade manager/owner name on the license. + if (data.managerName) { + setValue("companyName", data.managerName, { shouldValidate: true }); + } setValue("licenceNumber", data.licenceNumber); setValue("statusDescription", data.statusDescription); setValue("dateRegistered", data.dateRegistered); @@ -673,11 +677,10 @@ export default function CompanyProfileForm({ /> - {watch("licenceNumber") && ( - <> + <> - Registration Details from eTrade + Registration Details - )} )} diff --git a/apps/edr-freight-web/portal/src/pages/settings/CompanyRolesCard.tsx b/apps/edr-freight-web/portal/src/pages/settings/CompanyRolesCard.tsx index 00aa8aeb6..89fd0c3f9 100644 --- a/apps/edr-freight-web/portal/src/pages/settings/CompanyRolesCard.tsx +++ b/apps/edr-freight-web/portal/src/pages/settings/CompanyRolesCard.tsx @@ -72,7 +72,7 @@ export default function CompanyRolesCard({ profile }: CompanyRolesCardProps) { {profile.companyType === "customer" - ? "Select the role(s) your company operates as — importer, exporter, or both." + ? "Select the service(s) your company operates as — importer, exporter and/or freight forwarder." : "Your company's operational role."} diff --git a/apps/edr-freight-web/portal/src/pages/settings/OnboardingRoleSelect.tsx b/apps/edr-freight-web/portal/src/pages/settings/OnboardingRoleSelect.tsx index faa9e7377..d5196d024 100644 --- a/apps/edr-freight-web/portal/src/pages/settings/OnboardingRoleSelect.tsx +++ b/apps/edr-freight-web/portal/src/pages/settings/OnboardingRoleSelect.tsx @@ -1,39 +1,34 @@ -import { Card, Divider, Group, SimpleGrid, Text, Title } from "@mantine/core"; +import { Card, Group, SimpleGrid, Text, Title } from "@mantine/core"; import { Building2 } from "lucide-react"; import RoleCard from "./RoleCard"; -import { CUSTOMER_ROLES, FREIGHT_FORWARDER } from "./companyRoles"; +import { CUSTOMER_ROLES } from "./companyRoles"; interface OnboardingRoleSelectProps { - /** Currently selected profile types (e.g. ["importer"], ["importer","exporter"], ["freight_forwarder"]). */ + /** Currently selected profile types (e.g. ["importer"], ["importer","exporter","freight_forwarder"]). */ value: string[]; onChange: (next: string[]) => void; } /** * First (and only) thing shown in the Company Profile tab during onboarding. - * Importer / Exporter sit side by side and can both be picked; Freight - * Forwarder is a separate, mutually-exclusive choice below them. A valid - * selection reveals the company-profile fields. + * Importer / Exporter / Freight Forwarder are independent services that can be + * picked in any combination — each becomes its own profile (with its own + * business license) under the same company. A valid selection reveals the + * company-profile fields. */ export default function OnboardingRoleSelect({ value, onChange, }: OnboardingRoleSelectProps) { const selected = new Set(value); - const isForwarder = selected.has(FREIGHT_FORWARDER.type); - // Toggling a customer role drops any forwarder selection (mutually exclusive). - const toggleCustomerRole = (type: string) => { - const next = new Set(value.filter((t) => t !== FREIGHT_FORWARDER.type)); + const toggleRole = (type: string) => { + const next = new Set(value); if (next.has(type)) next.delete(type); else next.add(type); onChange([...next]); }; - const toggleForwarder = () => { - onChange(isForwarder ? [] : [FREIGHT_FORWARDER.type]); - }; - return ( @@ -41,7 +36,8 @@ export default function OnboardingRoleSelect({ What does your company do? - Pick Importer, Exporter, or both — or register as a Freight Forwarder. + Pick any combination of Importer, Exporter and Freight Forwarder — each + is set up with its own business license. @@ -52,26 +48,10 @@ export default function OnboardingRoleSelect({ description={role.description} icon={role.icon} selected={selected.has(role.type)} - onClick={() => toggleCustomerRole(role.type)} + onClick={() => toggleRole(role.type)} /> ))} - - - - ); } diff --git a/apps/edr-freight-web/portal/src/pages/settings/companyRoles.tsx b/apps/edr-freight-web/portal/src/pages/settings/companyRoles.tsx index d875d68af..58b9f18d1 100644 --- a/apps/edr-freight-web/portal/src/pages/settings/companyRoles.tsx +++ b/apps/edr-freight-web/portal/src/pages/settings/companyRoles.tsx @@ -28,8 +28,12 @@ export const FREIGHT_FORWARDER: RoleMeta = { icon: , }; -/** Importer / Exporter — the two roles a "customer" company can hold. */ -export const CUSTOMER_ROLES: RoleMeta[] = [IMPORTER, EXPORTER]; +/** + * Importer / Exporter / Freight Forwarder — the services a "customer" company + * can hold. A single company may register for any combination, each getting its + * own business license. + */ +export const CUSTOMER_ROLES: RoleMeta[] = [IMPORTER, EXPORTER, FREIGHT_FORWARDER]; // dj_freight_forwarder and transporter are intentionally not exposed yet. export function rolesForCompanyType(companyType: string): RoleMeta[] {