feat: enhance sport selection UI in public booking page
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -1,20 +1,89 @@
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Field, FieldError, FieldLabel } from '@/components/ui/field';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { ApiClientError, apiClient } from '@/lib/api-client';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
import { useNavigate } from '@tanstack/react-router';
|
||||
import { Dumbbell } from 'lucide-react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
import {SoccerBallIcon, RacquetIcon, TennisBallIcon,BasketballIcon, VolleyballIcon, BarbellIcon, PersonSimpleRunIcon, PingPongIcon} from '@phosphor-icons/react'
|
||||
|
||||
const sportConfig: Record<
|
||||
string,
|
||||
{ icon: typeof Dumbbell; color: string; bg: string; border: string; label: string }
|
||||
> = {
|
||||
tennis: {
|
||||
icon: TennisBallIcon,
|
||||
color: 'text-green-700 dark:text-green-400',
|
||||
bg: 'bg-green-50 dark:bg-green-950/40',
|
||||
border: 'border-green-200 dark:border-green-800',
|
||||
label: 'Tenis',
|
||||
},
|
||||
futbol: {
|
||||
icon: SoccerBallIcon,
|
||||
color: 'text-emerald-700 dark:text-emerald-400',
|
||||
bg: 'bg-emerald-50 dark:bg-emerald-950/40',
|
||||
border: 'border-emerald-200 dark:border-emerald-800',
|
||||
label: 'Fútbol',
|
||||
},
|
||||
paddle: {
|
||||
icon: RacquetIcon,
|
||||
color: 'text-amber-700 dark:text-amber-400',
|
||||
bg: 'bg-amber-50 dark:bg-amber-950/40',
|
||||
border: 'border-amber-200 dark:border-amber-800',
|
||||
label: 'Pádel',
|
||||
},
|
||||
basketball: {
|
||||
icon: BasketballIcon,
|
||||
color: 'text-orange-700 dark:text-orange-400',
|
||||
bg: 'bg-orange-50 dark:bg-orange-950/40',
|
||||
border: 'border-orange-200 dark:border-orange-800',
|
||||
label: 'Básquet',
|
||||
},
|
||||
volleyball: {
|
||||
icon: VolleyballIcon,
|
||||
color: 'text-sky-700 dark:text-sky-400',
|
||||
bg: 'bg-sky-50 dark:bg-sky-950/40',
|
||||
border: 'border-sky-200 dark:border-sky-800',
|
||||
label: 'Vóley',
|
||||
},
|
||||
gym: {
|
||||
icon: BarbellIcon,
|
||||
color: 'text-slate-700 dark:text-slate-400',
|
||||
bg: 'bg-slate-50 dark:bg-slate-950/40',
|
||||
border: 'border-slate-200 dark:border-slate-800',
|
||||
label: 'Gimnasio',
|
||||
},
|
||||
pickleball: {
|
||||
icon: RacquetIcon,
|
||||
color: 'text-lime-700 dark:text-lime-400',
|
||||
bg: 'bg-lime-50 dark:bg-lime-950/40',
|
||||
border: 'border-lime-200 dark:border-lime-800',
|
||||
label: 'Pickleball',
|
||||
},
|
||||
pingpong: {
|
||||
icon: PingPongIcon,
|
||||
color: 'text-blue-700 dark:text-blue-400',
|
||||
bg: 'bg-blue-50 dark:bg-blue-950/40',
|
||||
border: 'border-blue-200 dark:border-blue-800',
|
||||
label: 'Ping Pong',
|
||||
},
|
||||
default: {
|
||||
icon: PersonSimpleRunIcon,
|
||||
color: 'text-violet-700 dark:text-violet-400',
|
||||
bg: 'bg-violet-50 dark:bg-violet-950/40',
|
||||
border: 'border-violet-200 dark:border-violet-800',
|
||||
label: 'Deporte',
|
||||
},
|
||||
};
|
||||
|
||||
function getSportConfig(slug: string) {
|
||||
const key = Object.keys(sportConfig).find((k) => slug.toLowerCase().includes(k));
|
||||
return sportConfig[key || 'default'];
|
||||
}
|
||||
|
||||
const DAYS_STEP = 5;
|
||||
|
||||
@@ -138,6 +207,11 @@ export function PublicBookingPage({ complexSlug }: PublicBookingPageProps) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (availability.sports.length === 1 && !selectedSportId) {
|
||||
setSelectedSportId(availability.sports[0].id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedSportId && !availability.sports.some((sport) => sport.id === selectedSportId)) {
|
||||
setSelectedSportId(undefined);
|
||||
}
|
||||
@@ -419,28 +493,36 @@ export function PublicBookingPage({ complexSlug }: PublicBookingPageProps) {
|
||||
|
||||
{availabilityQuery.data?.sportSelectionRequired && (
|
||||
<div className="mb-4">
|
||||
<Field>
|
||||
<FieldLabel>Deporte</FieldLabel>
|
||||
<Select
|
||||
value={selectedSportId ?? ''}
|
||||
onValueChange={(value) => {
|
||||
setSelectedSportId(value);
|
||||
setSelectedSlot(null);
|
||||
setAutoAdjustedDateNotice(null);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="h-10 w-full">
|
||||
<SelectValue placeholder="Selecciona un deporte" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{availabilityQuery.data.sports.map((sport) => (
|
||||
<SelectItem key={sport.id} value={sport.id}>
|
||||
{sport.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</Field>
|
||||
<h2 className="text-sm font-medium mb-3">Elegí tu deporte</h2>
|
||||
<div className="grid grid-cols-2 gap-2 sm:grid-cols-3">
|
||||
{availabilityQuery.data?.sports.map((sport) => {
|
||||
const config = getSportConfig(sport.slug);
|
||||
const Icon = config.icon;
|
||||
const isSelected = selectedSportId === sport.id;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={sport.id}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setSelectedSportId(sport.id);
|
||||
setSelectedSlot(null);
|
||||
setAutoAdjustedDateNotice(null);
|
||||
}}
|
||||
className={`relative flex flex-col items-center gap-2 rounded-xl border-2 p-4 transition-all hover:scale-[1.02] ${
|
||||
isSelected
|
||||
? `${config.border} ${config.bg} ring-2 ring-offset-2 ring-primary/20`
|
||||
: 'border-border bg-card hover:border-muted-foreground/30'
|
||||
}`}
|
||||
>
|
||||
<Icon className={`size-8 ${isSelected ? config.color : 'text-muted-foreground'}`} />
|
||||
<span className={`text-sm font-medium ${isSelected ? 'text-foreground' : 'text-muted-foreground'}`}>
|
||||
{config.label}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user