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:
@@ -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