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
4 changes: 4 additions & 0 deletions src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
41 changes: 41 additions & 0 deletions src/components/coaches/BookLessonModal.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
31 changes: 30 additions & 1 deletion src/components/coaches/BookLessonModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<CoachProfile | undefined>();
const [selection, setSelection] = useState<SelectionState>({
day: ALL_DAYS_ID,
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -557,6 +560,32 @@ const BookLessonModal = ({ coach, onClose }: BookLessonModalProps) => {
</header>

<div className="book-lesson-modal__body">
{coachOptions?.length ? (
<div className="book-lesson-modal__coach-picker">
<label className="book-lesson-modal__coach-label" htmlFor={coachSelectorId}>
Coach
</label>
<div className="book-lesson-modal__coach-select">
<select
id={coachSelectorId}
value={coach.id}
onChange={(event) => {
const nextId = Number.parseInt(event.target.value, 10);
if (!Number.isNaN(nextId) && onCoachChange) {
onCoachChange(nextId);
}
}}
>
{coachOptions.map((option) => (
<option key={option.id} value={option.id}>
{option.name}
</option>
))}
</select>
<ChevronDown size={16} aria-hidden />
</div>
</div>
) : null}
{profile ? (
<div className="book-lesson-modal__booking-surface">
<div className="coach-booking__controls book-lesson-modal__controls">
Expand Down
114 changes: 88 additions & 26 deletions src/pages/DashboardPage.jsx
Original file line number Diff line number Diff line change
@@ -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 = [
Expand Down Expand Up @@ -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 = [
Expand All @@ -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",
Expand All @@ -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,
Expand All @@ -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";
Expand All @@ -223,7 +255,7 @@ const DashboardPage = () => {
return `${latitude}° ${latHemisphere}, ${longitude}° ${lonHemisphere}`;
};

const resolveLocationName = async (coords) => {
const resolveLocationName = useCallback(async (coords) => {
if (!coords) {
return;
}
Expand Down Expand Up @@ -288,9 +320,9 @@ const DashboardPage = () => {
};
});
}
};
}, []);

const detectLocation = () => {
const detectLocation = useCallback(() => {
if (!("geolocation" in navigator)) {
setLocationState({
status: "error",
Expand Down Expand Up @@ -338,11 +370,11 @@ const DashboardPage = () => {
});
}
);
};
}, [resolveLocationName]);

useEffect(() => {
detectLocation();
}, []);
}, [detectLocation]);

const locationChipLabel = () => {
if (locationState.status === "ready") {
Expand Down Expand Up @@ -514,16 +546,32 @@ const DashboardPage = () => {
<button
type="button"
onClick={() => {
if (action.id === "matches") {
navigate("/matches");
return;
}
if (action.id === "coaches") {
navigate("/find-coaches");
switch (action.id) {
case "matches":
navigate("/matches");
break;
case "players":
navigate("/community");
break;
case "lessons":
navigate("/group-lessons");
break;
case "coaches":
navigate("/find-coaches");
break;
case "quick-book":
if (isQuickBookOpen) {
handleCloseQuickBook();
} else {
handleOpenQuickBook();
}
break;
default:
break;
}
}}
>
{action.action}
{action.id === "quick-book" && isQuickBookOpen ? "Close" : action.action}
</button>
</article>
))}
Expand Down Expand Up @@ -573,14 +621,20 @@ const DashboardPage = () => {
</button>
</div>
<div className="coaches-grid">
{coaches.map((coach) => (
<article key={coach.name} className="coach-card">
{featuredCoaches.map((coach) => (
<article key={coach.id} className="coach-card">
<div className="coach-avatar">{coach.name.split(" ").map((part) => part[0]).join("")}</div>
<div className="coach-name">{coach.name}</div>
<div className="coach-speciality">{coach.speciality}</div>
<div className="coach-speciality">{coach.sessions}</div>
<div className="rating">⭐ {coach.rating}</div>
<button type="button" className="coach-btn">
<div className="coach-speciality">{coach.title}</div>
<div className="coach-speciality">{coach.availability}</div>
<div className="rating">⭐ {coach.rating.toFixed(1)}</div>
<button
type="button"
className="coach-btn"
onClick={() => {
handleOpenQuickBook(coach);
}}
>
Book Session
</button>
</article>
Expand All @@ -599,6 +653,14 @@ const DashboardPage = () => {
</article>
))}
</section>
{selectedCoach && isQuickBookOpen ? (
<BookLessonModal
coach={selectedCoach}
coachOptions={mockCoaches}
onCoachChange={handleCoachChange}
onClose={handleCloseQuickBook}
/>
) : null}
</MainLayout>
);
};
Expand Down