135 lines
4.3 KiB
TypeScript
135 lines
4.3 KiB
TypeScript
/**
|
|
* Page de connexion.
|
|
*
|
|
* Formulaire email + mot de passe, appel de `signIn` (Supabase via auth-client).
|
|
* Si la session devient active (arrivée déjà connecté OU succès de signIn),
|
|
* `useAuth` propage l'état et le useEffect redirige vers /dashboard. On évite
|
|
* ainsi un double `navigate` concurrent depuis le handler de submit.
|
|
*/
|
|
|
|
import { useEffect, useState, type FormEvent } from 'react'
|
|
import { Link, useNavigate } from 'react-router-dom'
|
|
import { Loader2 } from 'lucide-react'
|
|
|
|
import { Button } from '@/shared/components/ui/button'
|
|
import { Input } from '@/shared/components/ui/input'
|
|
import { Label } from '@/shared/components/ui/label'
|
|
import { signIn } from '@/shared/lib/auth-client'
|
|
import { useAuth } from '../hooks/useAuth'
|
|
|
|
function mapSignInError(message: string | undefined): string {
|
|
if (!message) return 'Connexion impossible. Réessayez dans quelques instants.'
|
|
if (message === 'Invalid login credentials') {
|
|
return 'Email ou mot de passe incorrect.'
|
|
}
|
|
if (message.includes('Email not confirmed')) {
|
|
return 'Email non confirmé. Vérifiez votre boîte mail.'
|
|
}
|
|
return 'Connexion impossible. Réessayez dans quelques instants.'
|
|
}
|
|
|
|
export function LoginPage() {
|
|
const navigate = useNavigate()
|
|
const { isAuthenticated, isLoading } = useAuth()
|
|
|
|
const [email, setEmail] = useState('')
|
|
const [password, setPassword] = useState('')
|
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
useEffect(() => {
|
|
if (!isLoading && isAuthenticated) {
|
|
navigate('/dashboard', { replace: true })
|
|
}
|
|
}, [isLoading, isAuthenticated, navigate])
|
|
|
|
if (isLoading || isAuthenticated) {
|
|
return (
|
|
<div
|
|
className="flex min-h-screen items-center justify-center bg-canvas text-ink-4"
|
|
role="status"
|
|
aria-live="polite"
|
|
aria-label="Chargement de la session"
|
|
>
|
|
<Loader2 className="size-6 animate-spin" aria-hidden="true" />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
async function handleSubmit(e: FormEvent<HTMLFormElement>) {
|
|
e.preventDefault()
|
|
setError(null)
|
|
setIsSubmitting(true)
|
|
try {
|
|
const { error: signInError } = await signIn(email, password)
|
|
if (signInError) {
|
|
setError(mapSignInError(signInError.message))
|
|
}
|
|
} finally {
|
|
setIsSubmitting(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<main className="flex min-h-screen items-center justify-center bg-canvas px-4 py-8">
|
|
<section className="w-full max-w-sm rounded-lg border border-line bg-surface p-6 shadow-sm">
|
|
<h1 className="text-2xl font-semibold text-ink-1">Se connecter</h1>
|
|
<p className="mt-1 text-sm text-ink-3">Accédez à votre espace Expria.</p>
|
|
|
|
{error && (
|
|
<div
|
|
role="alert"
|
|
className="mt-4 rounded-md border border-danger/40 bg-danger-bg px-3 py-2 text-sm text-danger"
|
|
>
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
<form onSubmit={handleSubmit} className="mt-6 space-y-4" noValidate>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="email">Email</Label>
|
|
<Input
|
|
id="email"
|
|
type="email"
|
|
autoComplete="email"
|
|
required
|
|
value={email}
|
|
onChange={(e) => setEmail(e.target.value)}
|
|
disabled={isSubmitting}
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="password">Mot de passe</Label>
|
|
<Input
|
|
id="password"
|
|
type="password"
|
|
autoComplete="current-password"
|
|
required
|
|
value={password}
|
|
onChange={(e) => setPassword(e.target.value)}
|
|
disabled={isSubmitting}
|
|
/>
|
|
</div>
|
|
|
|
<Button type="submit" className="w-full" disabled={isSubmitting}>
|
|
{isSubmitting ? (
|
|
<Loader2 className="animate-spin" aria-hidden="true" />
|
|
) : (
|
|
'Se connecter'
|
|
)}
|
|
</Button>
|
|
</form>
|
|
|
|
<p className="mt-6 text-center text-sm text-ink-3">
|
|
Pas encore de compte ?{' '}
|
|
<Link to="/register" className="text-expria underline-offset-4 hover:underline">
|
|
Créer un compte
|
|
</Link>
|
|
</p>
|
|
</section>
|
|
</main>
|
|
)
|
|
}
|
|
|
|
export default LoginPage
|