diff --git a/src/app/rides/new/page.tsx b/src/app/rides/new/page.tsx index 761873d..7bdd709 100644 --- a/src/app/rides/new/page.tsx +++ b/src/app/rides/new/page.tsx @@ -4,7 +4,6 @@ import React, { useState, useEffect, useRef } from "react"; import { fromZonedTime } from "date-fns-tz"; import { useRouter } from "next/navigation"; import Link from "next/link"; -import Image from "next/image"; import { TimeInput } from "@/components/TimeInput/TimeInput"; import styles from "./styles.module.css"; @@ -18,6 +17,14 @@ type Location = { longitude: number; }; +const SERVICE_START_TIME = "07:30"; +const SERVICE_END_TIME = "19:45"; + +function isServiceDay(date: Date): boolean { + const day = date.getDay(); + return day >= 1 && day <= 5; +} + export default function CreateRidePage() { const router = useRouter(); const [locations, setLocations] = useState([]); @@ -36,6 +43,9 @@ export default function CreateRidePage() { const mapContainerRef = useRef(null); const mapRef = useRef(null); + const focusSelectedLocationsRef = useRef< + (map: mapboxgl.Map, duration: number) => void + >(() => {}); const markerRefs = useRef([]); const dotMarkerRefs = useRef([]); @@ -50,7 +60,7 @@ export default function CreateRidePage() { try { const res = await fetch("/api/locations"); if (!res.ok) throw new Error("Failed to fetch locations"); - const data = await res.json(); + const data = (await res.json()) as Location[]; setLocations(data); if (data.length > 0) { @@ -104,6 +114,43 @@ export default function CreateRidePage() { return defaultCenter; }; + const focusSelectedLocations = (map: mapboxgl.Map, duration: number) => { + if (pickup && dropoff) { + const pickupCoordinates: [number, number] = [ + pickup.longitude, + pickup.latitude, + ]; + const dropoffCoordinates: [number, number] = [ + dropoff.longitude, + dropoff.latitude, + ]; + const sameLocation = + Math.abs(pickup.longitude - dropoff.longitude) < 0.0003 && + Math.abs(pickup.latitude - dropoff.latitude) < 0.0003; + + if (!sameLocation) { + const bounds = new mapboxgl.LngLatBounds( + pickupCoordinates, + pickupCoordinates, + ); + bounds.extend(dropoffCoordinates); + map.fitBounds(bounds, { + padding: 64, + maxZoom: defaultZoom, + duration, + }); + return; + } + } + + map.flyTo({ + center: center(), + zoom: defaultZoom, + duration, + }); + }; + focusSelectedLocationsRef.current = focusSelectedLocations; + mapboxgl.accessToken = token; if (!mapRef.current) { mapRef.current = new mapboxgl.Map({ @@ -112,9 +159,13 @@ export default function CreateRidePage() { center: center(), zoom: defaultZoom, }); - mapRef.current.on("load", () => mapRef.current?.resize()); - } else { - mapRef.current.flyTo({ center: center(), zoom: defaultZoom }); + mapRef.current.on("load", () => { + if (!mapRef.current) return; + mapRef.current.resize(); + focusSelectedLocationsRef.current(mapRef.current, 0); + }); + } else if (mapRef.current.loaded()) { + focusSelectedLocations(mapRef.current, 700); } markerRefs.current.forEach((marker) => marker.remove()); @@ -124,12 +175,10 @@ export default function CreateRidePage() { const createCustomPin = ( labelText: string, - color: string, extraStemPx = 0, ): HTMLDivElement => { const root = document.createElement("div"); root.className = styles.mapPinRoot; - root.style.setProperty("--pin-color", color); const label = document.createElement("div"); label.textContent = labelText; @@ -166,7 +215,6 @@ export default function CreateRidePage() { return wrapper; }; - const pinColor = "#183777"; const pickupLngLat: [number, number] | null = pickup ? [pickup.longitude, pickup.latitude] : null; @@ -179,7 +227,7 @@ export default function CreateRidePage() { Math.abs(pickupLngLat[0] - dropoffLngLat[0]) < 0.0003 && Math.abs(pickupLngLat[1] - dropoffLngLat[1]) < 0.0003; - // Add blue dot markers for all non-selected locations + // Add a dot for every non-selected campus location. for (const loc of locations) { const isPickup = loc.name === pickupLocationName; const isDropoff = loc.name === dropoffLocationName; @@ -196,7 +244,7 @@ export default function CreateRidePage() { if (pickup && pickupLngLat) { const pickupMarker = new mapboxgl.Marker({ - element: createCustomPin(`Pickup: ${pickup.name}`, pinColor), + element: createCustomPin(`Pickup: ${pickup.name}`), anchor: "bottom", }) .setLngLat(pickupLngLat) @@ -209,7 +257,6 @@ export default function CreateRidePage() { const dropoffMarker = new mapboxgl.Marker({ element: createCustomPin( `Dropoff: ${dropoff.name}`, - pinColor, overlap ? 50 : 0, ), anchor: "bottom", @@ -303,11 +350,30 @@ export default function CreateRidePage() { return; } + if (!isServiceDay(selectedDate)) { + setError("Ride dates must be Monday through Friday."); + return; + } + if (!pickupWindowFromTime || !pickupWindowToTime) { setError("Please provide both pickup window start and end times."); return; } + const requestedTimes = [ + pickupTime, + pickupWindowFromTime, + pickupWindowToTime, + ]; + if ( + requestedTimes.some( + (time) => time < SERVICE_START_TIME || time > SERVICE_END_TIME, + ) + ) { + setError("Pickup time and window must be within service hours."); + return; + } + // Build UTC timestamps treating the user-selected date/time as America/New_York const toEstUtc = (date: Date, timeStr: string): Date => { const [h, m] = timeStr.split(":").map(Number); @@ -405,6 +471,7 @@ export default function CreateRidePage() { ); const isSelected = selectedDate?.toDateString() === date.toDateString(); const isToday = new Date().toDateString() === date.toDateString(); + const isAvailable = isServiceDay(date); days.push( , @@ -442,37 +511,19 @@ export default function CreateRidePage() { )}
- ← Back to Rides + ← Back to rides -

Create Ride

+

Request Ride

-
-

Ride Details

-

- Please enter your desired ride information accordingly. -

-
- -
+
{/* Left Column */}
{/* Ride Date */}
-
-

- Ride Date* -

- Calendar -
+

Ride Date

-

Pick a day.

{/* Pickup Time */}
-

- Pickup Time* -

+

Pickup Time

- Please enter the exact time that you'd like to be - picked up. + Enter the exact time that you'd like to be picked up.

@@ -555,9 +604,7 @@ export default function CreateRidePage() { {/* Pickup Time Window */}
-

- Pickup Time Window* -

+

Pickup Time Window

(E.g. 12:15 PM - 12:45 PM)

@@ -574,12 +621,13 @@ export default function CreateRidePage() { id="pickup-window-from" value={pickupWindowFromTime} onChange={setPickupWindowFromTime} + min={SERVICE_START_TIME} + max={SERVICE_END_TIME} inputClassName={styles.pickupWindowInput} className={styles.pickupWindowInputWrapper} />
- →
+ + {/* Recurring rides - TBD on implementation */} +
+ + +
{/* Column Divider */} @@ -611,11 +684,9 @@ export default function CreateRidePage() { {/* Pickup Location */}
-

- Pickup Location* -

+

Pickup Location

- Please type or locate on the above map the{" "} + Type or locate on the map the{" "} on campus location that you'd like to be picked up at.

@@ -628,11 +699,19 @@ export default function CreateRidePage() { className={styles.locationIcon} > + setDropoffLocationName(e.target.value)} className={styles.locationSelect} @@ -696,13 +781,18 @@ export default function CreateRidePage() {
{/* Submit Button */} - +
+ {/* On hold until recurring rides functionality

+ You are requesting 1 ride(s). +

*/} + +
diff --git a/src/app/rides/new/styles.module.css b/src/app/rides/new/styles.module.css index 100b89e..8670d8d 100644 --- a/src/app/rides/new/styles.module.css +++ b/src/app/rides/new/styles.module.css @@ -25,26 +25,26 @@ } .main { - max-width: var(--layout-content-max); + max-width: 76.4rem; margin-inline: auto; - padding: 2.4rem 5rem 4rem; + padding: 4rem 0 8rem; display: flex; flex-direction: column; - gap: 1.6rem; } /* Back Button */ .backButton { font-family: var(--font-paragraph); font-weight: 400; - font-size: 1.6rem; + font-size: 1.4rem; line-height: 2rem; color: var(--color-grey-text-strong); text-decoration: none; display: inline-flex; align-items: center; gap: 0.4rem; - padding: 0.4rem 0; + padding: 0; + align-self: flex-start; } .backButton:hover { @@ -54,141 +54,106 @@ /* Page Title */ .pageTitle { font-family: var(--font-heading); - font-size: 4.8rem; - line-height: 5.6rem; + font-size: 3.2rem; + line-height: 4rem; color: var(--color-grey-text-strong); - margin: 0; + margin: 0.2rem 0 1rem; } /* Ride Details Section */ .rideDetailsSection { - display: flex; - flex-direction: column; - gap: 2.4rem; -} - -.sectionHeader { - display: flex; - flex-direction: column; - gap: 0.8rem; -} - -.sectionTitle { - font-family: var(--font-heading); - font-size: 4rem; - line-height: 4.8rem; - color: var(--color-grey-text-strong); - margin: 0; + width: 100%; } -.sectionDescription { - font-family: var(--font-paragraph); - font-weight: 400; - font-size: 1.8rem; - line-height: 2.4rem; - color: var(--color-grey-text-weak); - margin: 0; +.rideForm { + width: 100%; } /* Ride Details Outline */ .rideDetailsOutline { - display: flex; - flex-direction: row; - align-items: stretch; - padding: 3.2rem 0; - border: 1px solid var(--color-grey-stroke-weak); - border-radius: 2rem; + display: grid; + grid-template-columns: minmax(0, 30.9rem) 0.1rem minmax(0, 30.9rem); + column-gap: 7.25rem; + align-items: start; + padding: 0; + border: 0; box-sizing: border-box; width: 100%; - max-width: 96rem; } /* Left Column */ .leftColumn { - flex: 1; min-width: 0; display: flex; flex-direction: column; - gap: 3.2rem; - padding: 0 3.2rem; + gap: 4.4rem; + padding: 0; +} + +.leftColumn > .formGroup:first-child { + gap: 1.6rem; } /* Column Divider */ .columnDivider { width: 1px; + height: 100%; background: var(--color-grey-stroke-weak); - flex-shrink: 0; align-self: stretch; } /* Right Column */ .rightColumn { - flex: 1; min-width: 0; display: flex; flex-direction: column; - gap: 2.4rem; - padding: 0 3.2rem; + gap: 0; + padding: 0; +} + +.rightColumn > .formGroup + .formGroup { + margin-top: 3.2rem; } /* Form Groups */ .formGroup { display: flex; flex-direction: column; - gap: 1rem; -} - -.formGroupHeader { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; + gap: 0.4rem; } .formGroupTitle { font-family: var(--font-heading); - font-size: 2.4rem; - line-height: 3.2rem; + font-size: 1.8rem; + line-height: 2.4rem; color: var(--color-grey-text-strong); margin: 0; } -.required { - color: #c73a3a; - margin-left: 0.1rem; -} - .fieldDescription { font-family: var(--font-paragraph); font-weight: 400; - font-size: 1.4rem; - line-height: 2rem; + font-size: 1.2rem; + line-height: 1.6rem; color: var(--color-grey-text-weak); margin: 0; } -.calendarIcon { - width: 3.2rem; - height: 3.2rem; - flex-shrink: 0; -} - /* Date Picker */ .datePicker { display: flex; flex-direction: column; align-items: center; - padding: 2.4rem 1.2rem; - gap: 0.8rem; + padding: 2.4rem 2rem 1.8rem; + gap: 0.4rem; background: var(--color-solid-bg-base); - border: 1px solid var(--color-grey-stroke-weak); - box-shadow: - 0 0.8rem 0.8rem -0.4rem rgba(0, 0, 0, 0.04), - 0 2rem 2.4rem -0.4rem rgba(0, 0, 0, 0.08); - border-radius: 1.6rem; - width: 100%; - max-width: 38rem; - aspect-ratio: 1 / 1; + border: 0.1rem solid var(--color-grey-stroke-weak); + box-shadow: none; + border-radius: 1.2rem; + width: 29rem; + max-width: 100%; + height: 24rem; + align-self: center; justify-content: center; box-sizing: border-box; } @@ -224,8 +189,8 @@ .yearName { font-family: var(--font-paragraph); font-weight: 700; - font-size: 1.8rem; - line-height: 2.4rem; + font-size: 1.4rem; + line-height: 2rem; color: var(--color-grey-text-strong); } @@ -248,12 +213,12 @@ display: flex; justify-content: center; align-items: center; - width: 3.2rem; - height: 3.2rem; + width: 2.4rem; + height: 2.4rem; font-family: var(--font-paragraph); font-weight: 600; - font-size: 1.2rem; - line-height: 1.6rem; + font-size: 1rem; + line-height: 1.4rem; text-align: center; color: var(--color-grey-text-weak); } @@ -266,33 +231,39 @@ } .dateEmpty { - width: 3.2rem; - height: 3.2rem; + width: 2.4rem; + height: 2.4rem; } .dateCell { display: flex; justify-content: center; align-items: center; - width: 3.2rem; - height: 3.2rem; + width: 2.4rem; + height: 2.4rem; background: none; border: none; cursor: pointer; font-family: var(--font-paragraph); font-weight: 600; - font-size: 1.2rem; - line-height: 1.6rem; + font-size: 1rem; + line-height: 1.4rem; text-align: center; color: var(--color-grey-text-strong); border-radius: 50%; justify-self: center; } -.dateCell:hover { +.dateCell:hover:not(:disabled) { background: var(--color-brand-fill); } +.dateCell:disabled { + color: var(--color-grey-text-weak); + cursor: not-allowed; + opacity: 0.5; +} + .dateSelected { background: var(--color-brand-text) !important; color: white !important; @@ -320,13 +291,21 @@ display: flex; flex-direction: row; align-items: center; - padding: 1.4rem 1.6rem; + padding: 0.6rem 0.8rem; gap: 0.8rem; background: var(--color-grey-fill-weaker); - border-bottom: 1px solid var(--color-grey-fill-weak); + border: 0.1rem solid var(--color-grey-stroke-weak); + border-radius: 0.2rem; box-sizing: border-box; width: 100%; - height: 5.6rem; + height: 3.2rem; +} + +.locationCell { + gap: 1.2rem; + background: var(--color-solid-bg-sunken); + border-color: var(--color-grey-stroke-weak); + border-radius: 0.4rem; } .timeInput, @@ -337,8 +316,8 @@ background: transparent; font-family: var(--font-paragraph); font-weight: 400; - font-size: 1.6rem; - line-height: 2rem; + font-size: 1.2rem; + line-height: 1.6rem; color: var(--color-grey-text-strong); outline: none; cursor: pointer; @@ -377,6 +356,22 @@ flex-shrink: 0; } +.locationIcon { + color: var(--color-status-red-text); +} + +.locationIconGreen { + color: var(--color-status-green-text); +} + +.locationIconFill { + fill: currentColor; +} + +.locationIconCenter { + fill: var(--color-solid-bg-base); +} + .locationSelect option { background: var(--color-solid-bg-base); color: var(--color-grey-text-strong); @@ -386,24 +381,29 @@ .pickupWindowRow { width: 100%; display: flex; - align-items: flex-start; - gap: 0.8rem; + align-items: center; + gap: 1rem; } .pickupWindowField { flex: 1; display: flex; - flex-direction: column; - gap: 0.4rem; + flex-direction: row; + align-items: center; + gap: 0.8rem; + min-width: 0; } .pickupWindowCell { display: flex; align-items: center; - height: 5.6rem; - padding: 1.4rem 1.6rem; + flex: 1; + min-width: 0; + height: 3.2rem; + padding: 0.6rem 0.8rem; background: var(--color-grey-fill-weaker); - border-bottom: 1px solid var(--color-grey-fill-weak); + border: 0.1rem solid var(--color-grey-stroke-weak); + border-radius: 0.2rem; box-sizing: border-box; } @@ -411,8 +411,8 @@ font-family: var(--font-paragraph); font-size: 1.2rem; line-height: 1.6rem; - font-weight: 600; - color: var(--color-grey-text-weak); + font-weight: 400; + color: var(--color-grey-text-strong); margin: 0; } @@ -422,8 +422,8 @@ width: 100%; min-width: 0; font-family: var(--font-paragraph); - font-size: 1.6rem; - line-height: 2rem; + font-size: 1.2rem; + line-height: 1.6rem; color: var(--color-grey-text-strong); outline: none; } @@ -433,29 +433,82 @@ -webkit-appearance: none; } -.pickupWindowArrow { - color: var(--color-grey-text-weak); - font-size: 1.6rem; - line-height: 1; - font-weight: 400; - /* align with the input cell, which is below the label */ - margin-top: calc(1.6rem + 0.4rem); /* label height + gap */ - height: 5.6rem; +/* Non-functional preview for the planned recurring-ride feature. */ +.recurringRide { + display: flex; + flex-direction: column; + gap: 0.8rem; +} + +.recurringRideLabel { display: flex; align-items: center; - flex-shrink: 0; + gap: 0.8rem; + color: var(--color-grey-text-weak); + font-family: var(--font-paragraph); + font-size: 1.2rem; + line-height: 1.6rem; +} + +.recurringRideCheckbox { + appearance: none; + position: relative; + width: 1.2rem; + height: 1.2rem; + margin: 0; + border: 0.1rem solid var(--color-grey-stroke-strong); + border-radius: 0.2rem; + background: var(--color-solid-bg-base); + opacity: 1; + cursor: pointer; +} + +.recurringRideCheckbox:checked { + border-color: var(--color-brand-text); + background: var(--color-brand-text); +} + +.recurringRideCheckbox:checked::after { + content: "✓"; + position: absolute; + inset: 0; + display: grid; + place-items: center; + color: var(--color-solid-bg-base); + font-size: 0.9rem; + line-height: 1; +} + +.recurringRideSelect { + width: 100%; + height: 3.2rem; + padding: 0.6rem 0.8rem; + border: 0.1rem solid var(--color-grey-stroke-weak); + border-radius: 0.2rem; + background: var(--color-grey-fill-weaker); + color: var(--color-grey-text-weak); + font-family: var(--font-paragraph); + font-size: 1.2rem; + line-height: 1.6rem; + cursor: pointer; +} + +.recurringRideSelect:disabled { + opacity: 0.5; + cursor: not-allowed; } -/* Map — square, matches calendar widget */ +/* Map */ .mapImage { width: 100%; - aspect-ratio: 1 / 1; + height: 27.9rem; background-size: cover; background-position: center; background-repeat: no-repeat; - border: 1px solid var(--color-grey-stroke-weak); - border-radius: 1.6rem; + border: 0.1rem solid var(--color-grey-stroke-weak); + border-radius: 0; overflow: hidden; + margin-bottom: 4.4rem; } /* Map Pin */ @@ -467,31 +520,32 @@ } .mapPinLabel { - padding: 0.55rem 1.1rem; - border-radius: 0.6rem; - background-color: var(--pin-color); - color: white; + padding: 0.45rem 1.1rem; + border: 0.2rem solid var(--color-grey-text-strong); + border-radius: 1.6rem; + background-color: var(--color-map-pin); + color: var(--color-solid-bg-base); font-weight: 700; - font-size: 1.2rem; + font-size: 1.1rem; font-family: var(--font-paragraph); - line-height: 1.4; + line-height: 1.4rem; white-space: nowrap; - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25); + box-shadow: none; } .mapPinStem { - width: 0.25rem; + width: 0.2rem; height: 2.2rem; - background-color: var(--pin-color); + background-color: var(--color-map-pin); } .mapPinDot { - width: 1.05rem; - height: 1.05rem; + width: 1.4rem; + height: 1.4rem; border-radius: 50%; - background-color: var(--pin-color); - border: 0.25rem solid white; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + background-color: var(--color-map-pin); + border: 0.2rem solid var(--color-grey-text-strong); + box-shadow: none; } /* Location dot markers (all available locations) */ @@ -505,12 +559,12 @@ } .mapLocationDot { - width: 1.2rem; - height: 1.2rem; + width: 1.5rem; + height: 1.5rem; border-radius: 50%; - background: #2563eb; - border: 0.2rem solid white; - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); + background: var(--color-map-location-fill); + border: 0.2rem solid var(--color-grey-text-strong); + box-shadow: none; } .mapDotTooltip { @@ -518,10 +572,11 @@ bottom: calc(100% + 0.5rem); left: 50%; transform: translateX(-50%); - background: #183777; - color: white; + background: var(--color-brand-text); + color: var(--color-solid-bg-base); padding: 0.4rem 0.8rem; - border-radius: 0.4rem; + border: 0.1rem solid var(--color-grey-text-strong); + border-radius: 1.2rem; font-size: 1.1rem; font-family: var(--font-paragraph); font-weight: 600; @@ -537,23 +592,40 @@ } /* Submit Button */ +.submitRow { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1.6rem; + width: calc(100% + 2.2rem); + margin: 5rem 0 0 -2.2rem; +} + +.requestSummary { + margin: 0; + color: var(--color-grey-text-weak); + font-family: var(--font-paragraph); + font-size: 1.2rem; + line-height: 1.6rem; + white-space: nowrap; +} + .submitButton { display: flex; align-items: center; justify-content: center; - width: 100%; - height: 5rem; + width: 16rem; + height: 3.4rem; background: var(--color-brand-text); border-radius: 0.4rem; border: none; cursor: pointer; font-family: var(--font-paragraph); font-weight: 600; - font-size: 1.8rem; - line-height: 2.4rem; + font-size: 1.4rem; + line-height: 2rem; color: white; transition: background-color 0.2s; - margin-top: auto; } .submitButton:hover:not(:disabled) { @@ -567,9 +639,15 @@ /* Responsive */ @media (max-width: 900px) { + .main { + max-width: 64rem; + padding: 3.2rem; + } + .rideDetailsOutline { + display: flex; flex-direction: column; - padding: 3.2rem; + gap: 4rem; } .leftColumn, @@ -580,13 +658,20 @@ } .columnDivider { - width: 100%; - height: 1px; - align-self: unset; + display: none; + } + + .datePicker { + max-width: 38rem; } .mapImage { - height: 24rem; + height: 32rem; + } + + .submitRow { + width: 100%; + margin-left: 0; } } @@ -601,6 +686,16 @@ } .rideDetailsOutline { - padding: 2rem; + gap: 3.2rem; + } + + .pickupWindowRow, + .submitRow { + align-items: stretch; + flex-direction: column; + } + + .submitButton { + width: 100%; } } diff --git a/src/components/TimeInput/TimeInput.tsx b/src/components/TimeInput/TimeInput.tsx index 6d7b60d..1dfd9dd 100644 --- a/src/components/TimeInput/TimeInput.tsx +++ b/src/components/TimeInput/TimeInput.tsx @@ -33,10 +33,16 @@ function valueToLabel(value: string): string { return `${displayH}:${m.toString().padStart(2, "0")} ${period}`; } -function filterSlots(query: string): { value: string; label: string }[] { +function filterSlots( + query: string, + min?: string, + max?: string, +): { value: string; label: string }[] { const q = query.trim().toLowerCase(); - if (!q) return QUARTER_SLOTS; - const source = MINUTE_SLOTS; + const source = (q ? MINUTE_SLOTS : QUARTER_SLOTS).filter( + (slot) => (!min || slot.value >= min) && (!max || slot.value <= max), + ); + if (!q) return source; const startsWith = source.filter((s) => s.label.toLowerCase().startsWith(q)); const contains = source.filter( (s) => @@ -54,6 +60,10 @@ type TimeInputProps = { /** className applied to the text input itself */ inputClassName?: string; id?: string; + /** Earliest selectable time, inclusive, in HH:MM format. */ + min?: string; + /** Latest selectable time, inclusive, in HH:MM format. */ + max?: string; }; export function TimeInput({ @@ -63,6 +73,8 @@ export function TimeInput({ className, inputClassName, id, + min, + max, }: TimeInputProps) { const [draft, setDraft] = useState(valueToLabel(value)); const [open, setOpen] = useState(false); @@ -75,7 +87,7 @@ export function TimeInput({ setDraft(valueToLabel(value)); }, [value]); - const slots = filterSlots(draft); + const slots = filterSlots(draft, min, max); const commit = useCallback( (slot: { value: string; label: string }) => { diff --git a/src/styles/globals.css b/src/styles/globals.css index b46768b..c468bf4 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -10,6 +10,8 @@ --color-brand-stroke-weak: #416ebc33; --color-brand-hover: #416ebc80; --color-brand-fill: #416ebc0d; + --color-map-pin: #416ebc; + --color-map-location-fill: #a7d0ed; --color-grey-text-strong: #22070b; --color-grey-text-weak: #22070bb2; --color-grey-stroke-strong: #22070b80; diff --git a/tests/e2e/routeB.ui.spec.ts b/tests/e2e/routeB.ui.spec.ts index f30c440..adf2584 100644 --- a/tests/e2e/routeB.ui.spec.ts +++ b/tests/e2e/routeB.ui.spec.ts @@ -33,17 +33,22 @@ test("route B: driver start → pickup → dropoff (UI)", async ({ // ── Student requests a ride ──────────────────────────────────────────── await student.goto("/rides/new"); await expect( - student.getByRole("heading", { name: "Create Ride" }), + student.getByRole("heading", { name: "Request Ride" }), ).toBeVisible(); const today = estDateDaysFromNow(0); - const tomorrow = estDateDaysFromNow(1); - if (tomorrow.getMonth() !== today.getMonth()) { + let offset = 1; + let rideDate = estDateDaysFromNow(offset); + while (rideDate.getDay() === 0 || rideDate.getDay() === 6) { + offset += 1; + rideDate = estDateDaysFromNow(offset); + } + if (rideDate.getMonth() !== today.getMonth()) { // Calendar header: [prev][month/year][next]; the nav buttons are the only icon buttons. await student.locator('button[type="button"]:has(svg)').nth(1).click(); } await student - .getByRole("button", { name: String(tomorrow.getDate()), exact: true }) + .getByRole("button", { name: String(rideDate.getDate()), exact: true }) .click(); const times = student.getByPlaceholder("hh:mm"); @@ -51,9 +56,12 @@ test("route B: driver start → pickup → dropoff (UI)", async ({ await pickTime(student.locator("#pickup-window-from"), "9:30 AM"); await pickTime(student.locator("#pickup-window-to"), "10:30 AM"); - const selects = student.locator("select"); - await selects.nth(0).selectOption({ label: LOCATIONS.exhibitionHall.name }); - await selects.nth(1).selectOption({ label: LOCATIONS.techSquare.name }); + await student + .getByRole("combobox", { name: "Pickup location" }) + .selectOption({ label: LOCATIONS.exhibitionHall.name }); + await student + .getByRole("combobox", { name: "Dropoff location" }) + .selectOption({ label: LOCATIONS.techSquare.name }); const created = student.waitForResponse( (r) => r.url().endsWith("/api/routes") && r.request().method() === "POST",