From a308437941d1a71b20cc26150bb6912c5eda7ee7 Mon Sep 17 00:00:00 2001 From: Guritfak Gill Date: Sat, 18 Jul 2026 15:09:28 -0700 Subject: [PATCH 01/13] feat(ui): align upload flows with hi-fi designs Co-authored-by: Cursor --- app/ui/src/app/components/HiFiUploadPage.tsx | 491 +++++++++++++++++++ app/ui/src/app/layout.tsx | 28 +- app/ui/src/app/upload-route/page.tsx | 368 +------------- app/ui/src/app/upload-save-point/page.tsx | 331 +------------ app/ui/src/app/welcome/page.tsx | 24 +- 5 files changed, 570 insertions(+), 672 deletions(-) create mode 100644 app/ui/src/app/components/HiFiUploadPage.tsx diff --git a/app/ui/src/app/components/HiFiUploadPage.tsx b/app/ui/src/app/components/HiFiUploadPage.tsx new file mode 100644 index 00000000..b1eb9ffb --- /dev/null +++ b/app/ui/src/app/components/HiFiUploadPage.tsx @@ -0,0 +1,491 @@ +"use client"; + +import { useRef, type DragEventHandler } from "react"; +import ShellNavbar from "@/app/components/ShellNavbar"; +import { formatSize, PageFooter } from "@/app/utils/routeUtils"; + +type HiFiUploadPageProps = { + title: string; + dropzoneText: string; + description: string; + accept: string; + file: File | null; + isDragging: boolean; + isProcessing: boolean; + error: string | null; + onSelectFile: (file: File) => void; + onDragEnter: DragEventHandler; + onDragLeave: DragEventHandler; + onDrop: DragEventHandler; + onRemoveFile: () => void; + onCancel: () => void; + onNext: () => void; +}; + +export default function HiFiUploadPage({ + title, + dropzoneText, + description, + accept, + file, + isDragging, + isProcessing, + error, + onSelectFile, + onDragEnter, + onDragLeave, + onDrop, + onRemoveFile, + onCancel, + onNext, +}: HiFiUploadPageProps) { + const inputRef = useRef(null); + + const openFilePicker = () => { + if (!isProcessing) inputRef.current?.click(); + }; + + return ( + <> + + +
+ + +
+
+
+
+

{title}

+
event.preventDefault()} + onDragLeave={onDragLeave} + onDrop={onDrop} + > + {isProcessing ? ( +
+ ) : ( + <> + +
+

{dropzoneText}

+ +
+ + )} + { + const selectedFile = event.target.files?.[0]; + if (selectedFile) onSelectFile(selectedFile); + event.target.value = ""; + }} + /> +
+

{description}

