From 9dc3bc07652789aff1b25be1c016fd855886b190 Mon Sep 17 00:00:00 2001 From: Marshal Date: Wed, 17 Jun 2026 14:12:14 +0000 Subject: [PATCH] enhance booking form UI with new components and improved layouts --- .../new-booking-form/StepIndicator.tsx | 150 +++---- .../bookings/new-booking-form/shared.tsx | 236 +++++++++-- .../new-booking-form/step-documents.tsx | 11 +- .../new-booking-form/step-scheduling.tsx | 253 ++++++++---- .../new-booking-form/step1-contract-type.tsx | 48 +-- .../new-booking-form/step2-service-type.tsx | 381 +++++++++--------- .../bookings/new-booking-form/step4-route.tsx | 140 +++++-- .../new-booking-form/step5-cargo-details.tsx | 62 ++- .../new-booking-form/step8-review.tsx | 2 + 9 files changed, 816 insertions(+), 467 deletions(-) diff --git a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/StepIndicator.tsx b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/StepIndicator.tsx index 7ac5411fb..c5d9ad433 100644 --- a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/StepIndicator.tsx +++ b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/StepIndicator.tsx @@ -2,84 +2,88 @@ import { Check } from "lucide-react"; import { Fragment } from "react"; import { STEPS } from "./schema"; +const GREEN = "var(--mantine-color-edr-green-5)"; +const GREEN_DEEP = "var(--mantine-color-edr-green-7)"; +const BORDER = "var(--mantine-color-edr-border-0)"; +const MUTED = "var(--mantine-color-edr-muted-0)"; +const INK = "var(--mantine-color-edr-text-0)"; + export function StepIndicator({ step }: { step: number }) { return ( -
- {STEPS.map((item, index) => ( - -
-
item.id - ? { - backgroundColor: "var(--mantine-color-edr-green-5)", - color: "#fff", - boxShadow: "0 2px 8px rgba(14,163,113,0.4)", - } - : step === item.id +
+ {STEPS.map((item, index) => { + const done = step > item.id; + const active = step === item.id; + return ( + +
+
- {step > item.id ? ( - - ) : ( - item.id - )} + : active + ? { + border: `2.5px solid ${GREEN}`, + color: GREEN_DEEP, + backgroundColor: "#fff", + boxShadow: "0 0 0 4px rgba(14,163,113,0.12)", + } + : { + backgroundColor: "#fff", + color: MUTED, + border: `2px solid ${BORDER}`, + }), + }} + > + {done ? : item.id} +
+ = item.id ? INK : MUTED, + }} + className="md:!block" + > + {item.short} +
- = item.id - ? "var(--mantine-color-edr-text-0)" - : "var(--mantine-color-edr-muted-0)", - }} - className="lg:!block" - > - {item.short} - -
- {index < STEPS.length - 1 && ( -
item.id - ? "var(--mantine-color-edr-green-5)" - : "var(--mantine-color-edr-border-0)", - }} - /> - )} - - ))} + {index < STEPS.length - 1 && ( +
+ )} + + ); + })}
); } diff --git a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/shared.tsx b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/shared.tsx index 55b6eda44..b2ee1fd7e 100644 --- a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/shared.tsx +++ b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/shared.tsx @@ -1,48 +1,167 @@ -import { Alert, Combobox, Input, InputBase, Select, Text, Title, useCombobox } from "@mantine/core"; -import { AlertTriangle, Check, CheckCircle2, Info, Loader, XCircle } from "lucide-react"; +import { + Alert, + Box, + Combobox, + Group, + Input, + InputBase, + Paper, + Select, + Text, + Title, + useCombobox, +} from "@mantine/core"; +import { + AlertTriangle, + Check, + CheckCircle2, + Info, + Loader, + XCircle, +} from "lucide-react"; import type { ReactNode } from "react"; import { useMemo } from "react"; -import type { ControllerRenderProps, FieldError as RhfFieldError } from "react-hook-form"; +import type { + ControllerRenderProps, + FieldError as RhfFieldError, +} from "react-hook-form"; import type { BookingFormInputValues } from "./schema"; +// Brand tokens (kept local so the form reads consistently with the booking +// detail page and the scheduling step). +const INK = "#10202F"; +const MUTED = "#6B7C8E"; +const GREEN = "#0EA371"; +const GREEN_DARK = "#0A6F4D"; +const BORDER = "#E6ECF2"; + export function OptionFieldError({ error }: { error?: { message?: string } }) { if (!error?.message) return null; return ( - + {error.message} ); } +/** + * Premium selectable option card with an icon tile, title, and description. + * Pass `icon`/`iconBg`/`iconColor` for the leading tile, or compose freely via + * `children` (legacy callers still work). + */ export function OptionCard({ selected, onClick, disabled, + icon, + iconBg = "#ECF6F1", + iconColor = GREEN_DARK, + title, + description, children, }: { selected: boolean; onClick?: () => void; disabled?: boolean; - children: ReactNode; + icon?: ReactNode; + iconBg?: string; + iconColor?: string; + title?: ReactNode; + description?: ReactNode; + children?: ReactNode; }) { return ( ); @@ -63,7 +182,7 @@ export function AlertBox({ }; const { color, icon } = map[tone]; return ( - + {children} ); @@ -71,31 +190,89 @@ export function AlertBox({ export function StepLabel({ children }: { children: ReactNode }) { return ( - + {children} ); } +/** + * Card shell that wraps a step's body. Gives every step the same premium + * surface, padding, and an optional eyebrow. + */ +export function StepCard({ + children, + eyebrow, +}: { + children: ReactNode; + eyebrow?: ReactNode; +}) { + return ( + + {eyebrow} + {children} + + ); +} + export function StepHeader({ title, description, + icon, }: { title: string; description: string; + icon?: ReactNode; }) { return ( -
- - {title} - - - {description} - -
+ + {icon && ( + + {icon} + + )} + + + {title} + + + {description} + + + ); } +/** Shared Mantine input styling so every field in the form matches. */ +export const fieldStyles = { + label: { fontWeight: 600, fontSize: 13, color: INK, marginBottom: 6 }, + input: { borderRadius: 10, minHeight: 44, height: 44, borderColor: BORDER }, +} as const; + export function SelectField({ field, error, @@ -103,6 +280,7 @@ export function SelectField({ placeholder, disabled, data, + leftSection, }: { field: ControllerRenderProps; error?: RhfFieldError; @@ -110,6 +288,7 @@ export function SelectField({ placeholder: string; disabled?: boolean; data: string[] | { value: string; label: string }[]; + leftSection?: ReactNode; }) { return (