+
}
title="Contract Type"
- description="New contract or renewal of an existing one."
+ description="Start a new contract or renew an existing one to reuse its details."
/>
(
-
+
}
+ iconBg="#ECF6F1"
+ iconColor="#0A6F4D"
+ title="New Contract"
+ description="Create a fresh freight contract from scratch."
onClick={() => {
field.onChange("new");
form.clearErrors(["contractType", "previousContractRef"]);
form.setValue("previousContractRef", "");
}}
- >
-
-
-
-
New Contract
-
- Create a new contract.
-
-
+ />
}
+ iconBg="#EAF1FB"
+ iconColor="#2E5B96"
+ title="Contract Renewal"
+ description="Pick a previous reference to auto-fill historical parameters."
onClick={() => {
field.onChange("renewal");
form.clearErrors("contractType");
}}
- >
-
-
-
-
Contract Renewal
-
- Select a previous reference to auto-populate historical
- parameters.
-
-
+ />
@@ -234,7 +230,7 @@ export function Step1ContractType({
/>
{contractType === "renewal" && (
-
+
{error && (
Failed to load previous contracts. Please try again later.
@@ -263,8 +259,8 @@ export function Step1ContractType({
details will be pre-filled.
)}
-
+
)}
-
+
);
}
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 e01e2f879..20f63812f 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,9 +1,17 @@
-import { Switch, TextInput } from "@mantine/core";
-import { FileText, Train, Truck } from "lucide-react";
+import { Box, Group, Stack, Switch, Text, TextInput } from "@mantine/core";
+import type { ReactNode } from "react";
+import { FileText, Layers, Train, Truck } from "lucide-react";
import { useEffect, useRef } from "react";
import { Controller, type UseFormReturn } from "react-hook-form";
import { BookingFormInputValues, type BookingFormValues } from "./schema";
-import { OptionCard, OptionFieldError, StepHeader } from "./shared";
+import {
+ fieldStyles,
+ OptionCard,
+ OptionFieldError,
+ StepCard,
+ StepHeader,
+ StepLabel,
+} from "./shared";
import type { Freight } from "@edr/types";
@@ -61,10 +69,11 @@ export function Step2ServiceType({
const showServiceSections =
includesCustoms || includesFirstMile || includesLastMile;
return (
-
+
}
title="Service Type"
- description="Select the service combination and configure trucking options."
+ description="Choose the service combination, then configure your trucking options."
/>
(
-
+
{referenceData?.service
.filter((s) => s.canBeBookedAlone)
- .map((s) => {
- return (
-
field.onChange(s.id)}
- >
-
-
-
- {s.serviceName}
-
- {s.description}
-
-
- );
- })}
+ .map((s) => (
+
field.onChange(s.id)}
+ icon={}
+ iconBg="#EEF0FB"
+ iconColor="#4F46E5"
+ title={s.serviceName}
+ description={s.description}
+ />
+ ))}
@@ -98,185 +103,201 @@ export function Step2ServiceType({
/>
{showServiceSections && (
-
+
+ Trucking & customs options
{/* First Mile */}
{includesFirstMile && (
-
-
(
-
-
-
-
-
- First Mile — Pick-up
-
-
- Truck pick-up from your premises (Door to Port) to the
- origin rail yard.
-
-
-
-
{
- const value = e.currentTarget.checked;
- field.onChange(value);
- if (!value) {
- form.setValue("firstMile.pickUpAddress", "", {
- shouldDirty: true,
- shouldValidate: true,
- });
- }
- }}
- color="edr-green"
- />
-
- )}
- />
- {firstMileEnabled && (
- (
- (
+ }
+ title="First Mile — Pick-up"
+ description="Truck pick-up from your premises (Door to Port) to the origin rail yard."
+ checked={field.value}
+ onChange={(value) => {
+ field.onChange(value);
+ if (!value) {
+ form.setValue("firstMile.pickUpAddress", "", {
+ shouldDirty: true,
+ shouldValidate: true,
+ });
+ }
+ }}
+ >
+ {firstMileEnabled && (
+ (
+
+ )}
/>
)}
- />
+
)}
-
+ />
)}
{/* Last Mile */}
{includesLastMile && (
-
-
(
-
-
-
-
-
- Last Mile — Delivery
-
-
- Truck delivery from the destination rail yard to the
- final address (Port to Door).
-
-
-
-
{
- const value = e.currentTarget.checked;
- field.onChange(value);
- if (!value) {
- form.setValue("lastMile.deliveryAddress", "", {
- shouldDirty: true,
- shouldValidate: true,
- });
- form.setValue("equipmentReturn", "with_return", {
- shouldDirty: true,
- });
- }
- }}
- color="edr-green"
- />
-
- )}
- />
- {lastMileEnabled && (
- (
- (
+ }
+ title="Last Mile — Delivery"
+ description="Truck delivery from the destination rail yard to the final address (Port to Door)."
+ checked={field.value}
+ onChange={(value) => {
+ field.onChange(value);
+ if (!value) {
+ form.setValue("lastMile.deliveryAddress", "", {
+ shouldDirty: true,
+ shouldValidate: true,
+ });
+ form.setValue("equipmentReturn", "with_return", {
+ shouldDirty: true,
+ });
+ }
+ }}
+ >
+ {lastMileEnabled && (
+ (
+
+ )}
/>
)}
- />
+
)}
-
+ />
)}
{/* Equipment Return */}
{includesLastMile && lastMileEnabled && (
-
-
(
-
-
-
Equipment Return
-
- {field.value === "with_return"
- ? "Container returned to EDR after unloading."
- : "Container retained by the customer after delivery."}
-
-
-
{
- field.onChange(
- e.currentTarget.checked
- ? "with_return"
- : "without_return",
- );
- }}
- color="edr-green"
- />
-
- )}
- />
-
+ (
+ }
+ title="Equipment Return"
+ description={
+ field.value === "with_return"
+ ? "Container returned to EDR after unloading."
+ : "Container retained by the customer after delivery."
+ }
+ checked={field.value === "with_return"}
+ onChange={(v) =>
+ field.onChange(v ? "with_return" : "without_return")
+ }
+ />
+ )}
+ />
)}
{/* Customs Clearing */}
{includesCustoms && (
-
-
(
-
-
-
-
-
- Customs Clearing Service
-
-
- EDR handles customs documentation and clearance on
- your behalf.
-
-
-
-
field.onChange(e.currentTarget.checked)}
- color="edr-green"
- />
-
- )}
- />
-
+ (
+ }
+ title="Customs Clearing Service"
+ description="EDR handles customs documentation and clearance on your behalf."
+ checked={field.value}
+ onChange={(v) => field.onChange(v)}
+ />
+ )}
+ />
)}
-
+
)}
-
+
+ );
+}
+
+function ServiceToggle({
+ icon,
+ title,
+ description,
+ checked,
+ onChange,
+ children,
+}: {
+ icon: ReactNode;
+ title: string;
+ description: string;
+ checked: boolean;
+ onChange: (v: boolean) => void;
+ children?: ReactNode;
+}) {
+ return (
+
+
+
+
+ {icon}
+
+
+
+ {title}
+
+
+ {description}
+
+
+
+ onChange(e.currentTarget.checked)}
+ color="edr-green"
+ size="md"
+ style={{ flexShrink: 0 }}
+ />
+
+ {children}
+
);
}
diff --git a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step4-route.tsx b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step4-route.tsx
index e9d4a387e..dd5df7d9c 100644
--- a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step4-route.tsx
+++ b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step4-route.tsx
@@ -1,6 +1,6 @@
import type { Freight } from "@edr/types";
-import { Divider, Skeleton, Stack, Switch } from "@mantine/core";
-import { Flame, MapPin, Snowflake } from "lucide-react";
+import { Box, Divider, Group, Skeleton, Stack, Switch, Text } from "@mantine/core";
+import { Flame, MapPin, Route as RouteIcon, Snowflake } from "lucide-react";
import { useEffect, useMemo } from "react";
import { Controller, type UseFormReturn } from "react-hook-form";
import {
@@ -8,7 +8,7 @@ import {
type BookingFormValues,
getRouteDirection,
} from "./schema";
-import { SelectField, StepHeader, StepLabel } from "./shared";
+import { SelectField, StepCard, StepHeader, StepLabel } from "./shared";
type BookingForm = UseFormReturn<
BookingFormInputValues,
@@ -63,7 +63,6 @@ export function Step4Route({
const origin = referenceData?.yard.find((y) => y.id === originYard);
const dest = referenceData?.yard.find((y) => y.id === destinationYard);
const direction = getRouteDirection(origin, dest);
- console.log({ yardOptions, originYard, destinationYard, direction, origin, dest });
const directionStyle: Record
= {
EXPORT: "bg-sky-50 text-sky-800 border-sky-200",
@@ -85,10 +84,11 @@ export function Step4Route({
const stationSelectDisabled = yardOptions.length === 0;
return (
-
+
}
title="Route"
- description="Select the origin and destination yards."
+ description="Choose the origin and destination yards for your shipment."
/>
{isLoading ? (
@@ -96,7 +96,7 @@ export function Step4Route({
) : (
Route
-
+
)}
-
+
-
+
Cargo handling
+
(
-
-
-
-
-
Hazardous Material
-
- Applies a Hazard Surcharge to the final bill.
-
-
-
-
field.onChange(e.currentTarget.checked)}
- color="edr-green"
- />
-
+ }
+ iconBg="#FBEAE7"
+ iconColor="#C0392B"
+ title="Hazardous Material"
+ description="Applies a hazard surcharge to the final bill."
+ checked={field.value}
+ onChange={(v) => field.onChange(v)}
+ />
)}
/>
(
-
-
-
-
-
Refrigerated Cargo
-
- Temperature-controlled transport applies a Refrigerator
- Surcharge.
-
-
-
-
field.onChange(e.currentTarget.checked)}
- color="edr-green"
- />
-
+ }
+ iconBg="#E9F0F8"
+ iconColor="#2E5B96"
+ title="Refrigerated Cargo"
+ description="Temperature-controlled transport applies a refrigeration surcharge."
+ checked={field.value}
+ onChange={(v) => field.onChange(v)}
+ />
)}
/>
-
-
+
+
+ );
+}
+
+function ToggleRow({
+ icon,
+ iconBg,
+ iconColor,
+ title,
+ description,
+ checked,
+ onChange,
+}: {
+ icon: React.ReactNode;
+ iconBg: string;
+ iconColor: string;
+ title: string;
+ description: string;
+ checked: boolean;
+ onChange: (v: boolean) => void;
+}) {
+ return (
+
+
+
+ {icon}
+
+
+
+ {title}
+
+
+ {description}
+
+
+
+ onChange(e.currentTarget.checked)}
+ color="edr-green"
+ size="md"
+ />
+
);
}
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 47e2fa2ff..ce40c6082 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
@@ -5,7 +5,6 @@ import {
ActionIcon,
Button,
Skeleton,
- InputLabel,
Text,
TextInput,
} from "@mantine/core";
@@ -17,9 +16,11 @@ import {
} from "./schema";
import {
AlertBox,
+ fieldStyles,
OptionCard,
OptionFieldError,
SelectField,
+ StepCard,
StepHeader,
StepLabel,
} from "./shared";
@@ -116,70 +117,66 @@ export function Step5CargoDetails({
if (isLoading) {
return (
-
+
}
title="Cargo Details"
description="Define your cargo type, weight, and container configuration."
/>
-
+
+
);
}
return (
-
+
}
title="Cargo Details"
description="Define your cargo type, weight, and container configuration."
/>
{/* Cargo Type */}
-
Cargo Type *
+
Cargo Type *
(
-
+
}
+ iconBg="#ECF6F1"
+ iconColor="#0A6F4D"
+ title="Containerized"
+ description="Pre-packed containerized cargo (20ft / 40ft)."
onClick={() => {
field.onChange("container");
form.setValue("cargoTypePath", [], { shouldDirty: true });
}}
- >
-
-
Containerized
-
- Pre-packed containerized cargo (20ft / 40ft).
-
-
+ />
}
+ iconBg="#FDF3E0"
+ iconColor="#C77F09"
+ title="General Cargo"
+ description="Bulk commodities or break-bulk cargo."
onClick={() => {
field.onChange("bulk");
form.setValue("containers", [], { shouldDirty: true });
}}
- >
-
-
-
-
General Cargo
-
- Bulk commodities or break-bulk cargo.
-
-
+ />
@@ -201,7 +198,8 @@ export function Step5CargoDetails({
placeholder="0.00"
leftSection={
}
error={fieldState.error?.message}
- radius="md"
+ radius={10}
+ styles={fieldStyles}
min={0}
step={0.01}
/>
@@ -484,6 +482,6 @@ export function Step5CargoDetails({
})()}
>
)}
-
+
);
}
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 278698f83..387034ec5 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
@@ -19,6 +19,7 @@ import {
type BookingFormValues,
} from "./schema";
import { StepHeader } from "./shared";
+import { ClipboardCheck } from "lucide-react";
import type { Freight } from "@/types";
import type { GeneratePriceResponse } from "@/services/bookings.service";
@@ -154,6 +155,7 @@ export function Step8Review({
return (
}
title="Review & Submit"
description="Confirm your contract request before sending it for EDR staff review."
/>