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 + + + -
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 === "bulk" && ( +
+ ( + + + + )} + /> + {bulkCommodity === "Others" && ( ( - - - + + + + )} /> - {bulkCommodity === "Others" && ( - ( - - - - - )} - /> - )} -
- )} + )} +
+ )} - {freightType === "break_bulk" && ( -
+ {freightType === "break_bulk" && ( +
+ ( + + + + )} + /> + {breakBulkType === "Others" && ( ( - - - + + + + )} /> - {breakBulkType === "Others" && ( - ( - - - - - )} - /> - )} -
- )} -
- - - -
- 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} - />