+
+ +
+ + + {file ? file.name : "No file added"} + + {file && ( + <> + + {formatSize(file.size)} + + + + )} +
+
+ + {error && ( +

+ {error} +

+ )} + +
+ + +
+
+
+ + +
+ + ); +} diff --git a/app/ui/src/app/layout.tsx b/app/ui/src/app/layout.tsx index 08182e2d..5cc04c93 100644 --- a/app/ui/src/app/layout.tsx +++ b/app/ui/src/app/layout.tsx @@ -1,8 +1,28 @@ import type { Metadata, Viewport } from "next"; +import { Geist, Geist_Mono, Manrope } from "next/font/google"; import ServiceWorkerRegistration from "@/app/components/ServiceWorkerRegistration"; import "./globals.css"; import FeedbackLauncher from "./components/FeedbackLauncher"; +const geistSans = Geist({ + variable: "--font-geist-sans", + subsets: ["latin"], +}); + +const geistMono = Geist_Mono({ + variable: "--font-geist-mono", + subsets: ["latin"], +}); + +const manrope = Manrope({ + variable: "--font-manrope", + subsets: ["latin"], +}); + +type RootLayoutProps = { + children: React.ReactNode; +}; + export const metadata: Metadata = { title: { default: "Delivery Route Optimizer", @@ -22,12 +42,12 @@ export const viewport: Viewport = { export default function RootLayout({ children, -}: { - children: React.ReactNode; -}) { +}: RootLayoutProps) { return ( - + {children} diff --git a/app/ui/src/app/upload-route/page.tsx b/app/ui/src/app/upload-route/page.tsx index 42ae6195..9dd0d39f 100644 --- a/app/ui/src/app/upload-route/page.tsx +++ b/app/ui/src/app/upload-route/page.tsx @@ -1,12 +1,10 @@ -// app/upload-route/page.tsx "use client"; export const dynamic = "force-dynamic"; import { useCallback, useRef, useState } from "react"; import { useRouter } from "next/navigation"; -import ShellNavbar from "@/app/components/ShellNavbar"; -import { formatSize } from "@/app/utils/routeUtils"; +import HiFiUploadPage from "@/app/components/HiFiUploadPage"; const MAX_FILE_MB = 10; const MAX_FILE_BYTES = MAX_FILE_MB * 1024 * 1024; @@ -18,7 +16,6 @@ export default function UploadRoutePage() { const [isProcessing, setIsProcessing] = useState(false); const [error, setError] = useState(null); const dragDepth = useRef(0); - const inputRef = useRef(null); const handleFile = (f: File) => { setError(null); @@ -78,348 +75,25 @@ export default function UploadRoutePage() { }, [file, isProcessing, router]); return ( - <> - - -
- - -
-

Upload your route

-

- Upload your route to begin your deliveries! -

- - {/* Drop zone — shows spinner while file is being read */} -
!isProcessing && inputRef.current?.click()} - onDragEnter={handleDragEnter} - onDragOver={(e) => e.preventDefault()} - onDragLeave={handleDragLeave} - onDrop={handleDrop} - > - {isProcessing ? ( -
- ) : ( - <> -
- - - - - -
-

- Drag and drop .json files here, or -

-

Browse files

- - )} - { - const f = e.target.files?.[0]; - if (f) handleFile(f); - e.target.value = ""; - }} - /> -
- - {file && !isProcessing && ( -
- - - - {file.name} - {formatSize(file.size)} - -
- )} - - {error &&

{error}

} - -
- - -
-
-
- + { + setFile(null); + setError(null); + }} + onCancel={() => router.back()} + onNext={() => void handleContinue()} + /> ); } diff --git a/app/ui/src/app/upload-save-point/page.tsx b/app/ui/src/app/upload-save-point/page.tsx index 5cd55402..c66a6132 100644 --- a/app/ui/src/app/upload-save-point/page.tsx +++ b/app/ui/src/app/upload-save-point/page.tsx @@ -1,13 +1,11 @@ -// app/upload-save-point/page.tsx "use client"; export const dynamic = "force-dynamic"; -import { useState, useRef, useCallback } from "react"; +import { useCallback, useRef, useState } from "react"; import { useRouter } from "next/navigation"; -import ShellNavbar from "@/app/components/ShellNavbar"; +import HiFiUploadPage from "@/app/components/HiFiUploadPage"; import { migrateSessionSaveFile } from "@/lib/validation/session.schema"; -import { formatSize } from "@/app/utils/routeUtils"; const MAX_FILE_MB = 10; const MAX_FILE_BYTES = MAX_FILE_MB * 1024 * 1024; @@ -19,7 +17,6 @@ export default function UploadSavePointPage() { const [isProcessing, setIsProcessing] = useState(false); const [continueError, setContinueError] = useState(null); const dragDepth = useRef(0); - const inputRef = useRef(null); const handleFile = (f: File) => { setContinueError(null); @@ -115,309 +112,25 @@ export default function UploadSavePointPage() { }, [file, isProcessing, router]); return ( - <> - - -
- - -
-

Upload your save point

-

