SPB Git forge

spb/immbot-ai

Public
1commits 1branches 0releases
1.5 MBsize
maindefault branch
20 days agolast push
TypeScript 98.3% CSS 0.9% Shell 0.7%
5.8 KB · 121 lines tsx
Raw Blame History
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