From 55103f5028b3e6c8cd22ce9f370efc15066c0c36 Mon Sep 17 00:00:00 2001 From: Sennay Date: Sat, 8 Aug 2026 10:53:28 +0300 Subject: [PATCH 1/5] modify docker compose --- docker-compose.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index e40d8b49e..5d5faa2de 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -3,7 +3,6 @@ # Copy apps/*/env.example to .env and set real values before `docker compose up`. # # Build: DOCKER_BUILDKIT=1 docker compose build -# Run: docker compose up -d services: freight-api: From 182787e143c2d1d723e59da3e16235d1f9817129 Mon Sep 17 00:00:00 2001 From: Nathnael Date: Sun, 9 Aug 2026 07:57:45 +0000 Subject: [PATCH 2/5] fix: gm email --- .../companies.fayda-identity.spec.ts | 87 +++++++++++++++++++ .../accounts/companyProfileForm/helpers.ts | 12 ++- .../companyProfileForm/schema.test.ts | 34 ++++++++ 3 files changed, 130 insertions(+), 3 deletions(-) diff --git a/apps/edr-freight-api/src/modules/companies/companies.fayda-identity.spec.ts b/apps/edr-freight-api/src/modules/companies/companies.fayda-identity.spec.ts index dd198b1c6..599187b22 100644 --- a/apps/edr-freight-api/src/modules/companies/companies.fayda-identity.spec.ts +++ b/apps/edr-freight-api/src/modules/companies/companies.fayda-identity.spec.ts @@ -354,6 +354,93 @@ describe("Fayda identity verification binds a person to the company", () => { ).resolves.toBeDefined(); }); + // Fayda's email and phone claims are optional and routinely absent. The owner + // is who the company is reached through and the step renders no input for + // their contact details, so the onboarding account — already OTP-proven — + // stands in rather than leaving the company unreachable. + describe("account contact details stand in for absent Fayda claims", () => { + const noContactClaims = { + purpose: "VERIFY", + verified: true, + sub: "new-sub", + fullName: "Haile Gebrselassie", + address: "Addis Ababa", + }; + const account = { + email: "account@example.com", + phoneNumber: "+251911777777", + }; + + it("falls back to the account for an owner Fayda gave no email or phone", async () => { + const { service, ctx } = makeService({ verification: noContactClaims }); + + await service.completeIdentityVerification( + "user-1", + { subject: "owner", code: "c", state: "s" }, + account, + ); + + expect(ctx.attributes.ownerEmail).toBe("account@example.com"); + expect(ctx.attributes.ownerPhone).toBe("+251911777777"); + }); + + it("prefers the Fayda claim over the account when there is one", async () => { + const { service, ctx } = makeService(); + + await service.completeIdentityVerification( + "user-1", + { subject: "owner", code: "c", state: "s" }, + account, + ); + + expect(ctx.attributes.ownerEmail).toBe("haile@example.com"); + expect(ctx.attributes.ownerPhone).toBe("+251922000000"); + }); + + it("leaves the PoA alone — the account is not that person", async () => { + const { service, ctx } = makeService({ verification: noContactClaims }); + + await service.completeIdentityVerification( + "user-1", + { subject: "poa", code: "c", state: "s" }, + account, + ); + + expect(ctx.attributes.poaEmail).toBeUndefined(); + expect(ctx.attributes.poaPhone).toBeUndefined(); + }); + + // Owners verified before the fallback existed hold blank contacts. Copying + // those blanks onto the GM makes generalManagerEmail required by onboarding + // with no field anywhere to satisfy it. + it("fills the GM copy from the account when the stored owner has no contacts", async () => { + const { service, ctx } = makeService({ + attributes: { ...OWNER_VERIFIED }, + }); + + await service.setGmSameAsOwner("user-1", account); + + expect(ctx.attributes.gmEmail).toBe("account@example.com"); + expect(ctx.attributes.generalManagerEmail).toBe("account@example.com"); + expect(ctx.attributes.generalManagerPhone).toBe("+251911777777"); + }); + + it("keeps the stored owner contacts when the GM copy has them", async () => { + const { service, ctx } = makeService({ + attributes: { + ...OWNER_VERIFIED, + ownerEmail: "abebe@example.com", + ownerPhone: "+251911000111", + }, + }); + + await service.setGmSameAsOwner("user-1", account); + + expect(ctx.attributes.generalManagerEmail).toBe("abebe@example.com"); + expect(ctx.attributes.generalManagerPhone).toBe("+251911000111"); + }); + }); + it("never locks or gates the general manager — it is not the verified subject", async () => { // GM is a plain typed role; the portal offers a "same as owner" copy, but // the backend must not treat it as identity-owned or require it verified. diff --git a/apps/edr-freight-web/portal/src/pages/accounts/companyProfileForm/helpers.ts b/apps/edr-freight-web/portal/src/pages/accounts/companyProfileForm/helpers.ts index 572627292..36c0072fb 100644 --- a/apps/edr-freight-web/portal/src/pages/accounts/companyProfileForm/helpers.ts +++ b/apps/edr-freight-web/portal/src/pages/accounts/companyProfileForm/helpers.ts @@ -139,10 +139,16 @@ export function stepPayload( }; } case "personnel": + // `|| undefined`, never "": the DTO's `@IsOptional()` only skips null and + // undefined, so an empty string is validated and 400s with + // "generalManagerEmail must be an email". An Ethiopian company never types + // these — the GM comes from the Fayda verification (or the "same as owner" + // declaration), so the form fields are legitimately blank and would fail a + // step that has no input to fix. return { - generalManagerName: d.generalManagerName, - generalManagerEmail: d.generalManagerEmail, - generalManagerPhone: d.generalManagerPhone, + generalManagerName: d.generalManagerName || undefined, + generalManagerEmail: d.generalManagerEmail || undefined, + generalManagerPhone: d.generalManagerPhone || undefined, }; case "contact": return { diff --git a/apps/edr-freight-web/portal/src/pages/accounts/companyProfileForm/schema.test.ts b/apps/edr-freight-web/portal/src/pages/accounts/companyProfileForm/schema.test.ts index eb5471861..6e459a6fb 100644 --- a/apps/edr-freight-web/portal/src/pages/accounts/companyProfileForm/schema.test.ts +++ b/apps/edr-freight-web/portal/src/pages/accounts/companyProfileForm/schema.test.ts @@ -123,6 +123,40 @@ describe("stepPayload (company)", () => { }); }); +describe("stepPayload (personnel)", () => { + // An Ethiopian company never types the GM — Fayda (or "same as owner") owns + // those fields — so the form holds "". `@IsOptional()` on the DTO skips only + // null/undefined, so an empty string is validated and comes back as + // "generalManagerEmail must be an email", on a step that renders no input. + it("omits blank GM fields instead of sending empty strings", () => { + const payload = stepPayload( + "personnel", + values({ + generalManagerName: "", + generalManagerEmail: "", + generalManagerPhone: "", + }), + ); + expect(payload.generalManagerName).toBeUndefined(); + expect(payload.generalManagerEmail).toBeUndefined(); + expect(payload.generalManagerPhone).toBeUndefined(); + }); + + it("still sends typed GM details (foreign company)", () => { + const payload = stepPayload( + "personnel", + values({ + generalManagerName: "Abebe Bikila", + generalManagerEmail: "gm@example.com", + generalManagerPhone: "+251911223344", + }), + ); + expect(payload.generalManagerName).toBe("Abebe Bikila"); + expect(payload.generalManagerEmail).toBe("gm@example.com"); + expect(payload.generalManagerPhone).toBe("+251911223344"); + }); +}); + describe("firstPresent", () => { it("skips empty strings rather than stopping at them", () => { expect(firstPresent("", " ", "second@example.com")).toBe( From ff9bb4954aa43b1977d682e3de347ebc0c77cc89 Mon Sep 17 00:00:00 2001 From: Nathnael Date: Sun, 9 Aug 2026 13:27:05 +0000 Subject: [PATCH 3/5] fix: the content document --- .../3370000000000-SupportHelpInlineMedia.ts | 91 +++++++ .../dto/support-content.dto.ts | 76 +----- .../support-content.service.spec.ts | 63 ++--- .../support-content.service.ts | 33 +-- .../src/pages/portal_content/AccordionRow.tsx | 95 ------- .../src/pages/portal_content/DocumentRail.tsx | 163 ++++++++++++ .../src/pages/portal_content/EditorPane.tsx | 98 +++++++ .../src/pages/portal_content/FaqEditor.tsx | 242 ------------------ .../src/pages/portal_content/FaqWorkspace.tsx | 227 ++++++++++++++++ .../src/pages/portal_content/HelpEditor.tsx | 125 --------- .../pages/portal_content/LegalDocEditor.tsx | 110 -------- .../src/pages/portal_content/Markdown.tsx | 89 ++++++- .../pages/portal_content/MarkdownEditor.tsx | 194 ++++++++------ .../src/pages/portal_content/MediaDialog.tsx | 230 +++++++++++++++++ .../src/pages/portal_content/MediaManager.tsx | 122 --------- .../portal_content/PortalContentPage.tsx | 119 ++++++++- .../pages/portal_content/SectionWorkspace.tsx | 107 ++++++++ .../pages/portal_content/version-preview.ts | 8 +- .../src/services/portal-content.service.ts | 11 +- .../portal/src/pages/support/DocShell.tsx | 25 +- .../portal/src/pages/support/DocSidebar.tsx | 117 +++++++++ .../portal/src/pages/support/FaqPage.tsx | 24 +- .../portal/src/pages/support/HelpPage.tsx | 115 ++++----- .../portal/src/pages/support/Markdown.tsx | 67 ++++- .../src/pages/support/PrivacyPolicyPage.tsx | 15 +- .../portal/src/pages/support/TermsPage.tsx | 17 +- .../src/pages/support/portal-content.test.ts | 7 +- .../src/pages/support/portal-content.ts | 17 +- .../src/freight/portal-content.defaults.ts | 17 +- packages/types/src/freight/portal-content.ts | 54 ++-- 30 files changed, 1624 insertions(+), 1054 deletions(-) create mode 100644 apps/edr-freight-api/src/migrations/3370000000000-SupportHelpInlineMedia.ts delete mode 100644 apps/edr-freight-web/backoffice/src/pages/portal_content/AccordionRow.tsx create mode 100644 apps/edr-freight-web/backoffice/src/pages/portal_content/DocumentRail.tsx create mode 100644 apps/edr-freight-web/backoffice/src/pages/portal_content/EditorPane.tsx delete mode 100644 apps/edr-freight-web/backoffice/src/pages/portal_content/FaqEditor.tsx create mode 100644 apps/edr-freight-web/backoffice/src/pages/portal_content/FaqWorkspace.tsx delete mode 100644 apps/edr-freight-web/backoffice/src/pages/portal_content/HelpEditor.tsx delete mode 100644 apps/edr-freight-web/backoffice/src/pages/portal_content/LegalDocEditor.tsx create mode 100644 apps/edr-freight-web/backoffice/src/pages/portal_content/MediaDialog.tsx delete mode 100644 apps/edr-freight-web/backoffice/src/pages/portal_content/MediaManager.tsx create mode 100644 apps/edr-freight-web/backoffice/src/pages/portal_content/SectionWorkspace.tsx create mode 100644 apps/edr-freight-web/portal/src/pages/support/DocSidebar.tsx diff --git a/apps/edr-freight-api/src/migrations/3370000000000-SupportHelpInlineMedia.ts b/apps/edr-freight-api/src/migrations/3370000000000-SupportHelpInlineMedia.ts new file mode 100644 index 000000000..3301056e4 --- /dev/null +++ b/apps/edr-freight-api/src/migrations/3370000000000-SupportHelpInlineMedia.ts @@ -0,0 +1,91 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +/** + * Folds each help section's `media[]` array into its markdown body. + * + * Attachments used to hang off the section as a separate list, rendered after + * the text — which meant an author could not put a picture next to the sentence + * it illustrates, and had two different places to manage media. They are now + * embedded with markdown's image syntax, and the renderer picks `` or + * `