import { useEffect, useState, type FormEvent } from 'react'; import { Link, useNavigate, useSearchParams } from 'react-router-dom'; import { useQuery } from '@tanstack/react-query'; import { ArrowLeft, Lock, Mail, MailCheck, ShieldCheck } from 'lucide-react'; import { api, setToken } from '@/lib/api'; import type { User } from '@/lib/types'; import { useAuth } from '@/stores/auth'; import { Logo } from '@/components/ui/logo'; import { Button } from '@/components/ui/button'; import { Spinner } from '@/components/ui/spinner'; export interface AuthConfig { mail: boolean; password: boolean; min_password: number; domains: string[]; courses: string[]; term: string } interface LoginResp { mode: string; token?: string; user?: User } interface ForgotResp { sent: boolean; first_time: boolean; ttl_minutes: number; dev_link?: string; hint?: string } export function LoginPage() { const nav = useNavigate(); const [params] = useSearchParams(); const setUser = useAuth((s) => s.setUser); const { data: cfg } = useQuery({ queryKey: ['auth-config'], queryFn: () => api('/auth/config') }); const [email, setEmail] = useState(params.get('email') || ''); const [password, setPassword] = useState(''); const [mode, setMode] = useState<'password' | 'forgot'>(params.get('mode') === 'forgot' ? 'forgot' : 'password'); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const [sent, setSent] = useState(null); const [consent, setConsent] = useState(false); const token = params.get('token'); // Legacy direct sign-in links (/connexion?token=…) sent before the password flow. useEffect(() => { if (!token) return; setBusy(true); api('/auth/verify', { method: 'POST', body: JSON.stringify({ token }) }) .then((r) => { if (r.token) setToken(r.token); if (r.user) setUser(r.user); nav('/', { replace: true }); }) .catch((e) => setError(e.message)) .finally(() => setBusy(false)); }, [token, nav, setUser]); const submit = async (e: FormEvent) => { e.preventDefault(); setError(null); setSent(null); setBusy(true); try { if (mode === 'forgot') { const r = await api('/auth/forgot', { method: 'POST', body: JSON.stringify({ email: email.trim() }) }); setSent(r); return; } if (!consent) { setError('Merci d\'accepter la politique de confidentialité pour continuer.'); return; } const r = await api('/auth/login', { method: 'POST', body: JSON.stringify({ email: email.trim(), password }) }); if (r.token) setToken(r.token); if (r.user) setUser(r.user); await api('/me/consent', { method: 'POST' }).catch(() => undefined); nav('/', { replace: true }); } catch (e) { setError(e instanceof Error ? e.message : 'Connexion impossible.'); } finally { setBusy(false); } }; return (

Comprendre, pratiquer et calculer en évaluation immobilière — avec un tuteur qui cite tes notes de cours.

{mode === 'password' ? (

Connexion

IMM1003 · IMM1033 · {cfg?.term || ''}. Réservé aux étudiant·es inscrit·es par le professeur (courriel @{cfg?.domains?.[0] || 'uqo.ca'}).

) : (

Première connexion ou mot de passe oublié

Saisis ton courriel UQO : tu recevras un lien pour choisir ton mot de passe. Le lien est valide {sent?.ttl_minutes ?? 60} minutes.

)} {token && busy &&
Vérification du lien…
} {sent ? (
{sent.sent ? 'Courriel envoyé' : 'Courriel non envoyé'}
{sent.sent &&

Ouvre le message « {sent.first_time ? 'Choisis ton mot de passe UQO-Chat' : 'Réinitialisation de ton mot de passe UQO-Chat'} » envoyé à {email.trim()} et clique sur le bouton. Pense à vérifier les indésirables.

} {sent.hint &&

{sent.hint}

} {sent.dev_link &&

Mode développement — lien : {sent.dev_link}

}
) : ( <> {mode === 'password' && (
)} {mode === 'password' && ( )} {error &&

{error}

} )}

Outil pédagogique. Ne remplace pas un évaluateur agréé (É.A.) de l'OEAQ. L'utilisation de l'IA doit être déclarée dans tes travaux, conformément au plan de cours.

Pas encore de compte ? Ton professeur doit ajouter ton courriel dans UQO-Chat ; tu recevras alors une invitation. Confidentialité

); }