feat(public-booking): enhance booking confirmation with price and address details

This commit is contained in:
Jose Selesan
2026-06-02 16:55:47 -03:00
parent 9045a8c017
commit 07496e6673
4 changed files with 162 additions and 54 deletions

View File

@@ -1,7 +1,18 @@
import PlayzerIcon from '@/assets/playzer-favicon-512-transparent.png';
import { Button } from '@/components/ui/button';
import { ApiClientError, apiClient } from '@/lib/api-client';
import { useQuery } from '@tanstack/react-query';
import { useNavigate } from '@tanstack/react-router';
import {
AlertCircle,
ArrowRight,
CalendarDays,
CheckCircle2,
Clock3,
LoaderCircle,
Receipt,
Trophy,
} from 'lucide-react';
import { ShareWhatsappButton } from './components/share-whatsapp-button';
type PublicBookingConfirmationPageProps = {
@@ -21,12 +32,25 @@ function formatDateLabel(isoDate: string) {
const [year, month, day] = isoDate.split('-').map(Number);
const date = new Date(year, (month ?? 1) - 1, day ?? 1);
return new Intl.DateTimeFormat('es-AR', {
const label = new Intl.DateTimeFormat('es-AR', {
weekday: 'long',
day: '2-digit',
month: 'long',
year: 'numeric',
}).format(date);
return label.charAt(0).toUpperCase() + label.slice(1);
}
function formatBookingPrice(price: number): string {
if (price === 0) {
return 'Sin cargo';
}
return new Intl.NumberFormat('es-AR', {
style: 'currency',
currency: 'ARS',
maximumFractionDigits: Number.isInteger(price) ? 0 : 2,
}).format(price);
}
export function PublicBookingConfirmationPage({
@@ -40,62 +64,98 @@ export function PublicBookingConfirmationPage({
});
return (
<main className="min-h-screen bg-linear-to-b from-background via-background to-primary/5">
<div className="mx-auto flex min-h-screen w-full max-w-3xl items-center px-3 py-8 sm:px-6">
<section className="w-full rounded-3xl border bg-card p-5 shadow-lg sm:p-8">
<main className="min-h-screen bg-[#edf7f4] text-[#111827]">
<div className="mx-auto flex min-h-screen w-full max-w-3xl items-center px-4 py-8 sm:px-6">
<section className="w-full rounded-[28px] border border-emerald-950/10 bg-white p-5 shadow-[0_24px_70px_rgba(15,23,42,0.12)] sm:p-8">
{confirmationQuery.isLoading && (
<p className="text-sm text-muted-foreground">Cargando confirmacion...</p>
<div className="flex items-center gap-3 text-sm text-slate-500">
<LoaderCircle className="size-5 animate-spin text-emerald-600" />
Cargando confirmación...
</div>
)}
{confirmationQuery.isError && (
<p className="text-sm text-destructive">
{extractMessage(
confirmationQuery.error,
'No pudimos cargar la confirmacion de la reserva.'
)}
</p>
<div className="flex gap-3 rounded-2xl border border-red-200 bg-red-50 p-4 text-sm text-red-700">
<AlertCircle className="mt-0.5 size-5 shrink-0" />
<p>
{extractMessage(
confirmationQuery.error,
'No pudimos cargar la confirmación de la reserva.'
)}
</p>
</div>
)}
{confirmationQuery.data && (
<div className="space-y-6">
<div>
<p className="text-xs font-medium tracking-[0.2em] text-muted-foreground uppercase">
Reserva confirmada
</p>
<h1 className="mt-2 text-2xl font-semibold sm:text-3xl">
{confirmationQuery.data.complexName}
</h1>
<div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
<div>
<div className="inline-flex items-center gap-2 rounded-full bg-emerald-50 px-3 py-1 text-xs font-semibold tracking-[0.14em] text-emerald-700 uppercase">
<CheckCircle2 className="size-4" />
Reserva confirmada
</div>
<h1 className="mt-3 text-2xl font-bold tracking-tight sm:text-3xl">
{confirmationQuery.data.complexName}
</h1>
</div>
<div className="flex items-center gap-2 text-slate-600">
<img src={PlayzerIcon} alt="Playzer" className="size-8" />
<span className="text-lg font-bold tracking-tight">Playzer</span>
</div>
</div>
<div className="rounded-2xl border border-primary/30 bg-primary/5 p-4 text-center sm:p-5">
<p className="text-xs text-muted-foreground">Codigo de reserva</p>
<p className="mt-1 text-3xl font-bold tracking-[0.25em] text-primary sm:text-4xl">
{confirmationQuery.data.bookingCode}
</p>
<div className="rounded-[24px] border border-emerald-500/30 bg-emerald-50/80 p-5 sm:p-6">
<div className="flex flex-col gap-5 sm:flex-row sm:items-end sm:justify-between">
<div>
<p className="flex items-center gap-2 text-sm font-semibold text-emerald-700">
<CalendarDays className="size-4" />
Tu turno
</p>
<p className="mt-3 text-2xl font-black leading-tight tracking-tight text-slate-950 sm:text-4xl">
{formatDateLabel(confirmationQuery.data.date)}
</p>
<p className="mt-2 flex items-center gap-2 text-3xl font-black leading-none text-emerald-700 sm:text-5xl">
<Clock3 className="size-7 sm:size-9" />
{confirmationQuery.data.startTime} - {confirmationQuery.data.endTime}
</p>
</div>
<div className="rounded-2xl border border-emerald-600/20 bg-white/80 px-4 py-3 sm:min-w-40 sm:text-right">
<p className="text-xs font-medium text-slate-500">Precio del turno</p>
<p className="mt-1 text-2xl font-bold text-slate-950">
{formatBookingPrice(confirmationQuery.data.price)}
</p>
</div>
</div>
</div>
<div className="rounded-2xl border bg-background p-4 sm:p-5">
<p className="text-sm text-muted-foreground">Fecha</p>
<p className="mt-1 text-base font-medium capitalize sm:text-lg">
{formatDateLabel(confirmationQuery.data.date)}
</p>
<p className="mt-4 text-sm text-muted-foreground">Horario</p>
<p className="mt-1 text-base font-medium sm:text-lg">
{confirmationQuery.data.startTime} - {confirmationQuery.data.endTime}
</p>
<p className="mt-4 text-sm text-muted-foreground">Cancha</p>
<p className="mt-1 text-base font-medium sm:text-lg">
{confirmationQuery.data.courtName} · {confirmationQuery.data.sport.name}
</p>
<div className="overflow-hidden rounded-[22px] border border-slate-200 bg-slate-50/70">
<div className="grid gap-px bg-slate-200 sm:grid-cols-2">
<div className="bg-white p-4">
<p className="flex items-center gap-2 text-sm font-medium text-slate-500">
<Trophy className="size-4 text-emerald-600" />
Cancha
</p>
<p className="mt-2 text-base font-semibold text-slate-950">
{confirmationQuery.data.courtName} · {confirmationQuery.data.sport.name}
</p>
</div>
<div className="bg-white p-4">
<p className="flex items-center gap-2 text-sm font-medium text-slate-500">
<Receipt className="size-4 text-emerald-600" />
Código de reserva
</p>
<p className="mt-2 font-mono text-lg font-bold tracking-[0.18em] text-slate-950">
{confirmationQuery.data.bookingCode}
</p>
</div>
</div>
</div>
<div className="grid gap-3 sm:grid-cols-2">
<ShareWhatsappButton confirmation={confirmationQuery.data} />
<Button
type="button"
className="h-11 w-full text-sm sm:text-base"
className="h-12 w-full rounded-xl bg-emerald-600 text-sm font-semibold text-white hover:bg-emerald-500 sm:text-base"
onClick={() => {
void navigate({
to: '/$complexSlug/booking',
@@ -104,6 +164,7 @@ export function PublicBookingConfirmationPage({
}}
>
Hacer otra reserva
<ArrowRight className="size-4" />
</Button>
</div>
</div>