- Continue editing from where you left off. -

- - {/* Drop zone */} -
!isProcessing && inputRef.current?.click()} - onDragEnter={handleDragEnter} - onDragOver={(e) => e.preventDefault()} - onDragLeave={handleDragLeave} - onDrop={handleDrop} - > - {isProcessing ? ( -
- ) : ( - <> -
- - - - - -
-

- Drag and drop .json or .csv files here, or -

-

Browse files

- - )} - { - const f = e.target.files?.[0]; - if (f) handleFile(f); - e.target.value = ""; - }} - /> -
- - {file && !isProcessing && ( -
- - - - {file.name} - {formatSize(file.size)} - -
- )} - - {continueError && ( -

{continueError}

- )} - -
- - -
-
-
- + { + setFile(null); + setContinueError(null); + }} + onCancel={() => router.back()} + onNext={() => void handleContinue()} + /> ); } diff --git a/app/ui/src/app/welcome/page.tsx b/app/ui/src/app/welcome/page.tsx index 274ea070..404ef6e9 100644 --- a/app/ui/src/app/welcome/page.tsx +++ b/app/ui/src/app/welcome/page.tsx @@ -195,22 +195,22 @@ export default function WelcomePage() {
-

Resume session

+

Start new session

- Import a saved CSV file from a previous session to continue - managing and optimizing routes. + Set up vehicles, drivers, and delivery stops to create your + optimized route.

@@ -218,22 +218,22 @@ export default function WelcomePage() {
-

Start new session

+

Resume session

- Set up vehicles, drivers, and delivery stops to create your - optimized route. + Import a saved CSV file from a previous session to continue + managing and optimizing routes.

From 03d0a85ccc486b433161ea894b94ed2e45c1b2f6 Mon Sep 17 00:00:00 2001 From: Guritfak Gill Date: Sat, 18 Jul 2026 15:29:53 -0700 Subject: [PATCH 02/13] style(ui): format frontend sources Co-authored-by: Cursor --- app/ui/src/app/components/HiFiUploadPage.tsx | 7 ++++++- app/ui/src/app/layout.tsx | 4 +--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/ui/src/app/components/HiFiUploadPage.tsx b/app/ui/src/app/components/HiFiUploadPage.tsx index b1eb9ffb..a1c2c2c9 100644 --- a/app/ui/src/app/components/HiFiUploadPage.tsx +++ b/app/ui/src/app/components/HiFiUploadPage.tsx @@ -354,7 +354,12 @@ export default function HiFiUploadPage({ ) : ( <> ); diff --git a/app/ui/src/app/components/navbar/Navbar.tsx b/app/ui/src/app/components/navbar/Navbar.tsx index 7af1239a..396659ac 100644 --- a/app/ui/src/app/components/navbar/Navbar.tsx +++ b/app/ui/src/app/components/navbar/Navbar.tsx @@ -13,7 +13,7 @@ type NavbarProps = { export default function Navbar({ onSave }: NavbarProps) { return (
- DELIVERY OPTIMIZER + Delivery Optimizer From d6e9dbaf2feebb835101395883734c32f5077557 Mon Sep 17 00:00:00 2001 From: Guritfak Gill Date: Sat, 18 Jul 2026 17:10:41 -0700 Subject: [PATCH 04/13] fix(ui): restore CSV mapping option contrast Co-authored-by: Cursor --- .../components/address/CSVUploadOverlay.tsx | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/app/ui/src/app/edit/components/address/CSVUploadOverlay.tsx b/app/ui/src/app/edit/components/address/CSVUploadOverlay.tsx index 519bbafc..f7abc83e 100644 --- a/app/ui/src/app/edit/components/address/CSVUploadOverlay.tsx +++ b/app/ui/src/app/edit/components/address/CSVUploadOverlay.tsx @@ -201,29 +201,31 @@ function StepColumnMapper({ onChange={(e) => onMappingChange(header, e.target.value as MappableField) } - className="absolute inset-0 w-full h-full opacity-0 cursor-pointer" + className="absolute inset-0 w-full h-full appearance-none bg-transparent px-3 pr-10 text-[14px] leading-[1.5] text-[var(--edit-text-primary)] cursor-pointer" > - + {( Object.keys(FIELD_LABELS) as Exclude< MappableField, "" >[] ).map((f) => ( - ))} - - {mapping[header] - ? FIELD_LABELS[ - mapping[header] as Exclude - ] - : "Select"} - Date: Sat, 18 Jul 2026 17:26:09 -0700 Subject: [PATCH 05/13] fix(ui): align onboarding header surfaces Co-authored-by: Cursor --- app/ui/src/app/components/ShellNavbar.tsx | 10 +++++++++- app/ui/src/app/page.tsx | 1 - 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/ui/src/app/components/ShellNavbar.tsx b/app/ui/src/app/components/ShellNavbar.tsx index 40767ad7..6a74b179 100644 --- a/app/ui/src/app/components/ShellNavbar.tsx +++ b/app/ui/src/app/components/ShellNavbar.tsx @@ -12,6 +12,8 @@ import { usePathname } from "next/navigation"; export default function ShellNavbar() { const pathname = usePathname(); const isLandingPage = pathname === "/"; + const usesWhiteOnboardingBackground = + isLandingPage || pathname === "/welcome"; const brandStyles = { fontFamily: "var(--font-manrope), Arial, Helvetica, sans-serif", fontSize: "20px", @@ -28,7 +30,13 @@ export default function ShellNavbar() {
Date: Sat, 18 Jul 2026 17:46:42 -0700 Subject: [PATCH 06/13] fix(ui): reserve space for welcome Back button Co-authored-by: Cursor --- app/ui/src/app/components/ShellNavbar.tsx | 7 +- app/ui/src/app/welcome/page.tsx | 115 ++++++++++++---------- 2 files changed, 65 insertions(+), 57 deletions(-) diff --git a/app/ui/src/app/components/ShellNavbar.tsx b/app/ui/src/app/components/ShellNavbar.tsx index 6a74b179..3ea18bed 100644 --- a/app/ui/src/app/components/ShellNavbar.tsx +++ b/app/ui/src/app/components/ShellNavbar.tsx @@ -33,10 +33,9 @@ export default function ShellNavbar() { background: usesWhiteOnboardingBackground ? "#ffffff" : "var(--edit-stone-50)", - borderBottom: - usesWhiteOnboardingBackground - ? "1px solid rgba(0, 0, 0, 0.08)" - : "none", + borderBottom: usesWhiteOnboardingBackground + ? "1px solid rgba(0, 0, 0, 0.08)" + : "none", display: "flex", alignItems: "center", padding: "16px", diff --git a/app/ui/src/app/welcome/page.tsx b/app/ui/src/app/welcome/page.tsx index 404ef6e9..8dcf6ac4 100644 --- a/app/ui/src/app/welcome/page.tsx +++ b/app/ui/src/app/welcome/page.tsx @@ -46,12 +46,20 @@ export default function WelcomePage() { display: flex; flex-direction: column; align-items: center; - justify-content: center; + justify-content: flex-start; padding: 48px 24px; position: relative; z-index: 1; } + .welcome-main { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + margin-block: auto; + } + .welcome-title { font-family: 'DM Sans', sans-serif; font-size: 1.75rem; @@ -155,10 +163,9 @@ export default function WelcomePage() { } .welcome-back { - position: absolute; - bottom: 32px; - left: 50%; - transform: translateX(-50%); + align-self: center; + flex-shrink: 0; + margin-top: 24px; background: none; border: none; cursor: pointer; @@ -187,56 +194,58 @@ export default function WelcomePage() {
-

- Choose how you'd like to continue -

-

Select one

+
+

+ Choose how you'd like to continue +

+

Select one

-
-
- -

Start new session

-

- Set up vehicles, drivers, and delivery stops to create your - optimized route. -

- -
+
+
+ +

Start new session

+

+ Set up vehicles, drivers, and delivery stops to create your + optimized route. +

+ +
-
- -

Resume session

-

- Import a saved CSV file from a previous session to continue - managing and optimizing routes. -

- +
+ +

Resume session

+

+ Import a saved CSV file from a previous session to continue + managing and optimizing routes. +

+ +
From c46066b66a9a5f12f03f57f337594022627ccd23 Mon Sep 17 00:00:00 2001 From: Guritfak Gill Date: Sat, 18 Jul 2026 17:56:12 -0700 Subject: [PATCH 07/13] fix(ui): align Results header title Co-authored-by: Cursor --- app/ui/src/app/results/components/MobileResultsNavbar.tsx | 6 ++++-- app/ui/src/app/results/page.tsx | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/ui/src/app/results/components/MobileResultsNavbar.tsx b/app/ui/src/app/results/components/MobileResultsNavbar.tsx index 89ac7ce5..65eb6726 100644 --- a/app/ui/src/app/results/components/MobileResultsNavbar.tsx +++ b/app/ui/src/app/results/components/MobileResultsNavbar.tsx @@ -4,11 +4,11 @@ import { MOBILE_NAVBAR_LEFT_GROUP, MOBILE_NAVBAR_MENU_BTN, MOBILE_NAVBAR_ROOT, + MOBILE_NAVBAR_TITLE, } from "../../edit/formStyles.v2"; import { RESULTS_MOBILE_NAV_CANCEL_BTN, RESULTS_MOBILE_NAV_SAVE_BTN, - RESULTS_MOBILE_NAV_TITLE, } from "../formStyles.mobile"; type MobileResultsNavbarProps = { @@ -49,7 +49,9 @@ export default function MobileResultsNavbar({ /> - Delivery Optimizer + + Delivery Optimizer +
{isEditMode && ( diff --git a/app/ui/src/app/results/page.tsx b/app/ui/src/app/results/page.tsx index 0ec18165..cdf6e4bd 100644 --- a/app/ui/src/app/results/page.tsx +++ b/app/ui/src/app/results/page.tsx @@ -416,7 +416,7 @@ export default function ResultsPage() {

- - - - - - {route.vehicleType ?? "Vehicle"} + + + {route.vehicleType + ? capitalize(route.vehicleType) + : "Vehicle"} +

diff --git a/app/ui/src/app/results/components/VehicleTypeIcon.tsx b/app/ui/src/app/results/components/VehicleTypeIcon.tsx new file mode 100644 index 00000000..8866a014 --- /dev/null +++ b/app/ui/src/app/results/components/VehicleTypeIcon.tsx @@ -0,0 +1,153 @@ +import type { JSX } from "react"; + +type VehicleTypeIconProps = { + vehicleType?: string; + className?: string; +}; + +function normalizedVehicleType(vehicleType?: string): string { + return vehicleType?.trim().toLowerCase() ?? ""; +} + +function TruckIcon(): JSX.Element { + return ( + <> + + + + + + ); +} + +function CarIcon(): JSX.Element { + return ( + <> + + + + + + ); +} + +function BicycleIcon(): JSX.Element { + return ( + <> + + + + + + ); +} + +function vehicleIconContent(vehicleType?: string): JSX.Element { + switch (normalizedVehicleType(vehicleType)) { + case "truck": + return ; + case "bicycle": + return ; + default: + return ; + } +} + +export default function VehicleTypeIcon({ + vehicleType, + className = "h-6 w-6 shrink-0", +}: VehicleTypeIconProps): JSX.Element { + return ( + + {vehicleIconContent(vehicleType)} + + ); +} From f64fc01fd1266ed51e56c2e1361abd9169bc0742 Mon Sep 17 00:00:00 2001 From: Guritfak Gill Date: Sat, 18 Jul 2026 21:20:53 -0700 Subject: [PATCH 09/13] fix(ui): clarify saved import format Co-authored-by: Cursor --- app/ui/src/app/upload-save-point/page.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/ui/src/app/upload-save-point/page.tsx b/app/ui/src/app/upload-save-point/page.tsx index c66a6132..d7883bdc 100644 --- a/app/ui/src/app/upload-save-point/page.tsx +++ b/app/ui/src/app/upload-save-point/page.tsx @@ -113,9 +113,9 @@ export default function UploadSavePointPage() { return ( Date: Sat, 18 Jul 2026 21:20:54 -0700 Subject: [PATCH 10/13] fix(ui): link app headers to landing page Co-authored-by: Cursor --- app/ui/src/app/components/navbar/MobileNavbar.tsx | 10 +++++++++- app/ui/src/app/components/navbar/Navbar.tsx | 10 +++++++++- .../src/app/results/components/MobileResultsNavbar.tsx | 10 ++++++++-- app/ui/src/app/results/page.tsx | 10 +++++++++- 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/app/ui/src/app/components/navbar/MobileNavbar.tsx b/app/ui/src/app/components/navbar/MobileNavbar.tsx index 870dbc30..8de170df 100644 --- a/app/ui/src/app/components/navbar/MobileNavbar.tsx +++ b/app/ui/src/app/components/navbar/MobileNavbar.tsx @@ -1,5 +1,6 @@ "use client"; +import Link from "next/link"; import { MOBILE_NAVBAR_LEFT_GROUP, MOBILE_NAVBAR_MENU_BTN, @@ -34,7 +35,14 @@ export default function MobileNavbar({ onMenuClick }: MobileNavbarProps) { /> - Delivery Optimizer + + Delivery Optimizer +
); diff --git a/app/ui/src/app/components/navbar/Navbar.tsx b/app/ui/src/app/components/navbar/Navbar.tsx index 396659ac..8f51ce02 100644 --- a/app/ui/src/app/components/navbar/Navbar.tsx +++ b/app/ui/src/app/components/navbar/Navbar.tsx @@ -1,5 +1,6 @@ "use client"; +import Link from "next/link"; import { NAVBAR_V2_BTN_SAVE, NAVBAR_V2_LOGO, @@ -13,7 +14,14 @@ type NavbarProps = { export default function Navbar({ onSave }: NavbarProps) { return (
- Delivery Optimizer + + Delivery Optimizer + diff --git a/app/ui/src/app/results/components/MobileResultsNavbar.tsx b/app/ui/src/app/results/components/MobileResultsNavbar.tsx index 65eb6726..7d410ea5 100644 --- a/app/ui/src/app/results/components/MobileResultsNavbar.tsx +++ b/app/ui/src/app/results/components/MobileResultsNavbar.tsx @@ -1,5 +1,6 @@ "use client"; +import Link from "next/link"; import { MOBILE_NAVBAR_LEFT_GROUP, MOBILE_NAVBAR_MENU_BTN, @@ -49,9 +50,14 @@ export default function MobileResultsNavbar({ /> - + Delivery Optimizer - +
{isEditMode && ( diff --git a/app/ui/src/app/results/page.tsx b/app/ui/src/app/results/page.tsx index cdf6e4bd..64fc39f3 100644 --- a/app/ui/src/app/results/page.tsx +++ b/app/ui/src/app/results/page.tsx @@ -10,6 +10,7 @@ import { useState, useSyncExternalStore, } from "react"; +import Link from "next/link"; import { NAVBAR_V2_LOGO, NAVBAR_V2_ROOT } from "../edit/formStyles.v2"; import styles from "../edit/edit.module.css"; import MobileSidebar from "../components/sidebar/MobileSidebar"; @@ -416,7 +417,14 @@ export default function ResultsPage() {