diff --git a/apps/edr-freight-web/portal/src/App.tsx b/apps/edr-freight-web/portal/src/App.tsx
index ae795a892..1403ac234 100644
--- a/apps/edr-freight-web/portal/src/App.tsx
+++ b/apps/edr-freight-web/portal/src/App.tsx
@@ -2,7 +2,6 @@ import { AppLayout, type SidebarItem } from "@/components/AppLayout";
import { useDisclosure } from "@mantine/hooks";
import {
CalendarCheck,
- Clock,
Home,
Layers,
Loader2,
@@ -112,13 +111,11 @@ function isOnboardingAllowedPath(pathname: string): boolean {
* as users who haven't completed onboarding.
*/
function OnboardingGate() {
- const { company, onboardingCompleted, companyStatus } = useAuth();
+ const { company, onboardingCompleted } = useAuth();
const location = useLocation();
const needsOnboarding = !company || !onboardingCompleted;
const allowedHere = isOnboardingAllowedPath(location.pathname);
- // Onboarding done but not yet approved by an admin → awaiting-approval state.
- const awaitingApproval = !needsOnboarding && companyStatus === "pending";
// Open by default while onboarding is pending (covers the login case).
const [wizardOpen, { open: openWizard, close: closeWizard }] =
@@ -148,9 +145,7 @@ function OnboardingGate() {
return (
<>
- {needsOnboarding && (
-
- )}
+ {needsOnboarding && }
{!needsOnboarding && }
{
} />
} />
{/* Profile was merged into Settings — keep old links working. */}
- } />
+ }
+ />
} />
} />
diff --git a/apps/edr-freight-web/portal/src/pages/MyPortalPage/MyPortalPage.tsx b/apps/edr-freight-web/portal/src/pages/MyPortalPage/MyPortalPage.tsx
index a8e37d44f..23cc908a6 100644
--- a/apps/edr-freight-web/portal/src/pages/MyPortalPage/MyPortalPage.tsx
+++ b/apps/edr-freight-web/portal/src/pages/MyPortalPage/MyPortalPage.tsx
@@ -20,7 +20,6 @@ export default function MyPortalPage() {
null,
);
const {
- customer,
companyProfiles,
bookingsQuery,
dashboardQuery,
@@ -105,9 +104,7 @@ export default function MyPortalPage() {
p.length > 4 ? `${p.slice(0, 7)}${"*".repeat(Math.max(0, p.length - 7))}` : p;
const onboardingSchema = z.object({
- companyFirstName: z.string().min(1, "First name is required"),
- companyLastName: z.string().min(1, "Last name is required"),
+ companyName: z.string().min(1, "Company name is required"),
companyEmail: z.string().email("Invalid email address"),
companyPhone: z
.string()
@@ -74,10 +73,7 @@ const onboardingSchema = z.object({
// Derived from the eTrade address parts (kebele/woreda/zone/region); no
// standalone input — the granular fields live in the registration section.
companyAddress: z.string().optional(),
- tinNumber: z
- .string()
- .length(10, "TIN must be exactly 10 digits")
- .regex(/^00\d{8}$/, "TIN must be 10 digits starting with 00"),
+ tinNumber: z.string().length(10, "TIN must be exactly 10 digits"),
vatNumber: z
.string()
.min(1, "VAT number is required")
@@ -97,12 +93,7 @@ const onboardingSchema = z.object({
kebele: z.string().min(1, "Kebele is required"),
houseNo: z.string().min(1, "House number is required"),
etradePhone: z.string().optional(),
- contactPersonFirstName: z
- .string()
- .min(1, "Contact person first name is required"),
- contactPersonLastName: z
- .string()
- .min(1, "Contact person last name is required"),
+ contactPersonName: z.string().min(1, "Contact person name is required"),
contactPersonPosition: z.string().optional(),
contactPersonEmail: z
.string()
@@ -119,8 +110,7 @@ const onboardingSchema = z.object({
.string()
.min(1, "Manager phone is required")
.refine(isValidPhone, "Enter a valid phone number"),
- poaFirstName: z.string().optional(),
- poaLastName: z.string().optional(),
+ poaName: z.string().optional(),
poaPhone: z
.string()
.optional()
@@ -134,8 +124,7 @@ type FormData = z.infer;
const stepFields: Record = {
company: [
- "companyFirstName",
- "companyLastName",
+ "companyName",
"companyEmail",
"companyPhone",
"companyLocation",
@@ -157,14 +146,12 @@ const stepFields: Record = {
"etradePhone",
],
personnel: [
- "generalManagerFirstName",
- "generalManagerLastName",
+ "generalManagerName",
"generalManagerEmail",
"generalManagerPhone",
],
contact: [
- "contactPersonFirstName",
- "contactPersonLastName",
+ "contactPersonName",
"contactPersonPosition",
"contactPersonEmail",
"contactPersonPhone",
@@ -175,23 +162,9 @@ const stepFields: Record = {
additional: [],
};
-/** Join first + last into the single name the API stores. */
-function joinName(first?: string, last?: string): string {
- return [first?.trim(), last?.trim()].filter(Boolean).join(" ");
-}
-
-/** Split a stored single name into first (first token) + last (the rest). */
-function splitName(full?: string | null): { first: string; last: string } {
- const trimmed = (full ?? "").trim();
- if (!trimmed) return { first: "", last: "" };
- const idx = trimmed.indexOf(" ");
- if (idx === -1) return { first: trimmed, last: "" };
- return { first: trimmed.slice(0, idx), last: trimmed.slice(idx + 1).trim() };
-}
-
function buildPayload(data: FormData, _user: AuthUser): CreateCompanyPayload {
return {
- companyName: joinName(data.companyFirstName, data.companyLastName),
+ companyName: data.companyName,
companyEmail: data.companyEmail,
companyPhone: data.companyPhone,
companyLocation: data.companyLocation,
@@ -200,20 +173,14 @@ function buildPayload(data: FormData, _user: AuthUser): CreateCompanyPayload {
vatNumber: data.vatNumber,
fanNumber: data.fanNumber,
attributes: {
- contactPersonName: joinName(
- data.contactPersonFirstName,
- data.contactPersonLastName,
- ),
+ contactPersonName: data.contactPersonName,
contactPersonPosition: data.contactPersonPosition || undefined,
contactPersonEmail: data.contactPersonEmail || undefined,
contactPersonPhone: data.contactPersonPhone,
- generalManagerName: joinName(
- data.generalManagerFirstName,
- data.generalManagerLastName,
- ),
+ generalManagerName: data.generalManagerName,
generalManagerEmail: data.generalManagerEmail,
generalManagerPhone: data.generalManagerPhone,
- poaName: joinName(data.poaFirstName, data.poaLastName) || undefined,
+ poaName: data.poaName || undefined,
poaPhone: data.poaPhone || undefined,
poaAddress: data.poaAddress || undefined,
poaEmail: data.poaEmail || undefined,
@@ -230,7 +197,7 @@ function stepPayload(
switch (step) {
case "company":
return {
- companyName: joinName(d.companyFirstName, d.companyLastName),
+ companyName: d.companyName,
companyEmail: d.companyEmail,
companyPhone: d.companyPhone,
companyLocation: d.companyLocation,
@@ -253,26 +220,20 @@ function stepPayload(
};
case "personnel":
return {
- generalManagerName: joinName(
- d.generalManagerFirstName,
- d.generalManagerLastName,
- ),
+ generalManagerName: d.generalManagerName,
generalManagerEmail: d.generalManagerEmail,
generalManagerPhone: d.generalManagerPhone,
};
case "contact":
return {
- contactPersonName: joinName(
- d.contactPersonFirstName,
- d.contactPersonLastName,
- ),
+ contactPersonName: d.contactPersonName,
contactPersonPosition: d.contactPersonPosition || undefined,
contactPersonEmail: d.contactPersonEmail || undefined,
contactPersonPhone: d.contactPersonPhone,
};
case "poa":
return {
- poaName: joinName(d.poaFirstName, d.poaLastName) || undefined,
+ poaName: d.poaName || undefined,
poaPhone: d.poaPhone || undefined,
poaEmail: d.poaEmail || undefined,
poaLocation: d.poaLocation || undefined,
@@ -287,13 +248,8 @@ function stepPayload(
function toFormValues(p: ProfileResponse): FormData {
// The draft placeholder TIN ("D…") shouldn't show as a real value.
const tin = p.tinNumber && !p.tinNumber.startsWith("D") ? p.tinNumber : "";
- const companyN = splitName(p.companyName);
- const contactN = splitName(p.contactPersonName);
- const gmN = splitName(p.generalManagerName);
- const poaN = splitName(p.poaName);
return {
- companyFirstName: companyN.first,
- companyLastName: companyN.last,
+ companyName: p.companyName ?? "",
companyEmail: p.companyEmail ?? "",
companyPhone: p.companyPhone ?? "",
companyLocation: p.companyLocation ?? "",
@@ -313,17 +269,14 @@ function toFormValues(p: ProfileResponse): FormData {
kebele: p.kebele ?? "",
houseNo: p.houseNo ?? "",
etradePhone: p.etradePhone ?? "",
- contactPersonFirstName: contactN.first,
- contactPersonLastName: contactN.last,
+ contactPersonName: p.contactPersonName ?? "",
contactPersonPosition: p.contactPersonPosition ?? "",
contactPersonEmail: p.contactPersonEmail ?? "",
contactPersonPhone: p.contactPersonPhone ?? "",
- generalManagerFirstName: gmN.first,
- generalManagerLastName: gmN.last,
+ generalManagerName: p.generalManagerName ?? "",
generalManagerEmail: p.generalManagerEmail ?? "",
generalManagerPhone: p.generalManagerPhone ?? "",
- poaFirstName: poaN.first,
- poaLastName: poaN.last,
+ poaName: p.poaName ?? "",
poaPhone: p.poaPhone ?? "",
poaAddress: p.poaAddress ?? "",
poaEmail: p.poaEmail ?? "",
@@ -438,8 +391,7 @@ export default function CompanyProfileForm({
} = useForm({
resolver: zodResolver(onboardingSchema),
defaultValues: {
- companyFirstName: "",
- companyLastName: "",
+ companyName: "",
companyEmail: "",
companyPhone: "",
companyLocation: "",
@@ -459,17 +411,14 @@ export default function CompanyProfileForm({
kebele: "",
houseNo: "",
etradePhone: "",
- contactPersonFirstName: "",
- contactPersonLastName: "",
+ contactPersonName: "",
contactPersonPosition: "",
contactPersonEmail: "",
contactPersonPhone: "",
- generalManagerFirstName: "",
- generalManagerLastName: "",
+ generalManagerName: "",
generalManagerEmail: "",
generalManagerPhone: "",
- poaFirstName: "",
- poaLastName: "",
+ poaName: "",
poaPhone: "",
poaAddress: "",
poaEmail: "",
@@ -510,19 +459,16 @@ export default function CompanyProfileForm({
}, [region, zone, woreda, kebele, houseNo]);
// The business owner/manager pulled from eTrade — powers "Use owner as
- // manager" on the General Manager step.
+ // manager" on the General Manager step. Null until a TIN lookup succeeds.
const [etradeOwner, setEtradeOwner] = useState<{
name: string;
phone: string;
- email?: string;
} | null>(null);
const handleETradeDataLoaded = (data: CompanyRegistrationData) => {
// Company name comes from the eTrade manager/owner name on the license.
if (data.managerName) {
- const { first, last } = splitName(data.managerName);
- setValue("companyFirstName", first, { shouldValidate: true });
- setValue("companyLastName", last, { shouldValidate: true });
+ setValue("companyName", data.managerName, { shouldValidate: true });
}
setValue("licenceNumber", data.licenceNumber);
setValue("statusDescription", data.statusDescription);
@@ -554,20 +500,13 @@ export default function CompanyProfileForm({
phone: toEthiopianE164(
data.managerPhone || data.regularPhone || data.mobilePhone,
),
- email: data.managerEmail || undefined,
});
};
/** Fill the General Manager from the eTrade business owner. */
- const toggleOwnerAsGm = (checked: boolean) => {
- setOwnerIsGm(checked);
- if (!checked || !etradeOwner) return;
- const { first, last } = splitName(etradeOwner.name);
- setValue("generalManagerFirstName", first, { shouldValidate: true });
- setValue("generalManagerLastName", last, { shouldValidate: true });
- setValue("generalManagerEmail", etradeOwner.email ?? "", {
- shouldValidate: true,
- });
+ const useOwnerAsManager = () => {
+ if (!etradeOwner) return;
+ setValue("generalManagerName", etradeOwner.name);
setValue("generalManagerPhone", etradeOwner.phone ?? "", {
shouldValidate: true,
});
@@ -797,23 +736,15 @@ export default function CompanyProfileForm({
+
First Name *>}
- placeholder="Global"
- error={errors.companyFirstName?.message}
- {...register("companyFirstName")}
- />
- Last Name *>}
- placeholder="Logistics Ltd"
- error={errors.companyLastName?.message}
- {...register("companyLastName")}
- />
-
-
- Company Email *>}
+ label="Company Email"
type="email"
placeholder="ops@company.com"
error={errors.companyEmail?.message}
@@ -827,21 +758,21 @@ export default function CompanyProfileForm({
/>
Location *>}
+ label="Location"
placeholder="Addis Ababa, Ethiopia"
error={errors.companyLocation?.message}
{...register("companyLocation")}
/>
VAT Number *>}
+ label="VAT Number"
placeholder="VAT-12345"
maxLength={10}
error={errors.vatNumber?.message}
{...register("vatNumber")}
/>
FAN Number (16 digits) *>}
+ label="FAN Number (16 digits)"
placeholder="1234567890123456"
maxLength={16}
error={errors.fanNumber?.message}
@@ -944,33 +875,31 @@ export default function CompanyProfileForm({
{step === "personnel" && (
<>
-
- General Manager
-
- toggleOwnerAsGm(e.currentTarget.checked)}
+
+
+ General Manager
+
+ {etradeOwner && (
+ }
+ onClick={useOwnerAsManager}
+ >
+ Use owner as manager
+
+ )}
+
+
First Name *>}
- placeholder="Abebe"
- error={errors.generalManagerFirstName?.message}
- {...register("generalManagerFirstName")}
- />
- Last Name *>}
- placeholder="Bikila"
- error={errors.generalManagerLastName?.message}
- {...register("generalManagerLastName")}
- />
-
-
- Email *>}
+ label="Email"
type="email"
placeholder="gm@company.com"
error={errors.generalManagerEmail?.message}
@@ -1017,25 +946,19 @@ export default function CompanyProfileForm({
First Name *>}
- placeholder="Jane"
- error={errors.contactPersonFirstName?.message}
- {...register("contactPersonFirstName")}
+ label="Name"
+ placeholder="Jane Smith"
+ error={errors.contactPersonName?.message}
+ {...register("contactPersonName")}
/>
- Last Name *>}
- placeholder="Smith"
- error={errors.contactPersonLastName?.message}
- {...register("contactPersonLastName")}
- />
-
-
+
+
-
-
({ value: f.key, label: f.label }));
+const SELECT_DATA = STATUS_FILTERS.map((f) => ({
+ value: f.key,
+ label: f.label,
+}));
// ── Summary stat cards (clickable lifecycle filters) ──────────────────────────
@@ -96,42 +106,42 @@ const STAT_CARDS: Array<{
iconBg: string;
iconColor: string;
}> = [
- {
- key: "all",
- label: "All bookings",
- icon: LayoutList,
- iconBg: "#ECF6F1",
- iconColor: "#0A8A5F",
- },
- {
- key: "active",
- label: "In progress",
- icon: Package,
- iconBg: "#FDF3E0",
- iconColor: "#C77F09",
- },
- {
- key: "payment",
- label: "Awaiting payment",
- icon: Wallet,
- iconBg: "#FEF6E6",
- iconColor: "#F2A516",
- },
- {
- key: "draft",
- label: "Drafts",
- icon: FileEdit,
- iconBg: "#F1F4F7",
- iconColor: "#475569",
- },
- {
- key: "done",
- label: "Completed",
- icon: CheckCircle2,
- iconBg: "#ECF6F1",
- iconColor: "#0A8A5F",
- },
-];
+ {
+ key: "all",
+ label: "All bookings",
+ icon: LayoutList,
+ iconBg: "#ECF6F1",
+ iconColor: "#0A8A5F",
+ },
+ {
+ key: "active",
+ label: "In progress",
+ icon: Package,
+ iconBg: "#FDF3E0",
+ iconColor: "#C77F09",
+ },
+ {
+ key: "payment",
+ label: "Awaiting payment",
+ icon: Wallet,
+ iconBg: "#FEF6E6",
+ iconColor: "#F2A516",
+ },
+ {
+ key: "draft",
+ label: "Drafts",
+ icon: FileEdit,
+ iconBg: "#F1F4F7",
+ iconColor: "#475569",
+ },
+ {
+ key: "done",
+ label: "Completed",
+ icon: CheckCircle2,
+ iconBg: "#ECF6F1",
+ iconColor: "#0A8A5F",
+ },
+ ];
// ── Status badge (reuses the shared portal status config) ─────────────────────
@@ -139,8 +149,12 @@ function StatusBadge({ status }: { status: string }) {
const cfg = STATUS_CONFIG[status];
const label = cfg?.badgeLabel ?? status.replace(/_/g, " ");
const bg = cfg ? `var(--mantine-color-${cfg.badgeBg}-0, #F1F4F7)` : "#F1F4F7";
- const text = cfg ? `var(--mantine-color-${cfg.badgeText}-7, #475569)` : "#475569";
- const dot = cfg ? `var(--mantine-color-${cfg.badgeDot}-6, #94A3B8)` : "#94A3B8";
+ const text = cfg
+ ? `var(--mantine-color-${cfg.badgeText}-7, #475569)`
+ : "#475569";
+ const dot = cfg
+ ? `var(--mantine-color-${cfg.badgeDot}-6, #94A3B8)`
+ : "#94A3B8";
return (
}
- style={{ backgroundColor: "var(--mantine-color-edr-ink-0)", color: "#fff" }}
+ style={{
+ backgroundColor: "var(--mantine-color-edr-ink-0)",
+ color: "#fff",
+ }}
onClick={go}
>
Continue
@@ -220,7 +237,14 @@ function PrimaryAction({
return ;
}
return (
-