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

@@ -0,0 +1,57 @@
import { useAuth } from '@/auth/AuthContext'
function GoogleIcon() {
return (
<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
<path d="M17.64 9.2c0-.637-.057-1.251-.164-1.84H9v3.481h4.844c-.209 1.125-.843 2.078-1.796 2.717v2.258h2.908C16.658 14.013 17.64 11.79 17.64 9.2z" fill="white"/>
<path d="M9 18c2.43 0 4.467-.806 5.956-2.184l-2.908-2.258c-.806.54-1.837.86-3.048.86-2.344 0-4.328-1.584-5.036-3.711H.957v2.332C2.438 15.983 5.482 18 9 18z" fill="white"/>
<path d="M3.964 10.707A5.41 5.41 0 0 1 3.682 9c0-.59.102-1.167.282-1.707V4.961H.957C.347 6.175 0 7.55 0 9s.348 2.826.957 4.039l3.007-2.332z" fill="white"/>
<path d="M9 3.58c1.321 0 2.508.454 3.44 1.345l2.582-2.58C13.463.891 11.426 0 9 0 5.482 0 2.438 2.017.957 4.961L3.964 6.293C4.672 4.169 6.656 3.58 9 3.58z" fill="white"/>
</svg>
)
}
export function LoginPage() {
const { signIn, loading } = useAuth()
return (
<div className="min-h-screen bg-white flex items-center justify-center">
<div
className="flex flex-col items-center gap-6 px-8"
style={{ animation: 'loginFadeIn 0.5s ease-out both' }}
>
<style>{`
@keyframes loginFadeIn {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
`}</style>
<img
src="/inquality-logo-color.png"
alt="InQuality"
className="h-12 object-contain"
/>
<div className="text-center">
<h1 className="text-3xl font-bold text-slate-900 tracking-tight">InQ ROI</h1>
<p className="text-slate-500 mt-1 text-sm">Análisis de procesos y ROI de automatización</p>
</div>
<button
onClick={signIn}
disabled={loading}
className="flex items-center gap-3 px-6 py-3 rounded-xl font-semibold text-white text-sm shadow-sm transition-opacity hover:opacity-90 disabled:opacity-60 disabled:cursor-not-allowed"
style={{ backgroundColor: '#F59845' }}
>
<GoogleIcon />
Iniciar sesión con Google
</button>
<p className="text-xs text-slate-400">
Solo para usuarios con cuenta @inquality.com.py
</p>
</div>
</div>
)
}