Initial commit

This commit is contained in:
Jose Selesan
2026-04-08 22:53:11 -03:00
commit 9ae270609d
179 changed files with 28096 additions and 0 deletions

View File

@@ -0,0 +1,215 @@
import { Link, Outlet, useNavigate } from '@tanstack/react-router'
import { useEffect, useState } from 'react'
import { useQuery } from '@tanstack/react-query'
import { LogOut, Menu, UserRound } from 'lucide-react'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { Button } from '@/components/ui/button'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { useAuth } from '@/lib/auth'
import {
getCurrentComplexSlug,
setCurrentComplexSlug as persistCurrentComplexSlug,
} from '@/lib/current-complex'
import { apiClient } from '@/lib/api-client'
import { ThemeSwitcher } from './theme-switcher'
export function RootLayout() {
const navigate = useNavigate()
const { isAuthenticated, displayName, initials, avatarUrl, signOut } = useAuth()
const [currentComplexSlug, setCurrentComplexSlug] = useState<string | null>(null)
const myComplexesQuery = useQuery({
queryKey: ['my-complexes'],
enabled: isAuthenticated,
queryFn: () => apiClient.complexes.listMine(),
})
useEffect(() => {
setCurrentComplexSlug(getCurrentComplexSlug())
}, [])
useEffect(() => {
if (currentComplexSlug) return
const fallbackSlug = myComplexesQuery.data?.[0]?.complexSlug
if (fallbackSlug) {
setCurrentComplexSlug(fallbackSlug)
persistCurrentComplexSlug(fallbackSlug)
}
}, [currentComplexSlug, myComplexesQuery.data])
const handleSignOut = async () => {
await signOut()
await navigate({ to: '/login' })
}
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">TanStack Router</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
</Link>
<Link
to="/about"
className="text-muted-foreground"
activeProps={{ className: 'text-foreground font-medium' }}
>
About
</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>
</div>
<div className="flex items-center gap-2">
<ThemeSwitcher />
{!isAuthenticated && (
<Button asChild size="sm" variant="outline" className="hidden md:inline-flex">
<Link to="/login">Login</Link>
</Button>
)}
{isAuthenticated && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button
type="button"
className="hidden items-center gap-2 rounded-md px-2 py-1.5 transition-colors hover:bg-muted md:inline-flex"
>
<Avatar size="sm">
<AvatarImage src={avatarUrl ?? undefined} alt={displayName} />
<AvatarFallback>{initials}</AvatarFallback>
</Avatar>
<span className="max-w-32 truncate text-sm text-foreground">
{displayName}
</span>
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-52">
<DropdownMenuLabel>Mi cuenta</DropdownMenuLabel>
<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>
</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>
</header>
<div className="flex-1">
<Outlet />
</div>
</div>
)
}