Refactor header and layout components, remove About page, and introduce Background component for improved UI structure #1
@@ -17,7 +17,6 @@ function statusFromError(error: AppError): ContentfulStatusCode {
|
||||
case 'unexpected':
|
||||
return 500;
|
||||
default:
|
||||
|
||||
return 500;
|
||||
}
|
||||
}
|
||||
|
||||
15
apps/frontend/src/components/background.tsx
Normal file
15
apps/frontend/src/components/background.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
export function Background() {
|
||||
return (
|
||||
<div className="pointer-events-none fixed inset-0 -z-10 overflow-hidden">
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-[#040b11] to-[#09131c]" />
|
||||
|
||||
<div className="absolute left-0 top-0 h-[420px] w-[420px] -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary/20 blur-3xl" />
|
||||
<div className="absolute right-0 top-24 h-[360px] w-[360px] translate-x-1/3 rounded-full bg-reserved/15 blur-3xl" />
|
||||
<div className="absolute bottom-0 left-1/2 h-[300px] w-[300px] -translate-x-1/2 translate-y-1/3 rounded-full bg-accent/10 blur-3xl" />
|
||||
|
||||
<div className="absolute inset-0 bg-[linear-gradient(to_right,oklch(1_0_0/0.055)_1px,transparent_1px),linear-gradient(to_bottom,oklch(1_0_0/0.055)_1px,transparent_1px)] bg-[size:64px_64px]" />
|
||||
|
||||
<div className="absolute inset-0 bg-[radial-gradient(circle_at_center,transparent_0%,#09131c_78%)]" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import PlayzerIcon from '@/assets/playzer-favicon-512-transparent.png';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { Link, useLocation, useNavigate } from '@tanstack/react-router';
|
||||
import { Link, useNavigate } from '@tanstack/react-router';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
@@ -35,40 +35,6 @@ import { acceptStoredPendingInvite } from '@/lib/invitations';
|
||||
import { useCurrentComplexStore } from '@/lib/stores/current-complex-store';
|
||||
import { useTheme } from '@/lib/theme';
|
||||
|
||||
interface NavItemProps {
|
||||
to: '/' | '/about';
|
||||
label: string;
|
||||
isMobile?: boolean;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
function NavItem({ to, label, isMobile, onClick }: NavItemProps) {
|
||||
const location = useLocation();
|
||||
const currentPath = location.pathname;
|
||||
const isActive = to === '/' ? currentPath === '/' : currentPath.startsWith(to);
|
||||
|
||||
return (
|
||||
<Link to={to} onClick={onClick}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size={isMobile ? 'default' : 'sm'}
|
||||
className={[
|
||||
'rounded-none transition-all duration-200 hover:bg-transparent',
|
||||
'focus-visible:ring-2 focus-visible:ring-primary/50',
|
||||
isMobile && 'w-full justify-start rounded-2xl',
|
||||
isActive
|
||||
? 'text-primary relative after:absolute after:bottom-0 after:left-0 after:right-0 after:h-0.5 after:bg-primary'
|
||||
: 'text-muted-foreground hover:text-foreground relative hover:after:absolute hover:after:bottom-0 hover:after:left-0 hover:after:right-0 hover:after:h-0.5 hover:after:bg-primary/50',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
{label}
|
||||
</Button>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
export function Header() {
|
||||
const [open, setOpen] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
@@ -113,11 +79,6 @@ export function Header() {
|
||||
return selected?.complexName ?? complexes[0]?.complexName ?? 'Mi complejo';
|
||||
}, [complexSlug, myComplexesQuery.data]);
|
||||
|
||||
const menuItems = [
|
||||
{ label: 'Inicio', to: '/' },
|
||||
{ label: 'About', to: '/about' },
|
||||
] as const;
|
||||
|
||||
const handleSelectComplex = async (complexId: string, nextSlug: string) => {
|
||||
await apiClient.complexes.select({ complexId });
|
||||
setCurrentComplex(nextSlug);
|
||||
@@ -154,12 +115,6 @@ export function Header() {
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
<nav className="hidden absolute left-1/2 -translate-x-1/2 items-center gap-1 md:flex">
|
||||
{menuItems.map((item) => (
|
||||
<NavItem key={item.label} to={item.to} label={item.label} />
|
||||
))}
|
||||
</nav>
|
||||
|
||||
<div className="flex-1" />
|
||||
|
||||
<div className="hidden items-center gap-2 md:flex">
|
||||
@@ -336,18 +291,6 @@ export function Header() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav className="flex flex-col gap-1">
|
||||
{menuItems.map((item) => (
|
||||
<NavItem
|
||||
key={item.label}
|
||||
to={item.to}
|
||||
label={item.label}
|
||||
isMobile
|
||||
onClick={() => setOpen(false)}
|
||||
/>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
<Button
|
||||
className="rounded-2xl shadow-lg shadow-primary/20"
|
||||
onClick={() => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Background } from '@/components/background';
|
||||
import { useIsMobile } from '@/hooks/use-mobile';
|
||||
import { useLocation } from '@tanstack/react-router';
|
||||
import type { ReactNode } from 'react';
|
||||
@@ -28,19 +29,3 @@ export function Layout({ children }: LayoutProps) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Background() {
|
||||
return (
|
||||
<div className="pointer-events-none fixed inset-0 -z-10 overflow-hidden">
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-[#040b11] to-[#09131c]" />
|
||||
|
||||
<div className="absolute left-0 top-0 h-[420px] w-[420px] -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary/20 blur-3xl" />
|
||||
<div className="absolute right-0 top-24 h-[360px] w-[360px] translate-x-1/3 rounded-full bg-reserved/15 blur-3xl" />
|
||||
<div className="absolute bottom-0 left-1/2 h-[300px] w-[300px] -translate-x-1/2 translate-y-1/3 rounded-full bg-accent/10 blur-3xl" />
|
||||
|
||||
<div className="absolute inset-0 bg-[linear-gradient(to_right,oklch(1_0_0/0.055)_1px,transparent_1px),linear-gradient(to_bottom,oklch(1_0_0/0.055)_1px,transparent_1px)] bg-[size:64px_64px]" />
|
||||
|
||||
<div className="absolute inset-0 bg-[radial-gradient(circle_at_center,transparent_0%,#09131c_78%)]" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
export function AboutPage() {
|
||||
return (
|
||||
<main className="mx-auto flex min-h-[60vh] w-full max-w-6xl items-center justify-center px-3 sm:px-6">
|
||||
<section className="w-full max-w-3xl rounded-xl border bg-card p-6 text-card-foreground shadow-sm">
|
||||
<h2 className="text-2xl font-semibold">About</h2>
|
||||
<p className="mt-2 text-sm text-muted-foreground">
|
||||
Ruta de ejemplo funcionando con TanStack Router.
|
||||
</p>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -209,29 +209,6 @@ function MobileTopBar() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav className="flex flex-col gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="justify-start rounded-lg"
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
void navigate({ to: '/' });
|
||||
}}
|
||||
>
|
||||
Inicio
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="justify-start rounded-lg"
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
void navigate({ to: '/about' });
|
||||
}}
|
||||
>
|
||||
About
|
||||
</Button>
|
||||
</nav>
|
||||
|
||||
<Button
|
||||
className="rounded-lg shadow-lg shadow-primary/20"
|
||||
onClick={() => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import PlayzerIcon from '@/assets/playzer-favicon-512-transparent.png';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Field, FieldError, FieldLabel } from '@/components/ui/field';
|
||||
import { Input } from '@/components/ui/input';
|
||||
@@ -78,7 +79,15 @@ export function LoginPage({ redirectTo = '/' }: LoginPageProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<main className="flex min-h-screen w-full items-center justify-center px-3 sm:px-6">
|
||||
<main className="flex min-h-screen w-full flex-col items-center justify-center gap-6 px-3 sm:px-6">
|
||||
<Link to="/" className="group flex items-center gap-3">
|
||||
<div className="flex size-10 items-center justify-center">
|
||||
<img src={PlayzerIcon} alt="Playzer" className="size-7" />
|
||||
</div>
|
||||
<span className="bg-gradient-to-r from-primary via-primary to-reserved bg-clip-text text-xl font-bold tracking-tight text-transparent">
|
||||
Playzer
|
||||
</span>
|
||||
</Link>
|
||||
<section className="w-full max-w-sm rounded-xl border bg-card p-6 text-card-foreground shadow-sm">
|
||||
<h1 className="mb-2 text-2xl font-semibold">Iniciar sesión</h1>
|
||||
<p className="mb-4 text-sm text-muted-foreground">
|
||||
|
||||
@@ -22,7 +22,6 @@ import { Route as OnboardVerifyRouteImport } from './routes/onboard/verify'
|
||||
import { Route as OnboardCreateComplexRouteImport } from './routes/onboard/create-complex'
|
||||
import { Route as OnboardCompleteRouteImport } from './routes/onboard/complete'
|
||||
import { Route as AppProfileRouteImport } from './routes/_app/profile'
|
||||
import { Route as AppAboutRouteImport } from './routes/_app/about'
|
||||
import { Route as ComplexSlugBookingRouteImport } from './routes/$complexSlug/booking'
|
||||
import { Route as AppAuthenticatedRouteRouteImport } from './routes/_app/_authenticated/route'
|
||||
import { Route as AppAuthenticatedIndexRouteImport } from './routes/_app/_authenticated/index'
|
||||
@@ -94,11 +93,6 @@ const AppProfileRoute = AppProfileRouteImport.update({
|
||||
path: '/profile',
|
||||
getParentRoute: () => AppRouteRoute,
|
||||
} as any)
|
||||
const AppAboutRoute = AppAboutRouteImport.update({
|
||||
id: '/about',
|
||||
path: '/about',
|
||||
getParentRoute: () => AppRouteRoute,
|
||||
} as any)
|
||||
const ComplexSlugBookingRoute = ComplexSlugBookingRouteImport.update({
|
||||
id: '/$complexSlug/booking',
|
||||
path: '/$complexSlug/booking',
|
||||
@@ -140,7 +134,6 @@ export interface FileRoutesByFullPath {
|
||||
'/reset-password': typeof ResetPasswordRoute
|
||||
'/select-complex': typeof SelectComplexRoute
|
||||
'/$complexSlug/booking': typeof ComplexSlugBookingRouteWithChildren
|
||||
'/about': typeof AppAboutRoute
|
||||
'/profile': typeof AppProfileRoute
|
||||
'/onboard/complete': typeof OnboardCompleteRoute
|
||||
'/onboard/create-complex': typeof OnboardCreateComplexRoute
|
||||
@@ -158,7 +151,6 @@ export interface FileRoutesByTo {
|
||||
'/login': typeof LoginRoute
|
||||
'/reset-password': typeof ResetPasswordRoute
|
||||
'/select-complex': typeof SelectComplexRoute
|
||||
'/about': typeof AppAboutRoute
|
||||
'/profile': typeof AppProfileRoute
|
||||
'/onboard/complete': typeof OnboardCompleteRoute
|
||||
'/onboard/create-complex': typeof OnboardCreateComplexRoute
|
||||
@@ -180,7 +172,6 @@ export interface FileRoutesById {
|
||||
'/select-complex': typeof SelectComplexRoute
|
||||
'/_app/_authenticated': typeof AppAuthenticatedRouteRouteWithChildren
|
||||
'/$complexSlug/booking': typeof ComplexSlugBookingRouteWithChildren
|
||||
'/_app/about': typeof AppAboutRoute
|
||||
'/_app/profile': typeof AppProfileRoute
|
||||
'/onboard/complete': typeof OnboardCompleteRoute
|
||||
'/onboard/create-complex': typeof OnboardCreateComplexRoute
|
||||
@@ -203,7 +194,6 @@ export interface FileRouteTypes {
|
||||
| '/reset-password'
|
||||
| '/select-complex'
|
||||
| '/$complexSlug/booking'
|
||||
| '/about'
|
||||
| '/profile'
|
||||
| '/onboard/complete'
|
||||
| '/onboard/create-complex'
|
||||
@@ -221,7 +211,6 @@ export interface FileRouteTypes {
|
||||
| '/login'
|
||||
| '/reset-password'
|
||||
| '/select-complex'
|
||||
| '/about'
|
||||
| '/profile'
|
||||
| '/onboard/complete'
|
||||
| '/onboard/create-complex'
|
||||
@@ -242,7 +231,6 @@ export interface FileRouteTypes {
|
||||
| '/select-complex'
|
||||
| '/_app/_authenticated'
|
||||
| '/$complexSlug/booking'
|
||||
| '/_app/about'
|
||||
| '/_app/profile'
|
||||
| '/onboard/complete'
|
||||
| '/onboard/create-complex'
|
||||
@@ -359,13 +347,6 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof AppProfileRouteImport
|
||||
parentRoute: typeof AppRouteRoute
|
||||
}
|
||||
'/_app/about': {
|
||||
id: '/_app/about'
|
||||
path: '/about'
|
||||
fullPath: '/about'
|
||||
preLoaderRoute: typeof AppAboutRouteImport
|
||||
parentRoute: typeof AppRouteRoute
|
||||
}
|
||||
'/$complexSlug/booking': {
|
||||
id: '/$complexSlug/booking'
|
||||
path: '/$complexSlug/booking'
|
||||
@@ -428,13 +409,11 @@ const AppAuthenticatedRouteRouteWithChildren =
|
||||
|
||||
interface AppRouteRouteChildren {
|
||||
AppAuthenticatedRouteRoute: typeof AppAuthenticatedRouteRouteWithChildren
|
||||
AppAboutRoute: typeof AppAboutRoute
|
||||
AppProfileRoute: typeof AppProfileRoute
|
||||
}
|
||||
|
||||
const AppRouteRouteChildren: AppRouteRouteChildren = {
|
||||
AppAuthenticatedRouteRoute: AppAuthenticatedRouteRouteWithChildren,
|
||||
AppAboutRoute: AppAboutRoute,
|
||||
AppProfileRoute: AppProfileRoute,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Background } from '@/components/background';
|
||||
import { Layout } from '@/components/layout';
|
||||
import { NotFoundPage } from '@/features/not-found/not-found-page';
|
||||
import { ServerErrorPage } from '@/features/server-error/server-error-page';
|
||||
@@ -16,14 +17,27 @@ export const Route = createRootRouteWithContext<RouterContext>()({
|
||||
notFoundComponent: NotFoundPage,
|
||||
});
|
||||
|
||||
const AUTH_PAGE_REGEX =
|
||||
/^\/(login|select-complex|reset-password|onboard|invite|auth-callback)(?:\/|$)/;
|
||||
|
||||
function RootRoute() {
|
||||
const location = useLocation();
|
||||
const isPublicBookingRoute = /^\/[^/]+\/booking(?:\/|$)/.test(location.pathname);
|
||||
const isAuthPage = AUTH_PAGE_REGEX.test(location.pathname);
|
||||
|
||||
if (isPublicBookingRoute) {
|
||||
return <Outlet />;
|
||||
}
|
||||
|
||||
if (isAuthPage) {
|
||||
return (
|
||||
<div className="relative min-h-dvh overflow-x-hidden bg-background text-foreground">
|
||||
<Background />
|
||||
<Outlet />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className="flex flex-col">
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import { AboutPage } from '@/features/about/about-page';
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
|
||||
export const Route = createFileRoute('/_app/about')({
|
||||
component: AboutPage,
|
||||
});
|
||||
Reference in New Issue
Block a user