Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions app/ui/src/app/edit/components/layout/sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import { SIDEBAR_ROOT, SIDEBAR_NAV } from "@/app/edit/formStyles.v2";

type SidebarProps = {
children: React.ReactNode;
className?: string;
};

export default function Sidebar({ children }: SidebarProps) {
export default function Sidebar({ children, className }: SidebarProps) {
return (
<aside className={SIDEBAR_ROOT}>
<aside className={[SIDEBAR_ROOT, className].filter(Boolean).join(" ")}>
<div className={SIDEBAR_NAV}>{children}</div>
</aside>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
"use client";

import Link from "next/link";
import { usePathname } from "next/navigation";
import { useHasOptimizeResults } from "../../../utils/hasOptimizeResults";
import {
SIDEBAR_NAV_ITEM_ACTIVE,
SIDEBAR_NAV_ITEM_DISABLED,
SIDEBAR_NAV_ITEM_INACTIVE,
SIDEBAR_NAV_LABEL_ACTIVE,
SIDEBAR_NAV_LABEL_INACTIVE,
SIDEBAR_NAV_PILL_ACTIVE,
SIDEBAR_NAV_PILL_INACTIVE,
} from "@/app/edit/formStyles.v2";

Expand All @@ -12,26 +20,48 @@ const SIDEBAR_RESULTS_ICON = (
height="24"
viewBox="0 0 24 24"
fill="none"
aria-hidden
>
<path
d="M4.35 20.7C4.01667 20.8333 3.70833 20.7958 3.425 20.5875C3.14167 20.3792 3 20.1 3 19.75V5.75C3 5.53333 3.0625 5.34167 3.1875 5.175C3.3125 5.00833 3.48333 4.88333 3.7 4.8L9 3L15 5.1L19.65 3.3C19.9833 3.16667 20.2917 3.20417 20.575 3.4125C20.8583 3.62083 21 3.9 21 4.25V12.675C20.75 12.2917 20.4542 11.9417 20.1125 11.625C19.7708 11.3083 19.4 11.0333 19 10.8V5.7L16 6.85V10C15.65 10 15.3083 10.0292 14.975 10.0875C14.6417 10.1458 14.3167 10.2333 14 10.35V6.85L10 5.45V18.525L4.35 20.7ZM5 18.3L8 17.15V5.45L5 6.45V18.3ZM17.4125 17.5C17.7875 17.1667 17.9833 16.6667 18 16C18.0167 15.4333 17.8292 14.9583 17.4375 14.575C17.0458 14.1917 16.5667 14 16 14C15.4333 14 14.9583 14.1917 14.575 14.575C14.1917 14.9583 14 15.4333 14 16C14 16.5667 14.1917 17.0417 14.575 17.425C14.9583 17.8083 15.4333 18 16 18C16.5667 18 17.0375 17.8333 17.4125 17.5ZM16 20C14.9 20 13.9583 19.6083 13.175 18.825C12.3917 18.0417 12 17.1 12 16C12 14.9 12.3917 13.9583 13.175 13.175C13.9583 12.3917 14.9 12 16 12C17.1 12 18.0417 12.3917 18.825 13.175C19.6083 13.9583 20 14.9 20 16C20 16.3833 19.9542 16.7458 19.8625 17.0875C19.7708 17.4292 19.6333 17.75 19.45 18.05L22 20.6L20.6 22L18.05 19.45C17.75 19.6333 17.4292 19.7708 17.0875 19.8625C16.7458 19.9542 16.3833 20 16 20Z"
fill="var(--edit-text-primary)"
fill="currentColor"
/>
</svg>
);

export default function SidebarResultsButton() {
const pathname = usePathname();
const isResultsPage = pathname === "/results";
const hasStoredRoutes = useHasOptimizeResults();

if (isResultsPage) {
return (
<span className={SIDEBAR_NAV_ITEM_ACTIVE} aria-current="page">
<span
className={`${SIDEBAR_NAV_PILL_ACTIVE} text-[var(--edit-foreground)]`}
>
{SIDEBAR_RESULTS_ICON}
</span>
<span className={SIDEBAR_NAV_LABEL_ACTIVE}>Results</span>
</span>
);
}

if (hasStoredRoutes) {
return (
<Link href="/results" className={SIDEBAR_NAV_ITEM_INACTIVE}>
<span className={SIDEBAR_NAV_PILL_INACTIVE}>
{SIDEBAR_RESULTS_ICON}
</span>
<span className={SIDEBAR_NAV_LABEL_INACTIVE}>Results</span>
</Link>
);
}

return (
<Link
href="#"
className={SIDEBAR_NAV_ITEM_DISABLED}
aria-disabled="true"
tabIndex={-1}
>
{" "}
{/* TODO: add results page link when at least one route exists */}
<span className={SIDEBAR_NAV_ITEM_DISABLED} aria-disabled="true">
<span className={SIDEBAR_NAV_PILL_INACTIVE}>{SIDEBAR_RESULTS_ICON}</span>
<span className={SIDEBAR_NAV_LABEL_INACTIVE}>Results</span>
</Link>
</span>
);
}
90 changes: 3 additions & 87 deletions app/ui/src/app/edit/edit.module.css
Original file line number Diff line number Diff line change
@@ -1,93 +1,9 @@
/**
* Scoped design tokens for the edit page.
* Import this only in app/edit/page.tsx — does not affect other pages.
*
* Usage in page.tsx:
* import styles from './edit.module.css';
* <div className={styles.root}>...</div>
*
* All CSS variables defined on .root are available to every descendant,
* and can be referenced in formStyles.v2.ts as Tailwind arbitrary values:
* bg-[var(--edit-page-bg)]
* text-[var(--edit-foreground)]
* etc.
*/

/* tokens in shared/editDesignTokens.module.css */
.root {
/* ── Teal brand ──────────────────────────────────────────────── */
--edit-teal-300: #57ac91;
--edit-teal-500: #397461;
--edit-teal-600: #267b67;
--edit-teal-700: #265749;
--edit-teal-alpha: #39746126;
--edit-pagination-active-bg: rgba(26, 173, 144, 0.24);
--edit-pagination-mobile-active-bg: #c3e1d8;

/* ── Stone neutrals (Figma Stone scale) ─────────────────────── */
--edit-bg-primary: #fdfdfc;
--edit-stone-50: #f8f7f5;
--edit-stone-200: #dcdbd8;
--edit-stone-500: #908f8c;
--edit-stone-600: #777673;
--edit-stone-700: #5d5c59;

/* ── Nav ─────────────────────────────────────────────────────── */
--edit-container-active: #d5f2e8;
--edit-text-primary: #272725;
--edit-text-secondary: #464544;

/* ── Buttons ──────────────────────────────────────────────────── */
--edit-btn-primary: #4cb599;

--edit-secondary-btn-hover: rgba(0, 0, 0, 0.02);
--edit-secondary-btn-pressed: rgba(0, 0, 0, 0.04);

--edit-tertiary-btn-hover: #f6f5f2;
--edit-tertiary-btn-pressed: #f6f5f2;

/* ── Semantic status ─────────────────────────────────────────── */
--edit-container-success: #daf1db;
--edit-text-success: #2a7e3b;
--edit-required-asterisk: #da1b0b;
--edit-error-border: #da1b0b;
--edit-error-bg: #fef2f2;
--edit-footer-logo-bg: url("/b2logo.png") -25.852px -10.077px / 293.436%
265.161% no-repeat;

/* ── Delete button ──────────────────────────────────────────── */
--edit-btn-delete: #da1b0b;
--edit-text-invert: #fdfdfc;

/* ── Icons ──────────────────────────────────────────────────── */
--edit-icon-vehicle: #1c1b1f;
--edit-icon-location: #1c1b1f;
--edit-primary-icon: #3d3d3c;

/* ── Empty state illustrations ──────────────────────────────── */
--edit-empty-state-shadow: #e8e8e8;

/* Vehicle illustration */
--edit-vehicle-body: #4f46e5;
--edit-vehicle-window: #a5b4fc;
--edit-vehicle-accent: #3730a3;
--edit-vehicle-detail: #c7d2fe;
--edit-vehicle-wheel-outer: #1e1e2e;
--edit-vehicle-wheel-mid: #6b7280;
--edit-vehicle-wheel-inner: #d1d5db;
--edit-vehicle-light: #fcd34d;
--edit-vehicle-back-panel: #312e81;

/* Address illustration */
--edit-address-accent: #f97316;
--edit-address-on-accent: #ffffff;
--edit-address-card-bg: #fff7ed;
--edit-address-card-border: #fed7aa;
--edit-address-card-header: #ffedd5;
--edit-address-card-line-dark: #e5e7eb;
--edit-address-card-line-light: #f3f4f6;
composes: root from "../shared/editDesignTokens.module.css";
}

/* ── Primary Button Hover and Pressed States Overlay ──────────────────────── */
/* primary button hover overlay */
.primaryBtnOverlay {
position: relative;
overflow: hidden;
Expand Down
3 changes: 2 additions & 1 deletion app/ui/src/app/edit/hooks/useOptimize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
vehicleRowToVehicleInput,
} from "@/app/edit/utils/optimizeMapper";
import { SUPPORTED_STATES } from "@/app/edit/constants/supportedRegions";
import { setOptimizeResults } from "@/app/edit/utils/hasOptimizeResults";
import { vroomToRoutes } from "@/app/edit/utils/vroomToRoutes";
import type {
AddressCard,
Expand Down Expand Up @@ -362,7 +363,7 @@ export function useOptimize(
lockedVehicles,
addresses,
);
sessionStorage.setItem("optimizeResults", JSON.stringify(routes));
setOptimizeResults(routes);
router.push("/results");
return;
}
Expand Down
57 changes: 57 additions & 0 deletions app/ui/src/app/edit/utils/hasOptimizeResults.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"use client";

import { useEffect, useState } from "react";
import type { Route } from "@/app/results/types";

export const OPTIMIZE_RESULTS_STORAGE_KEY = "optimizeResults";

export const OPTIMIZE_RESULTS_UPDATED_EVENT = "optimize-results-updated";

/** True when sessionStorage has at least one route ready for /results. */
export function readHasOptimizeResults(): boolean {
if (typeof window === "undefined") return false;

const stored = sessionStorage.getItem(OPTIMIZE_RESULTS_STORAGE_KEY);
if (!stored) return false;

try {
const parsed = JSON.parse(stored) as Route[];
return Array.isArray(parsed) && parsed.length > 0;
} catch {
return false;
}
}

export function notifyOptimizeResultsUpdated(): void {
if (typeof window === "undefined") return;
window.dispatchEvent(new Event(OPTIMIZE_RESULTS_UPDATED_EVENT));
}

export function setOptimizeResults(routes: Route[]): void {
sessionStorage.setItem(OPTIMIZE_RESULTS_STORAGE_KEY, JSON.stringify(routes));
notifyOptimizeResultsUpdated();
}

export function clearOptimizeResults(): void {
sessionStorage.removeItem(OPTIMIZE_RESULTS_STORAGE_KEY);
notifyOptimizeResultsUpdated();
}

/** Subscribes to same-tab writes and cross-tab sessionStorage changes. */
export function useHasOptimizeResults(): boolean {
const [hasResults, setHasResults] = useState(false);

useEffect(() => {
const sync = () => setHasResults(readHasOptimizeResults());
sync();

window.addEventListener(OPTIMIZE_RESULTS_UPDATED_EVENT, sync);
window.addEventListener("storage", sync);
return () => {
window.removeEventListener(OPTIMIZE_RESULTS_UPDATED_EVENT, sync);
window.removeEventListener("storage", sync);
};
}, []);

return hasResults;
}
Loading
Loading