feat: add Supabase authentication context and routes
- Implemented Supabase authentication context in `supabase.tsx` for managing user login and logout. - Created a new SVG logo file for branding. - Set up main application entry point in `main.tsx` with router integration. - Added web vitals reporting in `reportWebVitals.ts`. - Generated route tree in `routeTree.gen.ts` for application routing. - Established root route in `__root.tsx` with TanStack Router Devtools integration. - Created authenticated route in `_authenticated.tsx` to protect routes. - Developed planes route under authenticated section in `planes.tsx`. - Implemented login route with form handling in `login.tsx`. - Added index route with welcome message in `index.tsx`. - Included basic styles in `styles.css`. - Configured TypeScript settings in `tsconfig.json`. - Set up Vite configuration with React and TanStack Router plugins in `vite.config.ts`.
This commit is contained in:
27
src/routes/__root.tsx
Normal file
27
src/routes/__root.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Outlet, createRootRouteWithContext } from '@tanstack/react-router'
|
||||
import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools'
|
||||
import { TanstackDevtools } from '@tanstack/react-devtools'
|
||||
import type { SupabaseAuthState } from '@/auth/supabase'
|
||||
|
||||
interface AuthContext {
|
||||
auth: SupabaseAuthState
|
||||
}
|
||||
|
||||
export const Route = createRootRouteWithContext<AuthContext>()({
|
||||
component: () => (
|
||||
<>
|
||||
<Outlet />
|
||||
<TanstackDevtools
|
||||
config={{
|
||||
position: 'bottom-left',
|
||||
}}
|
||||
plugins={[
|
||||
{
|
||||
name: 'Tanstack Router',
|
||||
render: <TanStackRouterDevtoolsPanel />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
})
|
||||
9
src/routes/_authenticated.tsx
Normal file
9
src/routes/_authenticated.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/_authenticated')({
|
||||
beforeLoad: ({ context }) => {
|
||||
if (!context.auth.isAuthenticated) {
|
||||
throw redirect({ to: '/login', search: { redirect: location.href } })
|
||||
}
|
||||
},
|
||||
})
|
||||
22
src/routes/_authenticated/planes.tsx
Normal file
22
src/routes/_authenticated/planes.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { useSupabaseAuth } from '@/auth/supabase'
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/_authenticated/planes')({
|
||||
component: RouteComponent,
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
const auth = useSupabaseAuth()
|
||||
console.log(auth.user);
|
||||
|
||||
return <div>
|
||||
<h2>Hello "/_authenticated/planes"!</h2>
|
||||
<button
|
||||
onClick={() => {
|
||||
auth.logout()
|
||||
}}
|
||||
>
|
||||
Logout {auth.isAuthenticated}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
15
src/routes/index.tsx
Normal file
15
src/routes/index.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { createFileRoute, Link } from '@tanstack/react-router'
|
||||
import '../App.css'
|
||||
|
||||
export const Route = createFileRoute('/')({
|
||||
component: App
|
||||
})
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="App">
|
||||
<h2>Bienvenido al sistema de gestión de vuelos</h2>
|
||||
<Link to="/planes">Iniciar sesión</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
92
src/routes/login.tsx
Normal file
92
src/routes/login.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
import { useState } from 'react'
|
||||
|
||||
export const Route = createFileRoute('/login')({
|
||||
validateSearch: (search) => ({
|
||||
redirect: (search.redirect as string) || '/planes',
|
||||
}),
|
||||
beforeLoad: ({ context, search }) => {
|
||||
if (context.auth.isAuthenticated) {
|
||||
throw redirect({ to: search.redirect })
|
||||
}
|
||||
},
|
||||
component: LoginComponent,
|
||||
})
|
||||
|
||||
function LoginComponent() {
|
||||
const { auth } = Route.useRouteContext()
|
||||
const { redirect } = Route.useSearch()
|
||||
const [email, setEmail] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
setIsLoading(true)
|
||||
setError('')
|
||||
|
||||
try {
|
||||
await auth.login(email, password)
|
||||
// Supabase auth will automatically update context
|
||||
window.location.href = redirect
|
||||
} catch (err: any) {
|
||||
setError(err.message || 'Login failed')
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="max-w-md w-full space-y-4 p-6 border rounded-lg"
|
||||
>
|
||||
<h1 className="text-2xl font-bold text-center">Sign In</h1>
|
||||
|
||||
{error && (
|
||||
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium mb-1">
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
id="email"
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="password" className="block text-sm font-medium mb-1">
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 disabled:opacity-50"
|
||||
>
|
||||
{isLoading ? 'Signing in...' : 'Sign In'}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user