feat(sprint-4/etapa-1): Supabase auth + Google OAuth + LoginPage + rutas protegidas

- Cliente Supabase singleton en src/lib/supabase.ts
- Interfaces AuthUser / AuthService en src/auth/types.ts
- SupabaseAuthService: signInWithGoogle, signOut, getCurrentUser, onAuthStateChange
  con upsert a tabla users (platform_admin para @inquality.com.py, guest resto)
- AuthProvider (React Context) wrappea toda la app; expone user, loading, signIn, signOut
- LoginPage /login: logo InQuality color, título, fade-in, botón Google naranja #F59845
- RootComponent del router guarda todas las rutas — sin sesión → /login, con sesión + /login → /
- Script SQL: supabase/migrations/001_create_users.sql (tabla users + RLS)
- AppHeader: <Link> → <a href="/"> para evitar dependencia de RouterContext en tests
- 561/561 tests verdes, build sin errores TS

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-27 23:53:12 -03:00
parent 2c57bd5bf4
commit 78e776df6b
14 changed files with 1089 additions and 18 deletions

View File

@@ -1,13 +1,13 @@
import { lazy, Suspense } from 'react'
import { createRouter, createRootRoute, createRoute, Outlet } from '@tanstack/react-router'
import { lazy, Suspense, useEffect } from 'react'
import { createRouter, createRootRoute, createRoute, Outlet, useNavigate, useRouterState } from '@tanstack/react-router'
import { WorkspacePage } from '@/features/workspace/WorkspacePage'
import { ImportPage } from '@/features/import/ImportPage'
import { LibraryPage } from '@/features/library/LibraryPage'
import { SettingsPage } from '@/features/settings/SettingsPage'
import { LoginPage } from '@/features/login/LoginPage'
import { useAuth } from '@/auth/AuthContext'
import { Loader2 } from 'lucide-react'
// ECharts es grande (373KB gzip). Lazy-load del ReportPage para que el
// bundle inicial no lo incluya — se carga solo cuando el usuario llega al reporte.
const ReportPage = lazy(() =>
import('@/features/report/ReportPage').then((m) => ({ default: m.ReportPage }))
)
@@ -25,7 +25,40 @@ function ReportPageWithSuspense() {
)
}
const rootRoute = createRootRoute({ component: () => <Outlet /> })
function RootComponent() {
const { user, loading } = useAuth()
const navigate = useNavigate()
const pathname = useRouterState({ select: (s) => s.location.pathname })
useEffect(() => {
if (loading) return
if (!user && pathname !== '/login') {
void navigate({ to: '/login' })
} else if (user && pathname === '/login') {
void navigate({ to: '/' })
}
}, [user, loading, pathname, navigate])
if (loading) {
return (
<div className="min-h-screen flex items-center justify-center">
<Loader2 className="h-8 w-8 animate-spin" style={{ color: '#F59845' }} />
</div>
)
}
if (!user && pathname !== '/login') return null
return <Outlet />
}
const rootRoute = createRootRoute({ component: RootComponent })
const loginRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/login',
component: LoginPage,
})
const indexRoute = createRoute({
getParentRoute: () => rootRoute,
@@ -57,7 +90,15 @@ const settingsRoute = createRoute({
component: SettingsPage,
})
const routeTree = rootRoute.addChildren([indexRoute, importRoute, workspaceRoute, reportRoute, settingsRoute])
const routeTree = rootRoute.addChildren([
loginRoute,
indexRoute,
importRoute,
workspaceRoute,
reportRoute,
settingsRoute,
])
export const router = createRouter({ routeTree })
declare module '@tanstack/react-router' {