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%
2.7 KB · 67 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 } from "@/components/ui";78export default function LoginPage() {9  const router = useRouter();10  const [username, setUsername] = useState("");11  const [password, setPassword] = useState("");12  const [error, setError] = useState<string | null>(null);13  const [loading, setLoading] = useState(false);1415  async function submit(e: React.FormEvent) {16    e.preventDefault();17    setError(null);18    setLoading(true);19    try {20      const res = await fetch("/api/auth/login", {21        method: "POST",22        headers: { "Content-Type": "application/json" },23        body: JSON.stringify({ username, password }),24      });25      const data = await res.json();26      if (!res.ok) {27        setError(data.error ?? "Erreur de connexion.");28        return;29      }30      router.push(data.mustChangePassword ? "/changer-mot-de-passe?force=1" : "/chat");31      router.refresh();32    } catch {33      setError("Impossible de joindre le serveur.");34    } finally {35      setLoading(false);36    }37  }3839  return (40    <div className="min-h-dvh bg-app flex flex-col items-center justify-center px-4">41      <Link href="/" className="mb-8"><ImmbotLogo size={34} /></Link>42      <Card className="w-full max-w-sm p-7 animate-fade-up">43        <h1 className="text-lg font-bold text-fg">Connexion</h1>44        <p className="text-sm text-muted mt-1">Accédez à vos cours d'évaluation immobilière.</p>45        <form onSubmit={submit} className="mt-6 space-y-4">46          <div>47            <Label htmlFor="username">Identifiant</Label>48            <Input id="username" value={username} onChange={(e) => setUsername(e.target.value)} autoComplete="username" autoFocus required />49          </div>50          <div>51            <Label htmlFor="password">Mot de passe</Label>52            <Input id="password" type="password" value={password} onChange={(e) => setPassword(e.target.value)} autoComplete="current-password" required />53          </div>54          {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>}55          <Button type="submit" className="w-full justify-center" disabled={loading}>56            {loading ? <Spinner /> : "Se connecter"}57          </Button>58        </form>59        <p className="text-[13px] text-muted mt-5 text-center">60          Pas de compte ? <Link href="/inscription" className="text-brand-500 font-medium hover:underline">S'inscrire</Link>61        </p>62      </Card>63      <div className="mt-8"><UqoLogo height={28} className="opacity-80 dark:invert" /></div>64    </div>65  );66}67