TypeScript 98.3%
CSS 0.9%
Shell 0.7%
1"use client";2import Link from "next/link";3import { useRouter } from "next/navigation";4import { useState } from "react";5import { ImmbotLogo, UqoLogo } from "@/components/logo";6import { Button, Card, Input, Label, Spinner, cn } from "@/components/ui";78const COURSES = [9 { code: "IMM1003" as const, title: "Éléments d'évaluation immobilière", session: "Automne 2026" },10 { code: "IMM1033" as const, title: "Méthodes du coût", session: "Automne 2026" },11];1213export default function RegisterPage() {14 const router = useRouter();15 const [form, setForm] = useState({ username: "", displayName: "", email: "", password: "", accessCode: "" });16 const [courses, setCourses] = useState<string[]>(["IMM1003"]);17 const [error, setError] = useState<string | null>(null);18 const [loading, setLoading] = useState(false);1920 const set = (k: keyof typeof form) => (e: React.ChangeEvent<HTMLInputElement>) =>21 setForm((f) => ({ ...f, [k]: e.target.value }));2223 async function submit(e: React.FormEvent) {24 e.preventDefault();25 setError(null);26 if (courses.length === 0) return setError("Choisissez au moins un cours.");27 setLoading(true);28 try {29 const res = await fetch("/api/auth/register", {30 method: "POST",31 headers: { "Content-Type": "application/json" },32 body: JSON.stringify({ ...form, courses }),33 });34 const data = await res.json();35 if (!res.ok) return setError(data.error ?? "Erreur d'inscription.");36 router.push("/chat");37 router.refresh();38 } catch {39 setError("Impossible de joindre le serveur.");40 } finally {41 setLoading(false);42 }43 }4445 return (46 <div className="min-h-dvh bg-app flex flex-col items-center justify-center px-4 py-10">47 <Link href="/" className="mb-8"><ImmbotLogo size={34} /></Link>48 <Card className="w-full max-w-md p-7 animate-fade-up">49 <h1 className="text-lg font-bold text-fg">Créer un compte étudiant</h1>50 <p className="text-sm text-muted mt-1">Choisissez vos cours et commencez à étudier.</p>51 <form onSubmit={submit} className="mt-6 space-y-4">52 <div className="grid grid-cols-2 gap-3">53 <div>54 <Label htmlFor="username">Identifiant</Label>55 <Input id="username" value={form.username} onChange={set("username")} autoComplete="username" required minLength={3} />56 </div>57 <div>58 <Label htmlFor="displayName">Nom affiché</Label>59 <Input id="displayName" value={form.displayName} onChange={set("displayName")} autoComplete="name" required />60 </div>61 </div>62 <div>63 <Label htmlFor="email">Courriel (optionnel)</Label>64 <Input id="email" type="email" value={form.email} onChange={set("email")} autoComplete="email" placeholder="prenom.nom@uqo.ca" />65 </div>66 <div>67 <Label htmlFor="password">Mot de passe (10 caractères minimum)</Label>68 <Input id="password" type="password" value={form.password} onChange={set("password")} autoComplete="new-password" required minLength={10} />69 </div>70 <div>71 <Label>Vos cours</Label>72 <div className="grid gap-2.5">73 {COURSES.map((c) => {74 const on = courses.includes(c.code);75 return (76 <button77 key={c.code}78 type="button"79 aria-pressed={on}80 onClick={() => setCourses((cs) => (on ? cs.filter((x) => x !== c.code) : [...cs, c.code]))}81 className={cn(82 "flex items-center justify-between px-4 py-3 rounded-xl border text-left transition-colors",83 on ? "border-brand-500 bg-brand-100/60 dark:bg-brand-900/50" : "border-app bg-card hover:bg-surface-2 dark:hover:bg-brand-900/30"84 )}85 >86 <span>87 <span className="block text-sm font-semibold text-fg">{c.code} — {c.title}</span>88 <span className="block text-[12px] text-muted mt-0.5">{c.session}</span>89 </span>90 <span className={cn("w-5 h-5 rounded-full border-2 flex items-center justify-center shrink-0", on ? "border-brand-500 bg-brand-500" : "border-app")}>91 {on && <span className="text-white text-[11px] leading-none">✓</span>}92 </span>93 </button>94 );95 })}96 </div>97 </div>98 <div>99 <Label htmlFor="accessCode">Code d'accès (si fourni par le professeur)</Label>100 <Input id="accessCode" value={form.accessCode} onChange={set("accessCode")} placeholder="Optionnel" />101 </div>102 {error && <p className="text-sm text-red-600 dark:text-red-400 bg-red-500/10 rounded-lg px-3 py-2" role="alert">{error}</p>}103 <Button type="submit" className="w-full justify-center" disabled={loading}>104 {loading ? <Spinner /> : "Créer mon compte"}105 </Button>106 <p className="text-[11.5px] text-muted text-center leading-relaxed">107 En créant un compte, vous acceptez les{" "}108 <Link href="/conditions" className="text-brand-500 hover:underline">conditions d'utilisation</Link> et la{" "}109 <Link href="/confidentialite" className="text-brand-500 hover:underline">politique de confidentialité</Link>{" "}110 (plateforme pédagogique réservée aux cours IMM1003 et IMM1033).111 </p>112 </form>113 <p className="text-[13px] text-muted mt-5 text-center">114 Déjà inscrit ? <Link href="/connexion" className="text-brand-500 font-medium hover:underline">Connexion</Link>115 </p>116 </Card>117 <div className="mt-8"><UqoLogo height={28} className="opacity-80 dark:invert" /></div>118 </div>119 );120}121