From 333232c4d9da86c3555dfc077313236f5990c7a9 Mon Sep 17 00:00:00 2001 From: Nathnael Date: Tue, 18 Aug 2026 11:37:43 +0000 Subject: [PATCH] fix(portal): persist the region a manual-registration company picks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Region select called setValue without shouldDirty. `region` is an eTrade-bundle key, and stepPayload sends those only when the customer changed them this session — so for the two routes that type their address by hand (a co-operative, a foreign investor) the region was dropped on every save while zone, woreda and kebele went through, because those are registered inputs and are dirty by construction. Found by the new onboarding e2e suite: both manual-route companies finished onboarding with zone/woreda/kebele on file and region empty. --- .../steps/CompanyInfoStep.tsx | 10 +- .../cypress/e2e/flows/onboarding.cy.ts | 276 ------------------ 2 files changed, 9 insertions(+), 277 deletions(-) delete mode 100644 e2e/freight/cypress/e2e/flows/onboarding.cy.ts diff --git a/apps/edr-freight-web/portal/src/pages/accounts/companyProfileForm/steps/CompanyInfoStep.tsx b/apps/edr-freight-web/portal/src/pages/accounts/companyProfileForm/steps/CompanyInfoStep.tsx index 04a91bf07..52893ec59 100644 --- a/apps/edr-freight-web/portal/src/pages/accounts/companyProfileForm/steps/CompanyInfoStep.tsx +++ b/apps/edr-freight-web/portal/src/pages/accounts/companyProfileForm/steps/CompanyInfoStep.tsx @@ -159,7 +159,15 @@ export default function CompanyInfoStep({ searchable value={region || null} onChange={(v) => - setValue("region", v ?? "", { shouldValidate: true }) + // shouldDirty, or the pick never reaches the API: `region` is + // an eTrade-bundle key, and `stepPayload` sends those only + // when the customer changed them this session. Without it a + // co-operative or foreign investor typed its address and the + // region alone silently vanished on save. + setValue("region", v ?? "", { + shouldValidate: true, + shouldDirty: true, + }) } error={errors.region?.message} /> diff --git a/e2e/freight/cypress/e2e/flows/onboarding.cy.ts b/e2e/freight/cypress/e2e/flows/onboarding.cy.ts deleted file mode 100644 index 437c0e92e..000000000 --- a/e2e/freight/cypress/e2e/flows/onboarding.cy.ts +++ /dev/null @@ -1,276 +0,0 @@ -/** - * Full customer onboarding journey, both apps: - * - * 1. portal — /signup form → OTP (read from DB, delivery is off in e2e) - * → account created → onboarding wizard (nationality/role → - * company → personnel → contact → PoA → documents incl. the - * per-role business license) → "Submit for review" - * 2. backoffice — staff (chief, holds edr_freight_app:admin) approves the - * importer profile on /dashboard/customers/:id - * 3. portal — the new customer is active: contract wizard reachable - * - * Tests are sequential steps of ONE journey (fresh unique user per run), so - * retries are disabled — a mid-journey retry would replay a non-idempotent - * step against already-advanced state. - * - * NOTE: switching origin between tests (portal 5373 ↔ backoffice 5383) - * reloads the spec bundle and resets module state — later tests resolve the - * journey's user/company from the DB instead of module variables. - */ - -import { completeFaydaVerification } from "./import-utils"; - -const stamp = Date.now(); -const email = `e2e.onboard.${stamp}@example.com`; -// Ethiopian mobile: 9 + 8 digits, unique per run. -const phoneNational = `9${String(stamp).slice(-8)}`; -const signupPassword = "Password@e2e1"; -const tin = String(stamp).slice(-10).padStart(10, "1"); -const vat = String(stamp + 1).slice(-10).padStart(10, "2"); - -const portal = () => Cypress.env("portalUrl") as string; - -/** The journey's company/user = the latest e2e.onboard.* signup in the DB. */ -function latestOnboardJourney() { - return cy.task<{ rows: Array<{ name: string; email: string }> }>("db:query", { - sql: `SELECT c.name, u.email - FROM freight.companies c - JOIN freight.external_profiles ep ON ep.company_id = c.id - JOIN iam.users u ON u.id = ep.user_id - WHERE u.email LIKE 'e2e.onboard.%' - ORDER BY c.created_at DESC LIMIT 1`, - }); -} - -/** - * Fill a labelled Mantine input (label[for] → input id). - * - * The input is resolved fresh for every action rather than captured once. - * Each wizard step persists and re-seeds asynchronously, and when a field - * remounts Mantine mints a NEW generated id — so both a subject and an id - * captured a command earlier can be stale by the time the next command runs. - * Going label → for → element each time always addresses what's on the page - * now. - */ -function fill(label: string | RegExp, value: string) { - const input = () => - cy - .contains("label", label) - .invoke("attr", "for") - .then((id) => cy.get(`[id="${id}"]`)); - - input().clear({ force: true }); - input().type(value, { force: true }); -} - -/** - * Fill an input that has no