From e9362abecdebddd35c88286ea427ce9c477de135 Mon Sep 17 00:00:00 2001
From: ghost2023
Date: Fri, 22 May 2026 11:38:22 +0300
Subject: [PATCH] feat(bookings): Rework New Booking flow to support cargo type
selection
---
.../src/pages/bookings/NewBookingPage.tsx | 876 +++++++++++-------
1 file changed, 563 insertions(+), 313 deletions(-)
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 6d127eb70..84a3b9c69 100644
--- a/apps/edr-freight-web/portal/src/pages/bookings/NewBookingPage.tsx
+++ b/apps/edr-freight-web/portal/src/pages/bookings/NewBookingPage.tsx
@@ -27,7 +27,13 @@ import {
CardTitle,
Input,
Label,
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
SmartFileInput,
+ Switch,
Textarea,
} from "@edr/ui-common";
import Breadcrumbs from "@/components/Breadcrumbs";
@@ -35,22 +41,45 @@ import Breadcrumbs from "@/components/Breadcrumbs";
// ── Mock Constants ─────────────────────────────────────────────────────────────
const STATIONS = [
- "Addis Ababa", "Adama", "Mojo", "Awash", "Mieso",
- "Dire Dawa", "Aysha", "Ali Sabieh", "Holhol", "Djibouti City",
+ "Addis Ababa",
+ "Adama",
+ "Mojo",
+ "Awash",
+ "Mieso",
+ "Dire Dawa",
+ "Aysha",
+ "Ali Sabieh",
+ "Holhol",
+ "Djibouti City",
];
const ETHIOPIA_STATIONS = new Set([
- "Addis Ababa", "Adama", "Mojo", "Awash", "Mieso", "Dire Dawa",
+ "Addis Ababa",
+ "Adama",
+ "Mojo",
+ "Awash",
+ "Mieso",
+ "Dire Dawa",
]);
const BULK_COMMODITIES = [
- "Coffee", "Beans", "Fertilizer", "Sugar", "Oil", "Livestock", "Steel", "Others",
+ "Coffee",
+ "Beans",
+ "Fertilizer",
+ "Sugar",
+ "Oil",
+ "Livestock",
+ "Steel",
+ "Others",
];
const BREAK_BULK_TYPES = ["Machinery", "Ro-Ro", "Others"];
const MOCK_VALID_CONTRACTS = [
- "EDR-2024-10001", "EDR-2024-10002", "EDR-2023-88123", "EDR-2022-55442",
+ "EDR-2024-10001",
+ "EDR-2024-10002",
+ "EDR-2023-88123",
+ "EDR-2022-55442",
];
// US-04 compliance documents (TIN, Business License, Registration, National ID, optional PoA)
@@ -66,11 +95,15 @@ const BOOKING_DOCS_SETTING = {
entity: "booking" as const,
fields: [
{
- id: "f1", createdAt: "", updatedAt: "", deletedAt: null,
+ id: "f1",
+ createdAt: "",
+ updatedAt: "",
+ deletedAt: null,
settingId: "booking-compliance",
fileKey: "tin_certificate",
fileLabel: "TIN Certificate",
- helpText: "Tax Identification Number certificate issued by ERCA (10-digit TIN).",
+ helpText:
+ "Tax Identification Number certificate issued by ERCA (10-digit TIN).",
isRequired: true,
isMultiple: false,
maxFiles: 1,
@@ -79,11 +112,15 @@ const BOOKING_DOCS_SETTING = {
order: 1,
},
{
- id: "f2", createdAt: "", updatedAt: "", deletedAt: null,
+ id: "f2",
+ createdAt: "",
+ updatedAt: "",
+ deletedAt: null,
settingId: "booking-compliance",
fileKey: "business_license",
fileLabel: "Business / Investment License",
- helpText: "Current business or investment license issued by the relevant government authority.",
+ helpText:
+ "Current business or investment license issued by the relevant government authority.",
isRequired: true,
isMultiple: false,
maxFiles: 1,
@@ -92,7 +129,10 @@ const BOOKING_DOCS_SETTING = {
order: 2,
},
{
- id: "f3", createdAt: "", updatedAt: "", deletedAt: null,
+ id: "f3",
+ createdAt: "",
+ updatedAt: "",
+ deletedAt: null,
settingId: "booking-compliance",
fileKey: "business_registration",
fileLabel: "Business Registration Certificate",
@@ -105,11 +145,15 @@ const BOOKING_DOCS_SETTING = {
order: 3,
},
{
- id: "f4", createdAt: "", updatedAt: "", deletedAt: null,
+ id: "f4",
+ createdAt: "",
+ updatedAt: "",
+ deletedAt: null,
settingId: "booking-compliance",
fileKey: "national_id",
fileLabel: "National ID / Passport",
- helpText: "Valid government-issued ID or passport of the authorized signatory.",
+ helpText:
+ "Valid government-issued ID or passport of the authorized signatory.",
isRequired: true,
isMultiple: false,
maxFiles: 1,
@@ -118,11 +162,15 @@ const BOOKING_DOCS_SETTING = {
order: 4,
},
{
- id: "f5", createdAt: "", updatedAt: "", deletedAt: null,
+ id: "f5",
+ createdAt: "",
+ updatedAt: "",
+ deletedAt: null,
settingId: "booking-compliance",
fileKey: "power_of_attorney",
fileLabel: "Power of Attorney (PoA)",
- helpText: "Required only if a representative is signing on behalf of the company.",
+ helpText:
+ "Required only if a representative is signing on behalf of the company.",
isRequired: false,
isMultiple: false,
maxFiles: 1,
@@ -144,8 +192,8 @@ const STEPS = [
{ id: 1, label: "Contract Type", short: "Contract" },
{ id: 2, label: "Service Type", short: "Service" },
{ id: 3, label: "First & Last Mile", short: "Mile" },
- { id: 4, label: "Route & Cargo", short: "Cargo" },
- { id: 5, label: "Container Config", short: "Container" },
+ { id: 4, label: "Route", short: "Route" },
+ { id: 5, label: "Cargo Details", short: "Cargo" },
{ id: 6, label: "Wagon Allocation", short: "Wagons" },
{ id: 7, label: "Documents", short: "Docs" },
{ id: 8, label: "Review & Submit", short: "Submit" },
@@ -171,6 +219,7 @@ interface FormData {
equipmentReturn: EquipmentReturn;
originYard: string;
destinationYard: string;
+ cargoType: "container" | "bulk" | "";
cargoWeight: string;
freightType: FreightTypeVal;
bulkCommodity: string;
@@ -200,6 +249,7 @@ const INITIAL_DATA: FormData = {
equipmentReturn: "with_return",
originYard: "",
destinationYard: "",
+ cargoType: "",
cargoWeight: "",
freightType: "",
bulkCommodity: "",
@@ -244,7 +294,8 @@ interface WagonCalcResult {
}
function calcWagons(type: ContainerTypeVal, qty: number): WagonCalcResult {
- if (type === "40ft") return { totalWagons: qty, hasOddUnit: false, sharedWagons: qty };
+ if (type === "40ft")
+ return { totalWagons: qty, hasOddUnit: false, sharedWagons: qty };
const pairs = Math.floor(qty / 2);
const odd = qty % 2;
return { totalWagons: pairs + odd, hasOddUnit: odd > 0, sharedWagons: pairs };
@@ -252,29 +303,11 @@ function calcWagons(type: ContainerTypeVal, qty: number): WagonCalcResult {
// ── Shared primitives ──────────────────────────────────────────────────────────
-const selectCls =
- "w-full rounded-lg border border-input bg-background px-3 py-2 text-sm text-foreground shadow-xs outline-none transition hover:border-ring/50 focus:border-primary/50 focus:ring-2 focus:ring-primary/20";
-
-function NativeSelect({
- value, onChange, options, placeholder,
-}: {
- value: string;
- onChange: (v: string) => void;
- options: { value: string; label: string }[];
- placeholder?: string;
-}) {
- return (
- onChange(e.target.value)} className={selectCls}>
- {placeholder && {placeholder} }
- {options.map((o) => (
- {o.label}
- ))}
-
- );
-}
-
function OptionCard({
- selected, onClick, disabled, children,
+ selected,
+ onClick,
+ disabled,
+ children,
}: {
selected: boolean;
onClick?: () => void;
@@ -286,13 +319,12 @@ function OptionCard({
type="button"
onClick={onClick}
disabled={disabled}
- className={`relative w-full rounded-xl border-2 p-4 text-left transition ${
- disabled
+ className={`relative w-full rounded-xl border-2 p-4 text-left transition ${disabled
? "cursor-not-allowed border-border bg-muted opacity-60"
: selected
- ? "border-primary bg-primary/5"
- : "border-border bg-card hover:border-primary/40"
- }`}
+ ? "border-primary bg-primary/5"
+ : "border-border bg-card hover:border-primary/40"
+ }`}
>
{selected && !disabled && (
@@ -304,27 +336,10 @@ function OptionCard({
);
}
-function Toggle({ enabled, onChange }: { enabled: boolean; onChange: (v: boolean) => void }) {
- return (
- onChange(!enabled)}
- className={`relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-colors ${
- enabled ? "bg-primary" : "bg-input"
- }`}
- >
-
-
- );
-}
-
-function AlertBox({ tone, children }: {
+function AlertBox({
+ tone,
+ children,
+}: {
tone: "warning" | "error" | "success" | "info";
children: React.ReactNode;
}) {
@@ -341,14 +356,22 @@ function AlertBox({ tone, children }: {
info: ,
};
return (
-
+
);
}
-function Field({ label, children }: { label: string; children: React.ReactNode }) {
+function Field({
+ label,
+ children,
+}: {
+ label: string;
+ children: React.ReactNode;
+}) {
return (
{label}
@@ -372,7 +395,11 @@ function StepLabel({ children }: { children: React.ReactNode }) {
// ── Step 1: Contract Type ──────────────────────────────────────────────────────
function Step1({
- d, set, renewalValid, renewalValidating, onValidate,
+ d,
+ set,
+ renewalValid,
+ renewalValidating,
+ onValidate,
}: {
d: FormData;
set: (k: K, v: FormData[K]) => void;
@@ -440,14 +467,18 @@ function Step1({
onClick={onValidate}
disabled={!d.previousContractRef || renewalValidating}
>
- {renewalValidating ? : "Validate"}
+ {renewalValidating ? (
+
+ ) : (
+ "Validate"
+ )}
{renewalValid === true && (
- Contract found. Company details, route, and wagon preferences will
- be pre-filled.
+ Contract found. Company details, route, and wagon
+ preferences will be pre-filled.
)}
{renewalValid === false && (
@@ -465,7 +496,8 @@ function Step1({
// ── Step 2: Service Type ───────────────────────────────────────────────────────
function Step2({
- d, set,
+ d,
+ set,
}: {
d: FormData;
set:
(k: K, v: FormData[K]) => void;
@@ -480,13 +512,17 @@ function Step2({
-
set("serviceType", "rail")}>
+ set("serviceType", "rail")}
+ >
Rail Transport Only
- Rail transport along the EDR corridor, with optional first/last mile trucking.
+ Rail transport along the EDR corridor, with optional first/last mile
+ trucking.
Option A
@@ -502,7 +538,8 @@ function Step2({
Rail Transport & Freight Forwarding
- Rail transport plus documentation, customs liaison, and a dedicated coordinator.
+ Rail transport plus documentation, customs liaison, and a dedicated
+ coordinator.
Option B
@@ -511,8 +548,8 @@ function Step2({
- Customs and Clearance Service cannot be selected independently — it must be bundled with
- a Rail Transport service.
+ Customs and Clearance Service cannot be selected independently — it must
+ be bundled with a Rail Transport service.
);
@@ -521,7 +558,8 @@ function Step2({
// ── Step 3: First & Last Mile ──────────────────────────────────────────────────
function Step3({
- d, set,
+ d,
+ set,
}: {
d: FormData;
set: (k: K, v: FormData[K]) => void;
@@ -545,7 +583,10 @@ function Step3({
Truck pick-up from your premises to the origin rail yard.
- set("firstMileEnabled", v)} />
+ set("firstMileEnabled", v)}
+ />
{d.firstMileEnabled && (
@@ -564,10 +605,14 @@ function Step3({
Last Mile – Delivery
- Truck delivery from the destination rail yard to the final address.
+ Truck delivery from the destination rail yard to the final
+ address.
-
set("lastMileEnabled", v)} />
+ set("lastMileEnabled", v)}
+ />
{d.lastMileEnabled && (
@@ -584,7 +629,8 @@ function Step3({
Equipment Return
- Declare whether the container asset will be returned after unloading.
+ Declare whether the container asset will be returned after
+ unloading.
(k: K, v: FormData[K]) => void;
@@ -635,9 +682,9 @@ function Step4({
return (
-
Route & Cargo
+
Route
- Define the route, weight, and cargo classification.
+ Select the origin and destination yards.
@@ -646,24 +693,38 @@ function Step4({
Route
- set("originYard", v)}
- options={STATIONS.filter((s) => s !== d.destinationYard).map((s) => ({
- value: s, label: s,
- }))}
- placeholder="Select origin..."
- />
+ onValueChange={(v) => set("originYard", v)}
+ >
+
+
+
+
+ {STATIONS.filter((s) => s !== d.destinationYard).map((s) => (
+
+ {s}
+
+ ))}
+
+
- set("destinationYard", v)}
- options={STATIONS.filter((s) => s !== d.originYard).map((s) => ({
- value: s, label: s,
- }))}
- placeholder="Select destination..."
- />
+ onValueChange={(v) => set("destinationYard", v)}
+ >
+
+
+
+
+ {STATIONS.filter((s) => s !== d.originYard).map((s) => (
+
+ {s}
+
+ ))}
+
+
{direction && (
@@ -678,90 +739,6 @@ function Step4({
- {/* Weight */}
-
-
Weight
-
-
-
- set("cargoWeight", e.target.value)}
- className="pl-9"
- min="0"
- step="0.01"
- />
-
-
-
-
-
-
- {/* Freight Classification */}
-
-
Freight Type *
-
-
set("freightType", "bulk")}
- >
- Bulk
-
- Coffee, fertilizer, grain, ore, etc.
-
-
-
set("freightType", "break_bulk")}
- >
- Break-Bulk
-
- Machinery, vehicles, project cargo, etc.
-
-
-
-
- {d.freightType === "bulk" && (
-
- { set("bulkCommodity", v); if (v !== "Others") set("bulkCommodityOther", ""); }}
- options={BULK_COMMODITIES.map((c) => ({ value: c, label: c }))}
- placeholder="Select commodity *"
- />
- {d.bulkCommodity === "Others" && (
- set("bulkCommodityOther", e.target.value)}
- />
- )}
-
- )}
-
- {d.freightType === "break_bulk" && (
-
- { set("breakBulkType", v); if (v !== "Others") set("breakBulkTypeOther", ""); }}
- options={BREAK_BULK_TYPES.map((c) => ({ value: c, label: c }))}
- placeholder="Select type *"
- />
- {d.breakBulkType === "Others" && (
- set("breakBulkTypeOther", e.target.value)}
- />
- )}
-
- )}
-
-
-
-
{/* Modifiers */}
@@ -774,7 +751,10 @@ function Step4({
-
set("isHazardous", v)} />
+ set("isHazardous", v)}
+ />
@@ -782,11 +762,15 @@ function Step4({
Refrigerated Cargo
- Temperature-controlled transport — applies a Refrigerator Surcharge.
+ Temperature-controlled transport — applies a Refrigerator
+ Surcharge.
-
set("isRefrigerated", v)} />
+ set("isRefrigerated", v)}
+ />
@@ -796,7 +780,9 @@ function Step4({
// ── Step 5: Container Configuration ───────────────────────────────────────────
function Step5({
- d, set, direction,
+ d,
+ set,
+ direction,
}: {
d: FormData;
set:
(k: K, v: FormData[K]) => void;
@@ -817,79 +803,247 @@ function Step5({
return (
-
Container Configuration
+
Cargo Details
- Select container type, quantity, and Verified Gross Mass per container.
+ Define your cargo type, weight, and container configuration.
-
- {([
- {
- val: "20ft" as const,
- label: "20ft Container (TEU)",
- limit: direction === "export" ? "Max 25t per container" : "Max 20t per container",
- },
- {
- val: "40ft" as const,
- label: "40ft Container (FEU)",
- limit: "Max 32.5t per container",
- },
- ]).map((ct) => (
-
set("containerType", ct.val)}>
-
+ {/* Cargo Type */}
+
+
Cargo Type *
+
+
set("cargoType", "container")}
+ >
+
- {ct.limit}
+ Container
+
+ Pre-packed containerized cargo (20ft / 40ft).
+
- ))}
+
set("cargoType", "bulk")}
+ >
+
+
+
+ Bulk
+
+ Bulk commodities or break-bulk cargo.
+
+
+
- {direction && (
-
-
- Route detected as {direction} workflow
-
+ {/* Bulk section */}
+ {d.cargoType === "bulk" && (
+ <>
+
+
+
Freight Type *
+
+
set("freightType", "bulk")}
+ >
+ Bulk
+
+ Coffee, fertilizer, grain, ore, etc.
+
+
+
set("freightType", "break_bulk")}
+ >
+ Break-Bulk
+
+ Machinery, vehicles, project cargo, etc.
+
+
+
+
+ {d.freightType === "bulk" && (
+
+ {
+ set("bulkCommodity", v);
+ if (v !== "Others") set("bulkCommodityOther", "");
+ }}
+ >
+
+
+
+
+ {BULK_COMMODITIES.map((c) => (
+
+ {c}
+
+ ))}
+
+
+ {d.bulkCommodity === "Others" && (
+ set("bulkCommodityOther", e.target.value)}
+ />
+ )}
+
+ )}
+
+ {d.freightType === "break_bulk" && (
+
+ {
+ set("breakBulkType", v);
+ if (v !== "Others") set("breakBulkTypeOther", "");
+ }}
+ >
+
+
+
+
+ {BREAK_BULK_TYPES.map((c) => (
+
+ {c}
+
+ ))}
+
+
+ {d.breakBulkType === "Others" && (
+ set("breakBulkTypeOther", e.target.value)}
+ />
+ )}
+
+ )}
+
+
+
+
+ {/* Weight */}
+
+
Weight
+
+
+
+ set("cargoWeight", e.target.value)}
+ className="pl-9"
+ min="0"
+ step="0.01"
+ />
+
+
+
+ >
)}
-
+ {/* Container section */}
+ {d.cargoType === "container" && (
+ <>
+
+
+
Container Configuration
+
+ {[
+ {
+ val: "20ft" as const,
+ label: "20ft Container (TEU)",
+ limit:
+ direction === "export"
+ ? "Max 25t per container"
+ : "Max 20t per container",
+ },
+ {
+ val: "40ft" as const,
+ label: "40ft Container (FEU)",
+ limit: "Max 32.5t per container",
+ },
+ ].map((ct) => (
+
set("containerType", ct.val)}
+ >
+
+ {ct.limit}
+
+ ))}
+
-
-
-
-
set("quantity", String(Math.max(1, qty - 1)))}
- className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border border-input transition hover:bg-accent"
- >−
-
set("quantity", e.target.value)}
- className="text-center"
- min="1"
- />
-
set("quantity", String(qty + 1))}
- className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border border-input transition hover:bg-accent"
- >+
+ {direction && (
+
+
+ Route detected as{" "}
+
+ {direction}
+ {" "}
+ workflow
+
+ )}
+
+
-
-
- set("vgm", e.target.value)}
- min="0"
- step="0.1"
- />
-
-
- {overweightAlert &&
Overweight Alert: {overweightAlert}}
+ {overweightAlert && (
+
+ Overweight Alert: {overweightAlert}
+
+ )}
+ >
+ )}
);
}
@@ -897,7 +1051,9 @@ function Step5({
// ── Step 6: Wagon Allocation ───────────────────────────────────────────────────
function Step6({
- d, set, wagons,
+ d,
+ set,
+ wagons,
}: {
d: FormData;
set:
(k: K, v: FormData[K]) => void;
@@ -916,18 +1072,25 @@ function Step6({
{!wagons ? (
- Complete the container configuration in the previous step to see wagon allocation.
+ Complete the container configuration in the previous step to see wagon
+ allocation.
) : (
<>
-
{wagons.totalWagons}
-
Wagons Required
+
+ {wagons.totalWagons}
+
+
+ Wagons Required
+
{qty}
-
{d.containerType} Containers
+
+ {d.containerType} Containers
+
{wagons.sharedWagons}
@@ -943,15 +1106,16 @@ function Step6({
return (
{isOdd
? `1 × ${d.containerType} (½)`
- : d.containerType === "20ft" ? "2 × 20ft" : "1 × 40ft"}
+ : d.containerType === "20ft"
+ ? "2 × 20ft"
+ : "1 × 40ft"}
);
})}
@@ -971,11 +1135,14 @@ function Step6({
-
Consolidation Option (US-07)
+
+ Consolidation Option (US-07)
+
- You have 1 unpaired 20ft container. Opt in to share a wagon slot with another
- shipper to optimise costs (2 × 20ft = 1 Wagon), or request a dedicated wagon.
+ You have 1 unpaired 20ft container. Opt in to share a wagon
+ slot with another shipper to optimise costs (2 × 20ft = 1
+ Wagon), or request a dedicated wagon.
(k: K, v: FormData[K]) => void;
@@ -1023,19 +1191,21 @@ function Step7Docs({
return (
-
Compliance Documents
+
+ Compliance Documents
+
- Upload your company's legal credentials for EDR contract eligibility verification (US-04).
+ Upload your company's legal credentials for EDR contract eligibility
+ verification (US-04).
{uploadedRequired}/{REQUIRED_DOC_KEYS.length}
@@ -1058,7 +1228,11 @@ function Step7Docs({
// ── Step 8: Review & Submit ────────────────────────────────────────────────────
function Step8Review({
- d, set, setStep, wagons, direction,
+ d,
+ set,
+ setStep,
+ wagons,
+ direction,
}: {
d: FormData;
set:
(k: K, v: FormData[K]) => void;
@@ -1066,7 +1240,15 @@ function Step8Review({
wagons: WagonCalcResult | null;
direction: RouteDirection;
}) {
- function Row({ label, value, target }: { label: string; value: string; target: number }) {
+ function Row({
+ label,
+ value,
+ target,
+ }: {
+ label: string;
+ value: string;
+ target: number;
+ }) {
return (
@@ -1088,8 +1270,8 @@ function Step8Review({
d.freightType === "bulk"
? `Bulk — ${d.bulkCommodity === "Others" ? d.bulkCommodityOther : d.bulkCommodity}`
: d.freightType === "break_bulk"
- ? `Break-Bulk — ${d.breakBulkType === "Others" ? d.breakBulkTypeOther : d.breakBulkType}`
- : "";
+ ? `Break-Bulk — ${d.breakBulkType === "Others" ? d.breakBulkTypeOther : d.breakBulkType}`
+ : "";
const uploadedCount = REQUIRED_DOC_KEYS.filter((k) => {
const f = d.documents[k];
@@ -1114,11 +1296,25 @@ function Step8Review({
-
-
+
+
@@ -1143,7 +1339,11 @@ function Step8Review({
/>
@@ -1156,13 +1356,36 @@ function Step8Review({
-
-
-
-
+
+
+
+
@@ -1175,11 +1398,23 @@ function Step8Review({
-
-
+
+
1 ? "s" : ""}` : ""}
+ value={
+ wagons
+ ? `${wagons.totalWagons} wagon${wagons.totalWagons > 1 ? "s" : ""}`
+ : ""
+ }
target={6}
/>
set("termsAccepted", !d.termsAccepted)}
- className={`mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded border-2 transition ${
- d.termsAccepted ? "border-primary bg-primary" : "border-input"
- }`}
+ className={`mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded border-2 transition ${d.termsAccepted ? "border-primary bg-primary" : "border-input"
+ }`}
>
- {d.termsAccepted && }
+ {d.termsAccepted && (
+
+ )}
I confirm the information is accurate and agree to EDR's{" "}
- freight contract terms and conditions .
+
+ freight contract terms and conditions
+
+ .
@@ -1250,7 +1489,9 @@ export default function NewBookingPage() {
setRenewalValid(null);
setTimeout(() => {
setRenewalValidating(false);
- setRenewalValid(MOCK_VALID_CONTRACTS.includes(data.previousContractRef.toUpperCase()));
+ setRenewalValid(
+ MOCK_VALID_CONTRACTS.includes(data.previousContractRef.toUpperCase()),
+ );
}, 1200);
}
@@ -1267,18 +1508,25 @@ export default function NewBookingPage() {
if (data.lastMileEnabled && !data.deliveryAddress) return false;
return true;
case 4:
- if (!data.originYard || !data.destinationYard || !data.cargoWeight || !data.freightType)
- return false;
- if (data.freightType === "bulk") {
- if (!data.bulkCommodity) return false;
- if (data.bulkCommodity === "Others" && !data.bulkCommodityOther) return false;
- }
- if (data.freightType === "break_bulk") {
- if (!data.breakBulkType) return false;
- if (data.breakBulkType === "Others" && !data.breakBulkTypeOther) return false;
- }
+ if (!data.originYard || !data.destinationYard) return false;
return true;
case 5:
+ if (!data.cargoType) return false;
+ if (data.cargoType === "bulk") {
+ if (!data.freightType) return false;
+ if (data.freightType === "bulk") {
+ if (!data.bulkCommodity) return false;
+ if (data.bulkCommodity === "Others" && !data.bulkCommodityOther)
+ return false;
+ }
+ if (data.freightType === "break_bulk") {
+ if (!data.breakBulkType) return false;
+ if (data.breakBulkType === "Others" && !data.breakBulkTypeOther)
+ return false;
+ }
+ if (!data.cargoWeight) return false;
+ return true;
+ }
return !!(data.containerType && data.quantity && data.vgm);
case 6:
return true;
@@ -1309,8 +1557,8 @@ export default function NewBookingPage() {
Contract Submitted
- Your request is queued for review by EDR Line Staff. You will be notified once
- approved.
+ Your request is queued for review by EDR Line Staff. You will be
+ notified once approved.
{data.draftContractId || data.previousContractRef}
@@ -1321,41 +1569,41 @@ export default function NewBookingPage() {
}
return (
-
+
{/* Sticky step indicator */}
{STEPS.map((s, idx) => (
s.id
+ className={`flex h-7 w-7 items-center justify-center rounded-full text-xs font-semibold transition-colors ${step > s.id
? "bg-primary text-primary-foreground"
: step === s.id
- ? "border-2 border-primary text-primary"
- : "bg-muted text-muted-foreground"
- }`}
+ ? "border-2 border-primary text-primary"
+ : "bg-muted text-muted-foreground"
+ }`}
>
{step > s.id ? : s.id}
= s.id ? "text-foreground" : "text-muted-foreground"
- }`}
+ className={`hidden text-[10px] font-medium lg:block ${step >= s.id ? "text-foreground" : "text-muted-foreground"
+ }`}
>
{s.short}
{idx < STEPS.length - 1 && (
s.id ? "bg-primary" : "bg-border"
- }`}
+ className={`mx-1 h-0.5 flex-1 rounded-full transition-colors ${step > s.id ? "bg-primary" : "bg-border"
+ }`}
/>
)}
@@ -1406,9 +1654,11 @@ export default function NewBookingPage() {
Back
- {step} / {STEPS.length}
{step < STEPS.length ? (
- setStep((s) => s + 1)} disabled={!canProceed()}>
+ setStep((s) => s + 1)}
+ disabled={!canProceed()}
+ >
Continue