diff --git a/apps/edr-freight-web/portal/src/pages/accounts/SignupPage.tsx b/apps/edr-freight-web/portal/src/pages/accounts/SignupPage.tsx
index 43f1abb21..bfb175895 100644
--- a/apps/edr-freight-web/portal/src/pages/accounts/SignupPage.tsx
+++ b/apps/edr-freight-web/portal/src/pages/accounts/SignupPage.tsx
@@ -20,9 +20,11 @@ import {
const userSchema = z.object({
email: z.string().email("Invalid email address"),
- username: z.string().min(3, "Username must be at least 3 characters"),
countryCode: z.string().min(1, "Country code is required"),
- phone: z.string().min(9, "Phone number is too short").max(9, "Phone number is too long"),
+ phone: z
+ .string()
+ .min(9, "Phone number is too short")
+ .max(9, "Phone number is too long"),
userType: z.string(),
name: z.object({
en: z.string().min(2, "Name is required"),
@@ -46,7 +48,6 @@ export default function SignupPage() {
resolver: zodResolver(userSchema),
defaultValues: {
email: "",
- username: "",
countryCode: "+251",
phone: "",
userType: userType.individual,
@@ -58,10 +59,12 @@ export default function SignupPage() {
setError(null);
setLoading(true);
try {
- const normalizedPhone = data.phone.startsWith("0") ? data.phone.slice(1) : data.phone;
+ const normalizedPhone = data.phone.startsWith("0")
+ ? data.phone.slice(1)
+ : data.phone;
const payload: SignupPayload = {
email: data.email,
- username: data.username,
+ username: data.email,
phoneNumber: `${data.countryCode}${normalizedPhone}`,
userType: data.userType,
name: { en: data.name.en, am: data.name.am ?? "" },
@@ -92,7 +95,12 @@ export default function SignupPage() {
"Enterprise-grade operations",
"Multi-corridor freight monitoring",
],
- stats: { label: "Active Corridors", value: "24+", footer: "Operational", progress: "w-[95%]" },
+ stats: {
+ label: "Active Corridors",
+ value: "24+",
+ footer: "Operational",
+ progress: "w-[95%]",
+ },
}}
>
@@ -125,31 +133,17 @@ export default function SignupPage() {
-
-
- Username
-
-
-
-
-
- Email Address
-
-
-
-
+
+ Email Address
+
+
+
-
+
{loading ? (
<>
diff --git a/apps/edr-freight-web/portal/src/pages/bookings/NewBookingPage.tsx b/apps/edr-freight-web/portal/src/pages/bookings/NewBookingPage.tsx
index 04728e0a2..7792c712a 100644
--- a/apps/edr-freight-web/portal/src/pages/bookings/NewBookingPage.tsx
+++ b/apps/edr-freight-web/portal/src/pages/bookings/NewBookingPage.tsx
@@ -50,8 +50,6 @@ export default function NewBookingPage() {
const originYard = form.watch("originYard");
const destinationYard = form.watch("destinationYard");
const containers = form.watch("containers");
- const contractType = form.watch("contractType");
- const previousContractRef = form.watch("previousContractRef");
const direction = useMemo(
() => getRouteDirection(originYard, destinationYard),
@@ -63,8 +61,6 @@ export default function NewBookingPage() {
return calcWagons(containers);
}, [containers]);
- const renewalValid = contractType === "renewal" && previousContractRef !== "";
-
async function handleContinue() {
const valid = await form.trigger(stepFields[step], { shouldFocus: true });
if (!valid) return;
@@ -101,16 +97,22 @@ export default function NewBookingPage() {
data.contractType.toUpperCase() as CreateBookingPayload["contractType"],
previousContractId: data.previousContractRef || undefined,
serviceType:
- data.serviceType === "rail" ? "RAIL_ONLY" : "RAIL_AND_FORWARDING",
- firstMileEnabled: data.firstMile.enabled,
- firstMilePickupAddress: data.firstMile.pickUpAddress ?? undefined,
- lastMileEnabled: data.lastMile.enabled,
- lastMileDeliveryAddress: data.lastMile.deliveryAddress ?? undefined,
- equipmentReturn:
- data.equipmentReturn === "with_return"
- ? ("WITH_RETURN" as const)
- : ("WITHOUT_RETURN" as const),
- customsClearingEnabled: data.customsClearingEnabled,
+ data.service.serviceType === "rail"
+ ? "RAIL_ONLY"
+ : "RAIL_AND_FORWARDING",
+ ...(data.service.serviceType === "rail"
+ ? {}
+ : {
+ firstMileEnabled: data.firstMile.enabled,
+ firstMilePickupAddress: data.firstMile.pickUpAddress ?? undefined,
+ lastMileEnabled: data.lastMile.enabled,
+ lastMileDeliveryAddress: data.lastMile.deliveryAddress ?? undefined,
+ equipmentReturn:
+ data.equipmentReturn === "with_return"
+ ? ("WITH_RETURN" as const)
+ : ("WITHOUT_RETURN" as const),
+ customsClearingEnabled: data.customsClearingEnabled,
+ }),
originStation: data.originYard,
destinationStation: data.destinationYard,
cargoTotalWeightVgm: totalWeight,
@@ -184,12 +186,7 @@ export default function NewBookingPage() {
)}
{step === 5 && (
-
+
)}
diff --git a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/schema.ts b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/schema.ts
index dbc979595..4da8ed6ef 100644
--- a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/schema.ts
+++ b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/schema.ts
@@ -1,3 +1,4 @@
+import { DeepPartial, Path } from "react-hook-form";
import * as z from "zod";
export const STATIONS = [
@@ -82,36 +83,26 @@ export const bookingFormSchema = z
serviceType: z.enum(["rail", "rail_forwarding"], "Select a service type."),
firstMile: z
.object({
- enabled: z.boolean(),
+ enabled: z.boolean().default(false),
pickUpAddress: z.string(),
})
- .refine(
- (data) => {
- console.log(data);
- return !(data.enabled && !data.pickUpAddress.trim());
- },
- {
- message: "Enter the pick-up address.",
- path: ["pickUpAddress"],
- },
- ),
+ .refine((data) => !(data.enabled && !data.pickUpAddress.trim()), {
+ message: "Enter the pick-up address.",
+ path: ["pickUpAddress"],
+ }),
lastMile: z
.object({
- enabled: z.boolean(),
+ enabled: z.boolean().default(false),
deliveryAddress: z.string(),
})
- .refine(
- (data) => {
- console.log(data);
- return !(data.enabled && !data.deliveryAddress.trim());
- },
- {
- message: "Enter the delivery address.",
- path: ["deliveryAddress"],
- },
- ),
- equipmentReturn: z.enum(["with_return", "without_return"]),
- customsClearingEnabled: z.boolean(),
+ .refine((data) => !(data.enabled && !data.deliveryAddress.trim()), {
+ message: "Enter the delivery address.",
+ path: ["deliveryAddress"],
+ }),
+ equipmentReturn: z
+ .enum(["with_return", "without_return"])
+ .default("with_return"),
+ customsClearingEnabled: z.boolean().default(false),
originYard: z.string().min(1, "Select an origin yard."),
destinationYard: z.string().min(1, "Select a destination yard."),
shippingLine: z.string(),
@@ -259,8 +250,9 @@ export const bookingFormSchema = z
export type BookingFormValues = z.infer;
-export const initialBookingFormValues: Partial = {
+export const initialBookingFormValues: DeepPartial = {
previousContractRef: "",
+
firstMile: {
enabled: false,
pickUpAddress: "",
@@ -287,7 +279,7 @@ export const initialBookingFormValues: Partial = {
termsAccepted: false,
};
-export const stepFields: Record> = {
+export const stepFields: Record>> = {
1: ["contractType", "previousContractRef"],
2: [
"serviceType",
diff --git a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step2-service-type.tsx b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step2-service-type.tsx
index 07e1638f3..e9bd1d331 100644
--- a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step2-service-type.tsx
+++ b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step2-service-type.tsx
@@ -1,3 +1,4 @@
+import { useEffect, useRef } from "react";
import { Controller, type UseFormReturn } from "react-hook-form";
import { FileText, Package, Train, Truck } from "lucide-react";
import { Badge, Field, FieldError, Input, Switch } from "@edr/ui-common";
@@ -11,6 +12,59 @@ export function Step2ServiceType({ form }: { form: BookingForm }) {
const firstMileEnabled = form.watch("firstMile.enabled");
const lastMileEnabled = form.watch("lastMile.enabled");
+ const prevServiceType = useRef(serviceType);
+
+ useEffect(() => {
+ const prev = prevServiceType.current;
+ prevServiceType.current = serviceType;
+
+ if (!prev || prev === serviceType) return;
+
+ if (serviceType === "rail") {
+ form.setValue(
+ "firstMile",
+ { enabled: false, pickUpAddress: "" },
+ { shouldDirty: true, shouldValidate: true },
+ );
+ form.setValue(
+ "lastMile",
+ { enabled: false, deliveryAddress: "" },
+ { shouldDirty: true, shouldValidate: true },
+ );
+ form.setValue("equipmentReturn", "with_return", {
+ shouldDirty: true,
+ });
+ form.setValue("customsClearingEnabled", false, {
+ shouldDirty: true,
+ });
+ } else if (serviceType === "rail_forwarding") {
+ form.setValue(
+ "firstMile",
+ {
+ enabled: false,
+ pickUpAddress: "",
+ },
+ {
+ shouldDirty: false,
+ shouldValidate: false,
+ },
+ );
+ form.setValue(
+ "lastMile",
+ {
+ enabled: false,
+ deliveryAddress: "",
+ },
+ {
+ shouldDirty: false,
+ shouldValidate: false,
+ },
+ );
+ }
+ }, [serviceType, form]);
+
+ const showServiceSections = serviceType === "rail_forwarding";
+
return (
-
- Rail Transport & Freight Forwarding
-
+ Logistics
Rail transport plus documentation, customs liaison, and a
dedicated coordinator.
@@ -65,164 +117,172 @@ export function Step2ServiceType({ form }: { form: BookingForm }) {
)}
/>
-
-
-
(
-
-
-
-
-
First Mile - Pick-up
-
- Truck pick-up from your premises (Door to Port) to the
- origin rail yard.
-
-
-
-
{
- field.onChange(value);
- if (!value) {
- form.setValue("firstMile.pickUpAddress", "", {
- shouldDirty: true,
- shouldValidate: true,
- });
- }
- }}
- />
-
- )}
- />
- {firstMileEnabled && (
- (
-
-
-
-
- )}
- />
- )}
-
-
-
-
(
-
-
-
-
-
Last Mile - Delivery
-
- Truck delivery from the destination rail yard to the final
- address (Port to Door).
-
-
-
-
{
- field.onChange(value);
- if (!value) {
- form.setValue("lastMile.deliveryAddress", "", {
- shouldDirty: true,
- shouldValidate: true,
- });
- form.setValue("equipmentReturn", "with_return", {
- shouldDirty: true,
- });
- }
- }}
- />
-
- )}
- />
- {lastMileEnabled && (
- (
-
-
-
-
- )}
- />
- )}
-
-
- {lastMileEnabled && (
+ {showServiceSections && (
+
(
+
-
Equipment Return
+
+ First Mile - Pick-up
+
- {field.value === "with_return"
- ? "Container returned to EDR after unloading."
- : "Container retained by the customer after delivery."}
+ Truck pick-up from your premises (Door to Port) to the
+ origin rail yard.
{
- field.onChange(value ? "with_return" : "without_return");
+ field.onChange(value);
+ if (!value) {
+ form.setValue("firstMile.pickUpAddress", "", {
+ shouldDirty: true,
+ shouldValidate: true,
+ });
+ }
}}
/>
)}
/>
-
- )}
-
-
-
(
-
-
-
-
-
- Customs Clearing Service
-
-
- EDR handles customs documentation and clearance on your
- behalf.
-
-
-
-
-
+ {firstMileEnabled && (
+ (
+
+
+
+
+ )}
+ />
)}
- />
+
+
+
+
(
+
+
+
+
+
+ Last Mile - Delivery
+
+
+ Truck delivery from the destination rail yard to the
+ final address (Port to Door).
+
+
+
+
{
+ field.onChange(value);
+ if (!value) {
+ form.setValue("lastMile.deliveryAddress", "", {
+ shouldDirty: true,
+ shouldValidate: true,
+ });
+ form.setValue("equipmentReturn", "with_return", {
+ shouldDirty: true,
+ });
+ }
+ }}
+ />
+
+ )}
+ />
+ {lastMileEnabled && (
+ (
+
+
+
+
+ )}
+ />
+ )}
+
+
+ {lastMileEnabled && (
+
+
(
+
+
+
+
Equipment Return
+
+ {field.value === "with_return"
+ ? "Container returned to EDR after unloading."
+ : "Container retained by the customer after delivery."}
+
+
+
+
{
+ field.onChange(
+ value ? "with_return" : "without_return",
+ );
+ }}
+ />
+
+ )}
+ />
+
+ )}
+
+
+
(
+
+
+
+
+
+ Customs Clearing Service
+
+
+ EDR handles customs documentation and clearance on your
+ behalf.
+
+
+
+
+
+ )}
+ />
+
-
+ )}
);
}
diff --git a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step5-cargo-details.tsx b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step5-cargo-details.tsx
index 7e017efbb..1898feb73 100644
--- a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step5-cargo-details.tsx
+++ b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step5-cargo-details.tsx
@@ -1,13 +1,6 @@
import { Controller, useFieldArray, type UseFormReturn } from "react-hook-form";
import { MapPin, Package, Plus, Trash2, Weight } from "lucide-react";
-import {
- Button,
- Field,
- FieldError,
- FieldLabel,
- Input,
- Separator,
-} from "@edr/ui-common";
+import { Button, Field, FieldError, FieldLabel, Input } from "@edr/ui-common";
import {
BREAK_BULK_TYPES,
BULK_COMMODITIES,
@@ -83,7 +76,6 @@ export function Step5CargoDetails({
form.setValue("freightType", undefined, {
shouldDirty: true,
});
- form.setValue("cargoWeight", "", { shouldDirty: true });
}}
>
@@ -98,18 +90,7 @@ export function Step5CargoDetails({
selected={cargoType === "bulk"}
onClick={() => {
field.onChange("bulk");
- form.setValue(
- "containers",
- [
- {
- type: "20ft",
- containerType: "",
- qty: "1",
- vgm: "",
- },
- ],
- { shouldDirty: true },
- );
+ form.setValue("containers", [], { shouldDirty: true });
}}
>
@@ -127,143 +108,137 @@ export function Step5CargoDetails({
/>
+
+
Weight
+
(
+
+
+ Total Cargo Weight(Tons)*
+
+
+
+
+
+
+
+ )}
+ />
+
{cargoType === "bulk" && (
- <>
-
-
-
Freight Type *
-
(
-
-
-
field.onChange("bulk")}
- >
- Bulk
-
- Coffee, fertilizer, grain, ore, etc.
-
-
-
field.onChange("break_bulk")}
- >
- Break-Bulk
-
- Machinery, vehicles, project cargo, etc.
-
-
-
-
-
- )}
- />
+
+
Freight Type *
+
(
+
+
+
field.onChange("bulk")}
+ >
+ Bulk
+
+ Coffee, fertilizer, grain, ore, etc.
+
+
+
field.onChange("break_bulk")}
+ >
+ Break-Bulk
+
+ Machinery, vehicles, project cargo, etc.
+
+
+
+
+
+ )}
+ />
- {freightType === "bulk" && (
-
+ )}
- {freightType === "break_bulk" && (
-
-
-
-
-
-
Weight
-
(
-
-
- Total Cargo Weight - VGM (Tons) *
-
-
-
-
-
-
-
)}
- />
-
- >
+
+ )}
+
)}
{cargoType === "container" && (
@@ -407,7 +382,7 @@ export function Step5CargoDetails({
control={form.control}
render={({ field: vgmField, fieldState }) => (
- VGM (Tons) *
+ Tons*
vgmField.onChange(e.target.value)}
diff --git a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step8-review.tsx b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step8-review.tsx
index 196aac9ad..6f72dc63f 100644
--- a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step8-review.tsx
+++ b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step8-review.tsx
@@ -22,12 +22,10 @@ type BookingForm = UseFormReturn;
export function Step8Review({
form,
setStep,
- wagons,
direction,
}: {
form: BookingForm;
setStep: (step: number) => void;
- wagons: WagonCalcResult | null;
direction: RouteDirection;
}) {
const values = form.watch();
@@ -102,11 +100,6 @@ export function Step8Review({
value={values.contractType === "new" ? "New Contract" : "Renewal"}
target={1}
/>
-
0 ? `${totalVgm.toFixed(1)} tons` : ""}
target={4}
/>
- 1 ? "s" : ""}`
- : ""
- }
- target={4}
- />