From 91125e82f566c69f0a8cd5cabd97db4186d0313a Mon Sep 17 00:00:00 2001 From: Jose Selesan Date: Tue, 19 May 2026 16:43:39 -0300 Subject: [PATCH] Improve sport selector UI with visual button components Replace dropdown select with interactive pill buttons for sport selection - Added SportSelector component with icon-based visual buttons (desktop) - Added MobileSportSelector component for mobile-optimized layout - Added getSportIcon() function to map sport names to appropriate icons - Improved visual feedback with emerald highlight on selection - Better hover states and transitions for improved UX - Works on both desktop and mobile views Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../public-booking/public-booking-page.tsx | 99 ++++++++++++++----- 1 file changed, 75 insertions(+), 24 deletions(-) diff --git a/apps/frontend/src/features/public-booking/public-booking-page.tsx b/apps/frontend/src/features/public-booking/public-booking-page.tsx index eec3fab..e4a5420 100644 --- a/apps/frontend/src/features/public-booking/public-booking-page.tsx +++ b/apps/frontend/src/features/public-booking/public-booking-page.tsx @@ -47,6 +47,7 @@ import { Trophy, Users, Wrench, + Zap, } from 'lucide-react'; import { useEffect, useMemo, useRef, useState } from 'react'; import { useForm } from 'react-hook-form'; @@ -250,6 +251,16 @@ function isSlotSelected( return selectedSlot?.courtId === courtId && selectedSlot.startTime === slot.startTime; } +function getSportIcon(sportName: string) { + const lower = sportName.toLowerCase(); + if (lower.includes('tenis')) return Zap; + if (lower.includes('futbol') || lower.includes('fútbol')) return Dumbbell; + if (lower.includes('paddle') || lower.includes('pádel')) return Trophy; + if (lower.includes('basquet') || lower.includes('básquet')) return Trophy; + if (lower.includes('volei') || lower.includes('vóley')) return Trophy; + return Trophy; +} + function PublicBookingThemeToggle() { const { resolvedTheme, toggleTheme } = useTheme(); const Icon = resolvedTheme === 'dark' ? Sun : Moon; @@ -553,11 +564,9 @@ function PublicBookingDesktop(props: BookingShellProps) {

Seleccioná tu cancha

{sportSelectionRequired && ( - } value={selectedSportId ?? ''} - placeholder="Seleccioná" onValueChange={onSelectSport} options={sports.map((sport) => ({ value: sport.id, label: sport.name }))} /> @@ -693,12 +702,11 @@ function PublicBookingMobile(props: BookingShellProps) {

Seleccioná tu cancha

-
+
{props.sportSelectionRequired && ( - ({ value: sport.id, label: sport.name }))} /> @@ -790,41 +798,84 @@ function StatusPanel({ children }: { children?: React.ReactNode }) { ); } -function DarkSelect({ +function SportSelector({ label, - icon, value, - placeholder, options, onValueChange, }: { label: string; - icon: React.ReactNode; value: string; - placeholder: string; options: { value: string; label: string }[]; onValueChange: (value: string) => void; }) { return ( {label} - +
+ {options.map((option) => { + const Icon = getSportIcon(option.label); + const isSelected = value === option.value; + return ( + + ); + })} +
); } +function MobileSportSelector({ + label, + value, + options, + onValueChange, +}: { + label: string; + value: string; + options: { value: string; label: string }[]; + onValueChange: (value: string) => void; +}) { + return ( +
+ {label} +
+ {options.map((option) => { + const Icon = getSportIcon(option.label); + const isSelected = value === option.value; + return ( + + ); + })} +
+
+ ); +} + function MobileSelect({ label, value,