From 185802ba611680149580488a87a558a2f918f799 Mon Sep 17 00:00:00 2001 From: Studio-18 Date: Wed, 12 Nov 2025 07:38:53 -0800 Subject: [PATCH 1/2] Add quick book lesson flow to dashboard --- src/components/coaches/BookLessonModal.css | 41 ++++++++ src/components/coaches/BookLessonModal.tsx | 31 +++++- src/pages/DashboardPage.jsx | 114 ++++++++++++++++----- 3 files changed, 159 insertions(+), 27 deletions(-) diff --git a/src/components/coaches/BookLessonModal.css b/src/components/coaches/BookLessonModal.css index ec6154e2..a49bb5c1 100644 --- a/src/components/coaches/BookLessonModal.css +++ b/src/components/coaches/BookLessonModal.css @@ -169,6 +169,47 @@ min-height: 0; } +.book-lesson-modal__coach-picker { + display: flex; + flex-direction: column; + gap: 8px; +} + +.book-lesson-modal__coach-label { + font-size: 0.9rem; + font-weight: 600; + color: var(--fc-color-text-secondary, #4b5563); +} + +.book-lesson-modal__coach-select { + position: relative; + display: flex; + align-items: center; + background: var(--fc-color-surface, #ffffff); + border: 1px solid var(--fc-color-border-strong, rgba(148, 163, 184, 0.45)); + border-radius: 12px; + padding: 0 12px; + min-height: 44px; + color: var(--fc-color-text-primary, #111827); +} + +.book-lesson-modal__coach-select select { + appearance: none; + border: none; + background: transparent; + font-size: 1rem; + font-weight: 500; + color: inherit; + width: 100%; + padding: 10px 0; + outline: none; +} + +.book-lesson-modal__coach-select svg { + flex-shrink: 0; + color: var(--fc-color-icon, #475569); +} + .book-lesson-modal__booking-surface { display: flex; flex-direction: column; diff --git a/src/components/coaches/BookLessonModal.tsx b/src/components/coaches/BookLessonModal.tsx index 02d3f721..022eea96 100644 --- a/src/components/coaches/BookLessonModal.tsx +++ b/src/components/coaches/BookLessonModal.tsx @@ -154,10 +154,12 @@ const getDateDisplayMeta = (isoDate: string) => { type BookLessonModalProps = { coach: Coach; + coachOptions?: Coach[]; + onCoachChange?: (coachId: number) => void; onClose: () => void; }; -const BookLessonModal = ({ coach, onClose }: BookLessonModalProps) => { +const BookLessonModal = ({ coach, coachOptions, onCoachChange, onClose }: BookLessonModalProps) => { const [profile, setProfile] = useState(); const [selection, setSelection] = useState({ day: ALL_DAYS_ID, @@ -331,6 +333,7 @@ const BookLessonModal = ({ coach, onClose }: BookLessonModalProps) => { const dateMenuId = useMemo(() => `book-lesson-date-menu-${coach.id}`, [coach.id]); const dateRangeHintId = useMemo(() => `book-lesson-date-hint-${coach.id}`, [coach.id]); const rangeErrorId = useMemo(() => `book-lesson-date-error-${coach.id}`, [coach.id]); + const coachSelectorId = useMemo(() => `book-lesson-coach-${coach.id}`, [coach.id]); useEffect(() => { if (!isDateMenuOpen) { @@ -557,6 +560,32 @@ const BookLessonModal = ({ coach, onClose }: BookLessonModalProps) => {
+ {coachOptions?.length ? ( +
+ +
+ + +
+
+ ) : null} {profile ? (
diff --git a/src/pages/DashboardPage.jsx b/src/pages/DashboardPage.jsx index 70bb8081..2133bc58 100644 --- a/src/pages/DashboardPage.jsx +++ b/src/pages/DashboardPage.jsx @@ -1,7 +1,9 @@ -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { MapPin } from "lucide-react"; import { useNavigate } from "react-router-dom"; +import BookLessonModal from "../components/coaches/BookLessonModal"; import MainLayout from "../components/MainLayout"; +import { mockCoaches } from "../data/mockCoaches"; import usePlayerIdentity from "../hooks/usePlayerIdentity"; const schedule = [ @@ -153,6 +155,13 @@ const quickActions = [ action: "View Coaches", className: "coaches", }, + { + id: "quick-book", + title: "Quick Book Lesson", + description: "Pick a coach and grab an open slot without leaving the dashboard.", + action: "Quick Book", + className: "quick-book", + }, ]; const matches = [ @@ -173,13 +182,6 @@ const matches = [ }, ]; -const coaches = [ - { name: "Mia Roberts", speciality: "USTA Certified", rating: "4.9", sessions: "32 lessons" }, - { name: "David Park", speciality: "High Performance", rating: "4.8", sessions: "28 lessons" }, - { name: "Jamie Lee", speciality: "Junior Development", rating: "4.9", sessions: "19 lessons" }, - { name: "Carlos Ramirez", speciality: "Serve Specialist", rating: "4.7", sessions: "24 lessons" }, -]; - const bottomActions = [ { title: "AI Match Me", @@ -198,6 +200,8 @@ const bottomActions = [ const DashboardPage = () => { const navigate = useNavigate(); const { displayName } = usePlayerIdentity(); + const [isQuickBookOpen, setIsQuickBookOpen] = useState(false); + const [selectedCoach, setSelectedCoach] = useState(null); const [locationState, setLocationState] = useState({ status: "idle", coords: null, @@ -210,6 +214,34 @@ const DashboardPage = () => { const distanceOptions = ["5 mi", "10 mi", "15 mi", "20 mi", "All"]; + const featuredCoaches = mockCoaches.slice(0, 4); + + const defaultQuickBookCoach = featuredCoaches[0] ?? mockCoaches[0] ?? null; + + const handleOpenQuickBook = (coachOverride = null) => { + const nextCoach = coachOverride ?? selectedCoach ?? defaultQuickBookCoach; + if (!nextCoach) { + return; + } + + setSelectedCoach(nextCoach); + setIsQuickBookOpen(true); + }; + + const handleCloseQuickBook = () => { + setIsQuickBookOpen(false); + setSelectedCoach(null); + }; + + const handleCoachChange = (coachId) => { + const nextCoach = mockCoaches.find((coach) => coach.id === coachId); + if (!nextCoach) { + return; + } + + setSelectedCoach(nextCoach); + }; + const formatCoordinatesLabel = (coords) => { if (!coords) { return "Your area"; @@ -223,7 +255,7 @@ const DashboardPage = () => { return `${latitude}° ${latHemisphere}, ${longitude}° ${lonHemisphere}`; }; - const resolveLocationName = async (coords) => { + const resolveLocationName = useCallback(async (coords) => { if (!coords) { return; } @@ -288,9 +320,9 @@ const DashboardPage = () => { }; }); } - }; + }, []); - const detectLocation = () => { + const detectLocation = useCallback(() => { if (!("geolocation" in navigator)) { setLocationState({ status: "error", @@ -338,11 +370,11 @@ const DashboardPage = () => { }); } ); - }; + }, [resolveLocationName]); useEffect(() => { detectLocation(); - }, []); + }, [detectLocation]); const locationChipLabel = () => { if (locationState.status === "ready") { @@ -514,16 +546,32 @@ const DashboardPage = () => { ))} @@ -573,14 +621,20 @@ const DashboardPage = () => {
- {coaches.map((coach) => ( -
+ {featuredCoaches.map((coach) => ( +
{coach.name.split(" ").map((part) => part[0]).join("")}
{coach.name}
-
{coach.speciality}
-
{coach.sessions}
-
⭐ {coach.rating}
-
@@ -599,6 +653,14 @@ const DashboardPage = () => {
))} + {selectedCoach && isQuickBookOpen ? ( + + ) : null} ); }; From d6c509747909f592715d22e5f5a9f47055b69d90 Mon Sep 17 00:00:00 2001 From: Studio-18 Date: Wed, 12 Nov 2025 07:48:49 -0800 Subject: [PATCH 2/2] Style quick book quick action --- src/App.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/App.css b/src/App.css index 8b68b08f..fb42cd82 100644 --- a/src/App.css +++ b/src/App.css @@ -755,6 +755,10 @@ background: linear-gradient(135deg, #facc15, #f97316); } +.quick-card.quick-book { + background: linear-gradient(135deg, #2563eb, #0ea5e9); +} + .matches-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));