feat: enhance public booking page with improved layout and date formatting
- Added new date formatting function for better user experience. - Updated layout of the public booking page for improved responsiveness and aesthetics. - Integrated sport selection with visual icons for better clarity. - Enhanced error handling and loading states for availability queries. feat: revamp select complex page for better user interaction - Redesigned the select complex page layout for a more modern look. - Added contextual information about complex selection. - Improved button interactions with loading indicators and icons. style: update CSS variables for a cohesive sports-club theme - Adjusted color variables to reflect a sports-club identity. - Enhanced background styles for both light and dark modes. - Improved overall styling consistency across components.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { FootballIcon } from '@/assets/icons/football';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
DropdownMenu,
|
||||
@@ -66,59 +67,194 @@ export function RootLayout() {
|
||||
await navigate({ to: '/login' });
|
||||
};
|
||||
|
||||
type NavigationItem = {
|
||||
label: string;
|
||||
to: '/' | '/profile' | '/complex/$slug/edit';
|
||||
show: boolean;
|
||||
params?: { slug: string };
|
||||
};
|
||||
|
||||
const navigationItems: NavigationItem[] = [
|
||||
{
|
||||
label: 'Inicio',
|
||||
to: '/',
|
||||
show: true,
|
||||
},
|
||||
{
|
||||
label: 'Configuración',
|
||||
to: '/complex/$slug/edit',
|
||||
params: currentComplexSlug ? { slug: currentComplexSlug } : undefined,
|
||||
show: isAuthenticated && Boolean(currentComplexSlug),
|
||||
},
|
||||
{
|
||||
label: 'Perfil',
|
||||
to: '/profile',
|
||||
show: isAuthenticated,
|
||||
},
|
||||
] as const;
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col">
|
||||
<header className="mx-auto flex w-full max-w-6xl items-center justify-between px-6 py-4">
|
||||
<div className="flex items-center gap-6">
|
||||
<h1 className="text-sm font-semibold">{currentComplexName}</h1>
|
||||
<nav className="hidden items-center gap-3 text-sm md:flex">
|
||||
<Link
|
||||
to="/"
|
||||
className="text-muted-foreground"
|
||||
activeProps={{ className: 'text-foreground font-medium' }}
|
||||
>
|
||||
Home
|
||||
<header className="sticky top-0 z-40 border-b border-border/70 bg-background/80 backdrop-blur-xl">
|
||||
<div className="mx-auto flex w-full max-w-7xl items-center justify-between gap-4 px-4 py-3 sm:px-6">
|
||||
<div className="flex min-w-0 items-center gap-4">
|
||||
<Link to="/" className="flex min-w-0 items-center gap-3">
|
||||
<span className="relative flex size-10 items-center justify-center rounded-2xl bg-primary text-primary-foreground shadow-sm ring-1 ring-primary/20">
|
||||
<FootballIcon className="size-5" />
|
||||
<span className="absolute -right-0.5 -bottom-0.5 size-2 rounded-full border border-background bg-accent" />
|
||||
</span>
|
||||
<span className="min-w-0">
|
||||
<span className="block text-[10px] font-semibold uppercase tracking-[0.32em] text-muted-foreground">
|
||||
Playzer
|
||||
</span>
|
||||
<span className="block truncate text-sm font-semibold text-foreground">
|
||||
{currentComplexName}
|
||||
</span>
|
||||
</span>
|
||||
</Link>
|
||||
{isAuthenticated && currentComplexSlug && (
|
||||
<Link
|
||||
to="/complex/$slug/edit"
|
||||
params={{ slug: currentComplexSlug }}
|
||||
className="text-muted-foreground"
|
||||
activeProps={{ className: 'text-foreground font-medium' }}
|
||||
>
|
||||
Configuración
|
||||
</Link>
|
||||
|
||||
<nav className="hidden items-center gap-1 lg:flex">
|
||||
{navigationItems
|
||||
.filter((item) => item.show)
|
||||
.map((item) =>
|
||||
item.params ? (
|
||||
<Link
|
||||
key={item.label}
|
||||
to={item.to}
|
||||
params={item.params}
|
||||
className="rounded-full px-3 py-1.5 text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
|
||||
activeProps={{ className: 'bg-muted text-foreground font-medium' }}
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
) : (
|
||||
<Link
|
||||
key={item.label}
|
||||
to={item.to}
|
||||
className="rounded-full px-3 py-1.5 text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
|
||||
activeProps={{ className: 'bg-muted text-foreground font-medium' }}
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
)
|
||||
)}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="hidden items-center gap-2 rounded-full border border-border/70 bg-card px-3 py-1.5 text-xs text-muted-foreground shadow-sm md:flex">
|
||||
<span className="size-2 rounded-full bg-emerald-500" />
|
||||
{isAuthenticated ? 'Sesión activa' : 'Modo visitante'}
|
||||
</div>
|
||||
|
||||
<ThemeSwitcher />
|
||||
|
||||
{!isAuthenticated && (
|
||||
<Button asChild size="sm" variant="outline" className="hidden md:inline-flex">
|
||||
<Link to="/login">Ingresar</Link>
|
||||
</Button>
|
||||
)}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<ThemeSwitcher />
|
||||
{isAuthenticated && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center gap-2 rounded-full border border-border/70 bg-card px-2 py-1.5 transition-colors hover:bg-muted"
|
||||
>
|
||||
<UserAvatar
|
||||
src={avatarUrl}
|
||||
alt={displayName}
|
||||
fallbackText={displayName}
|
||||
size="sm"
|
||||
/>
|
||||
<span className="hidden max-w-32 truncate text-sm text-foreground sm:block">
|
||||
{displayName}
|
||||
</span>
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-56">
|
||||
<DropdownMenuLabel>Mi cuenta</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
{myComplexesQuery.data && myComplexesQuery.data.length > 1 && (
|
||||
<>
|
||||
<DropdownMenuLabel className="text-xs font-normal text-muted-foreground">
|
||||
Cambiar complejo
|
||||
</DropdownMenuLabel>
|
||||
{myComplexesQuery.data.map((complex) => (
|
||||
<DropdownMenuItem
|
||||
key={complex.id}
|
||||
onSelect={async () => {
|
||||
await apiClient.complexes.select({ complexId: complex.id });
|
||||
setCurrentComplex(complex.complexSlug);
|
||||
}}
|
||||
>
|
||||
{complex.complexSlug === complexSlug && <span className="mr-2">✓</span>}
|
||||
{complex.complexName}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
<DropdownMenuSeparator />
|
||||
</>
|
||||
)}
|
||||
<DropdownMenuItem
|
||||
onSelect={() => {
|
||||
void navigate({ to: '/profile' });
|
||||
}}
|
||||
>
|
||||
<UserRound className="size-4" />
|
||||
Perfil
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
variant="destructive"
|
||||
onSelect={() => {
|
||||
void handleSignOut();
|
||||
}}
|
||||
>
|
||||
<LogOut className="size-4" />
|
||||
Cerrar sesión
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
|
||||
{!isAuthenticated && (
|
||||
<Button asChild size="sm" variant="outline" className="hidden md:inline-flex">
|
||||
<Link to="/login">Login</Link>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{isAuthenticated && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
className="hidden items-center gap-2 rounded-md px-2 py-1.5 transition-colors hover:bg-muted md:inline-flex"
|
||||
variant="outline"
|
||||
size="icon-sm"
|
||||
className="lg:hidden"
|
||||
aria-label="Abrir menú"
|
||||
>
|
||||
<UserAvatar
|
||||
src={avatarUrl}
|
||||
alt={displayName}
|
||||
fallbackText={displayName}
|
||||
size="sm"
|
||||
/>
|
||||
<span className="max-w-32 truncate text-sm text-foreground">{displayName}</span>
|
||||
</button>
|
||||
<Menu className="size-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-52">
|
||||
<DropdownMenuLabel>Mi cuenta</DropdownMenuLabel>
|
||||
<DropdownMenuContent align="end" className="w-64 lg:hidden">
|
||||
<DropdownMenuLabel>{currentComplexName}</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
{navigationItems
|
||||
.filter((item) => item.show)
|
||||
.map((item) =>
|
||||
item.params ? (
|
||||
<DropdownMenuItem
|
||||
key={item.label}
|
||||
onSelect={() => {
|
||||
void navigate({ to: item.to, params: item.params });
|
||||
}}
|
||||
>
|
||||
{item.label}
|
||||
</DropdownMenuItem>
|
||||
) : (
|
||||
<DropdownMenuItem
|
||||
key={item.label}
|
||||
onSelect={() => {
|
||||
void navigate({ to: item.to });
|
||||
}}
|
||||
>
|
||||
{item.label}
|
||||
</DropdownMenuItem>
|
||||
)
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
{myComplexesQuery.data && myComplexesQuery.data.length > 1 && (
|
||||
<>
|
||||
@@ -140,103 +276,41 @@ export function RootLayout() {
|
||||
<DropdownMenuSeparator />
|
||||
</>
|
||||
)}
|
||||
<DropdownMenuItem
|
||||
onSelect={() => {
|
||||
void navigate({ to: '/profile' });
|
||||
}}
|
||||
>
|
||||
<UserRound className="size-4" />
|
||||
Profile
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
variant="destructive"
|
||||
onSelect={() => {
|
||||
void handleSignOut();
|
||||
}}
|
||||
>
|
||||
<LogOut className="size-4" />
|
||||
Sign out
|
||||
</DropdownMenuItem>
|
||||
{isAuthenticated ? (
|
||||
<>
|
||||
<DropdownMenuItem
|
||||
onSelect={() => {
|
||||
void navigate({ to: '/profile' });
|
||||
}}
|
||||
>
|
||||
<UserRound className="size-4" />
|
||||
Perfil
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
variant="destructive"
|
||||
onSelect={() => {
|
||||
void handleSignOut();
|
||||
}}
|
||||
>
|
||||
<LogOut className="size-4" />
|
||||
Cerrar sesión
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
) : (
|
||||
<DropdownMenuItem
|
||||
onSelect={() => {
|
||||
void navigate({ to: '/login' });
|
||||
}}
|
||||
>
|
||||
Ingresar
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="icon-sm"
|
||||
className="md:hidden"
|
||||
aria-label="Abrir menú"
|
||||
>
|
||||
<Menu className="size-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-56 md:hidden">
|
||||
<DropdownMenuLabel>Navegación</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
onSelect={() => {
|
||||
void navigate({ to: '/' });
|
||||
}}
|
||||
>
|
||||
Home
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onSelect={() => {
|
||||
void navigate({ to: '/about' });
|
||||
}}
|
||||
>
|
||||
About
|
||||
</DropdownMenuItem>
|
||||
{isAuthenticated && currentComplexSlug && (
|
||||
<DropdownMenuItem
|
||||
onSelect={() => {
|
||||
void navigate({
|
||||
to: '/complex/$slug/edit',
|
||||
params: { slug: currentComplexSlug },
|
||||
});
|
||||
}}
|
||||
>
|
||||
Configuración
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
{isAuthenticated ? (
|
||||
<>
|
||||
<DropdownMenuItem
|
||||
onSelect={() => {
|
||||
void navigate({ to: '/profile' });
|
||||
}}
|
||||
>
|
||||
<UserRound className="size-4" />
|
||||
Profile
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
variant="destructive"
|
||||
onSelect={() => {
|
||||
void handleSignOut();
|
||||
}}
|
||||
>
|
||||
<LogOut className="size-4" />
|
||||
Sign out
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
) : (
|
||||
<DropdownMenuItem
|
||||
onSelect={() => {
|
||||
void navigate({ to: '/login' });
|
||||
}}
|
||||
>
|
||||
Login
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="flex-1">
|
||||
<Outlet />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user