From c63b5a0a0748ff4c95c3e7cc536dab29ab3cf8be Mon Sep 17 00:00:00 2001 From: Jose Selesan Date: Fri, 24 Apr 2026 14:14:09 -0300 Subject: [PATCH] feat: enhance sport selection UI in public booking page Co-authored-by: Copilot --- apps/frontend/package.json | 2 + apps/frontend/src/assets/icons/football.tsx | 32 ++++ .../public-booking/public-booking-page.tsx | 140 ++++++++++++++---- bun.lock | 8 + 4 files changed, 153 insertions(+), 29 deletions(-) create mode 100644 apps/frontend/src/assets/icons/football.tsx diff --git a/apps/frontend/package.json b/apps/frontend/package.json index a6cbbbe..712c92a 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -15,10 +15,12 @@ "@better-auth/infra": "^0.2.4", "@fontsource-variable/geist": "^5.2.8", "@hookform/resolvers": "^5.2.2", + "@phosphor-icons/react": "^2.1.10", "@radix-ui/react-label": "^2.1.8", "@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-slot": "^1.2.4", "@repo/api-contract": "workspace:*", + "@tabler/icons-react": "^3.41.1", "@tanstack/react-query": "^5.96.1", "@tanstack/react-query-devtools": "^5.96.1", "@tanstack/react-router": "^1.168.10", diff --git a/apps/frontend/src/assets/icons/football.tsx b/apps/frontend/src/assets/icons/football.tsx new file mode 100644 index 0000000..f171b39 --- /dev/null +++ b/apps/frontend/src/assets/icons/football.tsx @@ -0,0 +1,32 @@ +import { type LucideProps } from 'lucide-react'; +import { forwardRef } from 'react'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const SoccerBallIcon = forwardRef(({ className, ...props }, ref) => ( + + + + + + + + + + + +)); + +export { SoccerBallIcon as FootballIcon }; \ No newline at end of file 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 5d24b81..1b63865 100644 --- a/apps/frontend/src/features/public-booking/public-booking-page.tsx +++ b/apps/frontend/src/features/public-booking/public-booking-page.tsx @@ -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 && (
- - Deporte - - +

Elegí tu deporte

+
+ {availabilityQuery.data?.sports.map((sport) => { + const config = getSportConfig(sport.slug); + const Icon = config.icon; + const isSelected = selectedSportId === sport.id; + + return ( + + ); + })} +
)} diff --git a/bun.lock b/bun.lock index 1fee589..5fa687d 100644 --- a/bun.lock +++ b/bun.lock @@ -47,10 +47,12 @@ "@better-auth/infra": "^0.2.4", "@fontsource-variable/geist": "^5.2.8", "@hookform/resolvers": "^5.2.2", + "@phosphor-icons/react": "^2.1.10", "@radix-ui/react-label": "^2.1.8", "@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-slot": "^1.2.4", "@repo/api-contract": "workspace:*", + "@tabler/icons-react": "^3.41.1", "@tanstack/react-query": "^5.96.1", "@tanstack/react-query-devtools": "^5.96.1", "@tanstack/react-router": "^1.168.10", @@ -345,6 +347,8 @@ "@oxc-project/types": ["@oxc-project/types@0.122.0", "", {}, "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA=="], + "@phosphor-icons/react": ["@phosphor-icons/react@2.1.10", "", { "peerDependencies": { "react": ">= 16.8", "react-dom": ">= 16.8" } }, "sha512-vt8Tvq8GLjheAZZYa+YG/pW7HDbov8El/MANW8pOAz4eGxrwhnbfrQZq0Cp4q8zBEu8NIhHdnr+r8thnfRSNYA=="], + "@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="], "@prisma/adapter-pg": ["@prisma/adapter-pg@7.6.0", "", { "dependencies": { "@prisma/driver-adapter-utils": "7.6.0", "@types/pg": "^8.16.0", "pg": "^8.16.3", "postgres-array": "3.0.4" } }, "sha512-BjHNmJqqa42NqJSDPnXUfwUofWo8LJY7Ui2gqxN4DmAOb+H/gGKv+hln2Xq/1kSJXPW5AXMXuNiPDMpywvyIOw=="], @@ -559,6 +563,10 @@ "@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="], + "@tabler/icons": ["@tabler/icons@3.41.1", "", {}, "sha512-OaRnVbRmH2nHtFeg+RmMJ/7m2oBIF9XCJAUD5gQnMrpK9f05ydj8MZrAf3NZQqOXyxGN1UBL0D5IKLLEUfr74Q=="], + + "@tabler/icons-react": ["@tabler/icons-react@3.41.1", "", { "dependencies": { "@tabler/icons": "3.41.1" }, "peerDependencies": { "react": ">= 16" } }, "sha512-kUgweE+DJtAlMZVIns1FTDdcbpRVnkK7ZpUOXmoxy3JAF0rSHj0TcP4VHF14+gMJGnF+psH2Zt26BLT6owetBA=="], + "@tailwindcss/node": ["@tailwindcss/node@4.2.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.2" } }, "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA=="], "@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.2", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.2", "@tailwindcss/oxide-darwin-arm64": "4.2.2", "@tailwindcss/oxide-darwin-x64": "4.2.2", "@tailwindcss/oxide-freebsd-x64": "4.2.2", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", "@tailwindcss/oxide-linux-x64-musl": "4.2.2", "@tailwindcss/oxide-wasm32-wasi": "4.2.2", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" } }, "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg=="],