- {/* --- Partie gauche : fond bleu + logo textuel + robot --- */}
-
-
- {/* Logo textuel */}
-
-
-
-
-
- {/* --- Partie droite : contenu dynamique (login/register) --- */}
-
+export default function AuthLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return (
+
+ {/* --- Partie gauche : fond bleu + logo textuel + robot --- */}
+
+
+ {/* Logo textuel */}
+
+
- );
+
+
+ {/* --- Partie droite : contenu dynamique (login/register) --- */}
+
+
+ );
}
diff --git a/frontend/web/src/app/authentication/login/page.tsx b/frontend/web/src/app/authentication/login/page.tsx
index 2c9d5b6..56ae449 100644
--- a/frontend/web/src/app/authentication/login/page.tsx
+++ b/frontend/web/src/app/authentication/login/page.tsx
@@ -1,83 +1,125 @@
-'use client';
+"use client";
-import { useRouter } from 'next/navigation';
-import { Button } from '@/component/ui/button';
-import { Input } from '@/component/ui/input';
-import { Label } from '@/component/ui/label';
-import { Mail, Eye } from 'lucide-react';
+import { useRouter } from "next/navigation";
+import { useState } from "react";
+import { Button } from "@/component/ui/button";
+import { Input } from "@/component/ui/input";
+import { Label } from "@/component/ui/label";
+import { Mail } from "lucide-react";
+import PasswordInput from "@/component/ui/password-input";
+import {
+ login,
+ type LoginSuccess,
+} from "@/lib/api/auth";
+import { saveUser } from "@/lib/session";
export default function LoginPage() {
- const router = useRouter();
+ const router = useRouter();
+ const [loading, setLoading] = useState(false);
+ const [err, setErr] = useState
(null);
- const handleSubmit = (e: React.FormEvent) => {
- e.preventDefault();
- router.push('/dashboard/upload');
- };
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ setErr(null);
+ setLoading(true);
- return (
+ const email = (document.getElementById("email") as HTMLInputElement)?.value.trim();
+ const password = (document.getElementById("mot-password") as HTMLInputElement)?.value;
+
+ try {
+ const res = await login({ email, password });
+ const u = res as LoginSuccess;
+
+ saveUser({ id: u.id, email: u.email, fullName: u.fullName });
+
+ // 🔥 Le loader reste visible jusqu'à ce que la page change
+ router.push("/dashboard/upload");
+ } catch (e: unknown) {
+ const message =
+ e instanceof Error
+ ? e.message
+ : typeof e === "string"
+ ? e
+ : "Erreur réseau/serveur";
+
+ setErr(message);
+ setLoading(false); // on enlève le loader si erreur
+ }
+ };
+
+ return (
+
+
CONNEXION
+
+
+ Heureux de vous revoir, entrez vos identifiants pour vous connecter
+
+
+
+
+ {/* Sign up link */}
+
+ Vous n'avez pas de compte ?{" "}
+
+ S'inscrire
+
+
+
+ {/*Loading overlay identique à celui de l’upload */}
+ {loading && (
+
+
+
+
Connexion en cours…
+
- );
+ )}
+
+ );
}
+
\ No newline at end of file
diff --git a/frontend/web/src/app/authentication/register/page.tsx b/frontend/web/src/app/authentication/register/page.tsx
index 4aad0b7..4e7bfc0 100644
--- a/frontend/web/src/app/authentication/register/page.tsx
+++ b/frontend/web/src/app/authentication/register/page.tsx
@@ -1,107 +1,284 @@
-'use client';
+"use client";
-import { useRouter } from 'next/navigation';
-import { Button } from '@/component/ui/button';
-import { Input } from '@/component/ui/input';
-import { Label } from '@/component/ui/label';
-import { Mail, Eye, User } from 'lucide-react';
+import { useState } from "react";
+import { useRouter } from "next/navigation";
+import { Button } from "@/component/ui/button";
+import { Input } from "@/component/ui/input";
+import { Label } from "@/component/ui/label";
+import { Mail, User } from "lucide-react";
+import { signup, type SignupRequest } from "@/lib/api/auth";
-export default function LoginPage() {
- const router = useRouter();
+type Field = "fullName" | "email" | "password" | "confirm" | "general";
+type FieldErrors = Partial>;
- const handleSubmit = (e: React.FormEvent) => {
- e.preventDefault();
- router.push('/dashboard/upload');
- };
+const cn = (...c: (string | false | undefined)[]) =>
+ c.filter(Boolean).join(" ");
- return (
+export default function RegisterPage() {
+ const router = useRouter();
+
+ // champs contrôlés
+ const [fullName, setFullName] = useState("");
+ const [email, setEmail] = useState("");
+ const [password, setPassword] = useState("");
+ const [confirm, setConfirm] = useState("");
+
+ const [errors, setErrors] = useState({});
+ const [loading, setLoading] = useState(false);
+ const [ok, setOk] = useState(null);
+
+ // --- Validations simples
+ const validate = (partial = false) => {
+ const e: FieldErrors = {};
+
+ if (!partial || fullName) {
+ if (!fullName.trim()) e.fullName = "Nom & prénoms requis.";
+ }
+ if (!partial || email) {
+ if (!email.trim()) e.email = "Email requis.";
+ else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email))
+ e.email = "Email invalide.";
+ }
+ if (!partial || password) {
+ if (!password) e.password = "Mot de passe requis.";
+ else if (password.length < 8) e.password = "Au moins 8 caractères.";
+ }
+ if (!partial || confirm) {
+ if (!confirm) e.confirm = "Confirmation requise.";
+ else if (confirm !== password)
+ e.confirm = "Les mots de passe ne correspondent pas.";
+ }
+
+ setErrors((prev) => ({ ...prev, ...e }));
+ return e;
+ };
+
+ const onBlurField = (f: Field) => {
+ // validation légère champ par champ
+ if (f === "fullName")
+ setErrors((s) => ({
+ ...s,
+ fullName: fullName.trim() ? "" : "Nom & prénoms requis.",
+ }));
+ if (f === "email") {
+ const err = !email.trim()
+ ? "Email requis."
+ : !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
+ ? "Email invalide."
+ : "";
+ setErrors((s) => ({ ...s, email: err }));
+ }
+ if (f === "password") {
+ const err = !password
+ ? "Mot de passe requis."
+ : password.length < 8
+ ? "Au moins 8 caractères."
+ : "";
+ setErrors((s) => ({ ...s, password: err }));
+ }
+ if (f === "confirm") {
+ const err = !confirm
+ ? "Confirmation requise."
+ : confirm !== password
+ ? "Les mots de passe ne correspondent pas."
+ : "";
+ setErrors((s) => ({ ...s, confirm: err }));
+ }
+ };
+
+ const onSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ setOk(null);
+ setErrors({}); // reset
+
+ const eAll = validate(); // validation complète
+ if (Object.values(eAll).some(Boolean)) return;
+
+ setLoading(true);
+ try {
+ const payload: SignupRequest = {
+ fullName: fullName.trim(),
+ email: email.trim(),
+ password,
+ };
+ const res = await signup(payload);
+
+ // Si le back renvoie { error } ou { fieldErrors: { email: ... } }
+ const anyRes: any = res;
+ if ((anyRes && anyRes.error) || (anyRes && anyRes.fieldErrors)) {
+ setErrors({
+ general: anyRes.error ?? "",
+ ...anyRes.fieldErrors,
+ });
+ return;
+ }
+
+ setOk(res.message ?? "Inscription réussie !");
+ setTimeout(() => router.replace("/authentication/login"), 800);
+ } catch (err: any) {
+ console.log("Error during : ", err);
+ // si le serveur renvoie HTML (DOCTYPE...), on masque par un message propre
+ setErrors({ general: "Erreur serveur. Réessayez dans un instant." });
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+
+
+ INSCRIPTION
+
+
+ Bienvenue sur StudyAI, veuillez remplir vos informations :
+
+
+
+ {/* Nom & Prénoms */}
+
+
+ Nom & Prénoms
+
+
+ setFullName(e.target.value)}
+ onBlur={() => onBlurField("fullName")}
+ aria-invalid={!!errors.fullName}
+ aria-describedby="nompre-error"
+ className={cn(
+ "pr-10 bg-white text-gray-600",
+ errors.fullName
+ ? "border-red-500 focus-visible:ring-red-500"
+ : "border-gray-300 focus-visible:ring-[#3FA9D9]",
+ )}
+ />
+
+
+ {errors.fullName && (
+
+ {errors.fullName}
+
+ )}
+
+
+ {/* Email */}
+
+
+ Email
+
+
+ setEmail(e.target.value)}
+ onBlur={() => onBlurField("email")}
+ aria-invalid={!!errors.email}
+ aria-describedby="email-error"
+ className={cn(
+ "pr-10 bg-white text-gray-600",
+ errors.email
+ ? "border-red-500 focus-visible:ring-red-500"
+ : "border-gray-300 focus-visible:ring-[#3FA9D9]",
+ )}
+ />
+
+
+ {errors.email && (
+
+ {errors.email}
+
+ )}
+
+
+ {/* Mot de passe */}
-
INSCRIPTION
-
-
- Bienvenue sur studyAI, veuillez remplir vos informations :
-
-
-
- {/* Nom&Prenoms */}
-
-
- Nom & Prénoms
-
-
-
-
-
-
- {/* Email */}
-
-
- {/* Password */}
-
-
- Mot de passe
-
-
-
-
-
-
- {/* Confirmation Password */}
-
-
- Confirmer le Mot de passe
-
-
-
-
-
-
-
-
- {/* Submit button */}
-
- S'inscrire
-
-
-
- {/* Sign up link */}
-
- Vous avez un compte ?{' '}
-
- Se connecter
-
+
+ Mot de passe
+
+ setPassword(e.target.value)}
+ onBlur={() => onBlurField("password")}
+ aria-invalid={!!errors.password}
+ aria-describedby="pwd-error"
+ className={cn(
+ "bg-white text-gray-600",
+ errors.password
+ ? "border-red-500 focus-visible:ring-red-500"
+ : "border-gray-300 focus-visible:ring-[#3FA9D9]",
+ )}
+ />
+ {errors.password && (
+
+ {errors.password}
+ )}
- );
+
+ {/* Confirmer le mot de passe */}
+
+
+ Confirmer le Mot de passe
+
+
setConfirm(e.target.value)}
+ onBlur={() => onBlurField("confirm")}
+ aria-invalid={!!errors.confirm}
+ aria-describedby="confirm-error"
+ className={cn(
+ "bg-white text-gray-600",
+ errors.confirm
+ ? "border-red-500 focus-visible:ring-red-500"
+ : "border-gray-300 focus-visible:ring-[#3FA9D9]",
+ )}
+ />
+ {errors.confirm && (
+
+ {errors.confirm}
+
+ )}
+
+
+ {/* Messages globaux */}
+ {errors.general && (
+ {errors.general}
+ )}
+ {ok && {ok}
}
+
+
+ {loading ? "Création du compte…" : "S'inscrire"}
+
+
+
+
+ Vous avez un compte ?{" "}
+
+ Se connecter
+
+
+
+ );
}
diff --git a/frontend/web/src/app/authentication/reset-password/MAJmdp/page.tsx b/frontend/web/src/app/authentication/reset-password/MAJmdp/page.tsx
index 9581559..a93d1b4 100644
--- a/frontend/web/src/app/authentication/reset-password/MAJmdp/page.tsx
+++ b/frontend/web/src/app/authentication/reset-password/MAJmdp/page.tsx
@@ -1,19 +1,20 @@
"use client";
-import { useRouter, useSearchParams } from "next/navigation";
+import { useRouter } from "next/navigation";
export default function VerifiedPage() {
const router = useRouter();
- const params = useSearchParams();
- const email = params.get("email") ?? "";
-
return (
-
+
-
Mot de passe mis à jour !
+
+ Mot de passe mis à jour !
+
- Vous pouvez maintenant créer un nouveau mot de passe pour votre compte.
- Appuyez sur Confirmer pour continuer.
+ Vous pouvez maintenant créer un nouveau mot de passe pour votre
+ compte.
+
+ Appuyez sur Confirmer pour continuer.
{
- e.preventDefault();
- router.push('/authentication/reset-password/ready');
- };
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+ router.push("/authentication/reset-password/ready");
+ };
- return (
-
-
Créer un nouveau mot de passe
-
-
- Saisissez et confirmez votre nouveau mot de passe.
-
+ return (
+
+
+ Créer un nouveau mot de passe
+
-
- {/* Email */}
-
-