Skip to content

Docs creation#4

Merged
codeline401 merged 4 commits into
mainfrom
docs-creation
Apr 27, 2026
Merged

Docs creation#4
codeline401 merged 4 commits into
mainfrom
docs-creation

Conversation

@codeline401

@codeline401 codeline401 commented Apr 27, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

Release Notes

  • New Features

    • Registration page with multi-role enrollment via invite codes
    • Route protection with role-based access control for dashboard pages
    • Enhanced authentication system with name and role fields
  • UI & Styling

    • Redesigned login page with improved visuals and messaging
    • Global theme colors and custom typography (DM Sans, Playfair Display)
    • Toast notification library integration
  • Documentation

    • Developer contribution guidelines and setup instructions
    • Project roadmap with feature tracking and acceptance criteria

feat(web): update index.html title and add Google Fonts

feat(web): add react-hot-toast for notifications

feat(web): implement ProtectedRoute component for role-based access control

feat(web): create RegisterPage for user registration with role selection and invite code

style(web): enhance LoginPage UI with improved styling and layout

style(web): add global CSS variables and background textures

fix(api): add nom and prenom fields to User model and make inviteCode optional

fix(api): update database migrations for inviteCode and user fields
@coderabbitai

coderabbitai Bot commented Apr 27, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f1a957c7-8735-4288-af51-d8441607711c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR enhances the authentication system by introducing invite code-based registration, updating database schema to include user name fields and school invite codes, implementing frontend route protection with a new ProtectedRoute component, and creating a registration page with updated styling and configuration.

Changes

Cohort / File(s) Summary
Documentation
CONTRIBUTING.md, ROADMAP.md
New developer onboarding guides covering setup steps, coding conventions, backend/frontend/Prisma contribution rules, Git workflow, and technical stack overview with feature inventory.
Database Schema
apps/api/prisma/migrations/..., apps/api/prisma/schema.prisma
Three migrations add inviteCode (unique, required) to School and nom/prenom fields to User. Schema updated to reflect new fields with inviteCode auto-generated via cuid().
Authentication Backend
apps/api/src/controllers/authController.ts, apps/api/src/schemas/authSchema.ts
Registration flow refactored to compute schoolId based on role and validate inviteCode for non-admin users; user creation now persists nom/prenom. Schema now requires nom/prenom and optional inviteCode instead of schoolId; exported RegisterInput/LoginInput types.
Authentication Frontend
apps/web/src/hooks/useAuth.ts, apps/web/src/pages/LoginPage.tsx, apps/web/src/pages/RegisterPage.tsx
New useAuth hook provides useRegister() and useLogin() mutations with auth state management. LoginPage refactored with new UI and uses hook; new RegisterPage component manages registration form state with role-conditional inviteCode field and error handling.
Route Protection & Structure
apps/web/src/App.tsx, apps/web/src/components/ProtectedRoute.tsx
New ProtectedRoute component guards routes using auth store state and optional role restrictions. App routing restructured to protect dashboard routes and /eleves with role-based access control.
Styling & Configuration
apps/web/tailwind.config.js, apps/web/src/index.css, apps/web/index.html, apps/web/package.json
Tailwind extended with custom imperial/emerald color scales and display/body font families. Global CSS adds theme variables, body typography, and .bg-texture utility. Google Fonts linked in HTML; react-hot-toast dependency added.

Sequence Diagrams

sequenceDiagram
    participant User as User
    participant Frontend as RegisterPage
    participant API as POST /api/auth/register
    participant DB as Prisma / School
    participant AuthStore as Auth Store
    participant Router as Router

    User->>Frontend: Enter registration details (nom, prenom, email, password, role, inviteCode)
    Frontend->>Frontend: Validate form locally
    User->>Frontend: Submit form
    Frontend->>API: POST {email, nom, prenom, password, role, inviteCode?}
    API->>DB: Query school by inviteCode (if not ADMIN/SUDO_ADMIN)
    alt Valid inviteCode or ADMIN/SUDO_ADMIN
        API->>DB: Create User with nom, prenom, computed schoolId
        API-->>Frontend: {success, user, token}
        Frontend->>AuthStore: setAuth(user, token)
        Frontend->>Router: Redirect to /login
    else Invalid/Missing inviteCode
        API-->>Frontend: {error: 400}
        Frontend->>User: Show error message
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 A rabbit hops through invite codes so bright,
Routes now protected with authorization might,
Registration flows where names now reside,
Colors and fonts in Tailwind styled with pride,
Authentication's dance—from form to store,
The warren applauds this feature once more! 🎉

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title 'Docs creation' is vague and does not accurately reflect the substantial code changes included in the PR, which involve authentication refactoring, database migrations, new React components, and styling updates alongside documentation. Rename the title to reflect the primary changes such as 'Add authentication flows with invite codes and user registration' or 'Implement user registration, invite codes, and protected routes'.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch docs-creation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/web/index.html (1)

2-2: ⚠️ Potential issue | 🟡 Minor

Set lang="fr" to match the French UI.

CONTRIBUTING.md (Line 136) states the UI language is French, but <html lang="en"> is unchanged. This affects screen-reader pronunciation, hyphenation, and search engine language detection. Also worth localizing the page <title> (Line 7) to French if appropriate.

-<html lang="en">
+<html lang="fr">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/index.html` at line 2, Update the document language on the root HTML
element by changing the lang attribute on the <html> tag from "en" to "fr" to
match the French UI (the <html lang="..."> element), and review/localize the
page <title> text to French as appropriate so screen readers, hyphenation, and
SEO reflect the correct language.
apps/api/src/controllers/authController.ts (1)

75-79: ⚠️ Potential issue | 🔴 Critical

Fix Zod 4 error property access—currently using incorrect error.errors instead of error.issues.

The project uses Zod 4.3.6, which changed the validation error property from errors to issues. The current code at lines 76 and 113 accesses error.errors, which will be undefined in Zod 4, causing all validation failures to silently fall through to the generic 500 error instead of returning a proper 400 with validation details.

Update to use proper type narrowing and the correct property:

-  } catch (error: any) {
-    if (error.errors) return res.status(400).json({ error: error.errors });
+  } catch (error) {
+    if (error instanceof z.ZodError) {
+      return res.status(400).json({ error: error.issues });
+    }

Apply this fix to both occurrences in authController.ts (lines 76 and 113).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/controllers/authController.ts` around lines 75 - 79, Replace the
incorrect Zod validation check that uses error.errors with proper narrowing to
ZodError and access to error.issues: in the catch blocks inside the auth
controller (e.g., in the signup/register and the other validation-handling catch
where error.errors is checked) import ZodError from 'zod' (or check
Array.isArray(error.issues)) and change the branch to if (error instanceof
ZodError || Array.isArray((error as any).issues)) return res.status(400).json({
error: (error as ZodError).issues || (error as any).issues }); apply the same
change to both occurrences in authController.ts so validation failures return
400 with error.issues instead of falling through to the 500 handler.
🧹 Nitpick comments (14)
apps/api/src/controllers/authController.ts (2)

47-50: Drop the || "" fallback — it's dead code and obscures intent.

You already short-circuit with 400 at Line 41-45 when data.inviteCode is falsy, so by Line 49 data.inviteCode is guaranteed to be a non-empty string. The || "" fallback can never fire and would, if ever reached, query for a school with inviteCode = "" instead of producing a clean error.

-      const school = await prisma.school.findUnique({
-        where: { inviteCode: data.inviteCode || "" },
-      });
+      const school = await prisma.school.findUnique({
+        where: { inviteCode: data.inviteCode },
+      });

Also, small typo on Line 47: corrspondantecorrespondante.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/controllers/authController.ts` around lines 47 - 50, Remove the
dead `|| ""` fallback from the prisma query so prisma.school.findUnique({ where:
{ inviteCode: data.inviteCode } }) uses the guaranteed non-empty value
(data.inviteCode is already validated earlier), and correct the inline comment
typo from "corrspondante" to "correspondante" to keep intent clear.

31-39: Prefer the Role enum over string literals for role comparisons.

registerSchema uses z.nativeEnum(Role), so data.role is typed as the Prisma Role enum. Comparing against string literals works but bypasses the type system — a typo like "SUDOADMIN" would compile silently. Importing Role from the generated Prisma client makes this safer and easier to refactor.

-    if (data.role === "ADMIN") {
+    if (data.role === Role.ADMIN) {
       ...
-    } else if (data.role === "SUDO_ADMIN") {
+    } else if (data.role === Role.SUDO_ADMIN) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/controllers/authController.ts` around lines 31 - 39, The code
compares data.role to string literals; instead import the Prisma-generated Role
enum and use it for comparisons (e.g., replace checks like data.role === "ADMIN"
/ "SUDO_ADMIN" with data.role === Role.ADMIN / Role.SUDO_ADMIN) so the
comparisons align with registerSchema's z.nativeEnum(Role) typing and prevent
typos; add the import for Role from your Prisma client (the same module used
elsewhere) and update the conditional branches in authController (where
data.role is used) to use the Role enum values.
CONTRIBUTING.md (2)

98-98: Add language hints to fenced code blocks (markdownlint MD040).

The directory tree, branch examples, commit message template and Git flow diagram blocks have no language specified. Use ```text (or appropriate language) so renderers can syntax-highlight or skip highlighting cleanly.

Also applies to: 298-298, 312-312, 329-329, 337-337

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CONTRIBUTING.md` at line 98, Update the fenced code blocks in CONTRIBUTING.md
that show the directory tree, branch examples, commit message template, and Git
flow diagram to include a language hint (e.g., replace ``` with ```text or
another appropriate language) so markdown renderers can properly
syntax-highlight or disable highlighting; search for the unlabeled fences around
the "directory tree", "branch examples", "commit message template", and "Git
flow diagram" sections and add the appropriate language tag to each opening
fence.

9-17: Verify TOC anchor links resolve correctly.

markdownlint flagged a few fragments here as invalid (MD051). GitHub strips emojis when generating heading anchors, so e.g. [Prérequis](#-prérequis) typically resolves to #prérequis (no leading dash). Please verify each link in the rendered preview and remove the leading hyphens if needed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CONTRIBUTING.md` around lines 9 - 17, The TOC links contain leading hyphens
that don't match GitHub's generated heading anchors (emojis are stripped), so
update each anchor in the list (e.g., the entries for "Prérequis",
"Installation", "Structure du projet", "Conventions de code", "Travailler sur le
backend", "Travailler sur le frontend", "Base de données & Prisma", "Workflow
Git", "Variables d'environnement") to match GitHub anchor rules — remove the
leading dash and ensure lowercased, spaces converted to hyphens and special
characters handled (for example change [Prérequis](`#-prérequis`) to
[Prérequis](`#prérequis`)); verify the rendered preview to confirm all links
resolve.
ROADMAP.md (1)

123-134: Nit: add a language identifier to the fenced code block.

markdownlint (MD040) flags the role-hierarchy block as missing a language. Use text to silence the warning while keeping it non-syntax-highlighted:

-```
+```text
 SUDO_ADMIN
   └─ Accès à toutes les écoles
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ROADMAP.md` around lines 123 - 134, The fenced code block containing the role
hierarchy (the block with SUDO_ADMIN, ADMIN, PROF / ELEVE / PARENT / USER) is
missing a language identifier and triggers markdownlint MD040; update that
fenced block in ROADMAP.md to start with ```text (i.e., add the language
identifier "text") so the block remains non-highlighted and the linter warning
is silenced while keeping the content unchanged.
apps/web/src/components/ProtectedRoute.tsx (1)

4-8: Tighten allowedRoles to the role union from User.

string[] lets typos like "ADMI" slip through at the call site (App.tsx lines 38). Reusing the role union from authStore.User makes role-list mismatches a compile error.

+import type { User } from "../store/authStore";
+
+type Role = User["role"];
+
 interface ProtectedRouteProps {
-  allowedRoles?: string[];
+  allowedRoles?: Role[];
 }

(You'll need to export the User interface from authStore.ts, or hoist Role to a shared types module.)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/ProtectedRoute.tsx` around lines 4 - 8,
ProtectedRouteProps currently types allowedRoles as string[] which permits
typos; change allowedRoles to the exact role union from your user model (e.g.,
use User['role'][] or a exported Role union) so mismatched role names become
compile errors. Export the User interface or the Role union from authStore.ts
(or move Role to a shared types module), update the ProtectedRouteProps
definition (allowedRoles) to reference that exported type, and then update
callers (like App.tsx) to use the tightened type.
apps/web/src/index.css (1)

22-34: Optional: declare .bg-texture via the v4 @utility directive.

Tailwind v4 introduced @utility to register custom utilities so they participate in the cascade layer system and accept variants (e.g. hover:bg-texture, md:bg-texture). A plain class still works, but the v4-idiomatic form is:

-/* Texture subtile sur les fonds */
-.bg-texture {
-  background-image:
-    radial-gradient(...),
-    radial-gradient(...);
-}
+/* Texture subtile sur les fonds */
+@utility bg-texture {
+  background-image:
+    radial-gradient(...),
+    radial-gradient(...);
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/index.css` around lines 22 - 34, The .bg-texture class is
declared as a plain CSS rule and won't be registered as a Tailwind v4 utility or
accept variants; convert it to a Tailwind utility by registering it with the
`@utility` directive (declare the same radial-gradient background under an
`@utility` for .bg-texture) so it participates in Tailwind's cascade and supports
variants like hover:bg-texture or md:bg-texture; update the declaration that
currently defines .bg-texture to use `@utility` with the identical
background-image values and ensure it is included in your Tailwind-processed CSS
file.
apps/web/src/pages/LoginPage.tsx (2)

67-72: Optional: extract the error-extraction shape to a reusable helper.

The inline as { response?: { data?: { error?: string } } } cast will be repeated in RegisterPage.tsx and any future mutation. Consider a small helper (or use axios.isAxiosError) so each page just calls getApiErrorMessage(loginMutation.error, "Identifiants incorrects").

// e.g. apps/web/src/lib/apiError.ts
import { AxiosError } from "axios";
export const getApiErrorMessage = (err: unknown, fallback: string): string => {
  if (err instanceof AxiosError) {
    return err.response?.data?.error ?? fallback;
  }
  return fallback;
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/pages/LoginPage.tsx` around lines 67 - 72, Extract the inline
error-shape cast into a reusable helper function (e.g. getApiErrorMessage) and
use it from LoginPage and RegisterPage instead of casting loginMutation.error;
implement getApiErrorMessage(err: unknown, fallback: string) to detect Axios
errors (using AxiosError instanceof check or axios.isAxiosError) and return
err.response?.data?.error ?? fallback, and update the span to call
getApiErrorMessage(loginMutation.error, "Identifiants incorrects") so the same
logic is centralized and reusable.

17-46: Optional: replace inline gradient/colour styles with Tailwind tokens.

Once the Tailwind v4 theme tokens are actually loaded (see comment on tailwind.config.js), the inline style={{ background: "..." }} blocks can be expressed with the new imperial/emerald scales (e.g. bg-gradient-to-br from-imperial-900 via-imperial-600 to-emerald-800), removing duplicated hex literals between LoginPage.tsx, index.css and tailwind.config.js.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/pages/LoginPage.tsx` around lines 17 - 46, The LoginPage.tsx
contains inline style gradients on the outer container, decorative circles, and
logo wrapper (the divs in the LoginPage component that currently use style={{
background: "..."}}, e.g. the outer container linear-gradient, the two
radial-gradient circles, and the logo's linear-gradient); replace those inline
style objects with Tailwind utility classes using your theme tokens (for example
use bg-gradient-to-br plus from-*/via-*/to-* classes like from-imperial-900
via-imperial-600 to-emerald-800 and radial gradient utilities or
bg-[radial-gradient(...)] variants if you’ve added plugin support), remove the
duplicated hex literals from LoginPage.tsx, and ensure the classes match the
tokens defined in tailwind.config.js once the v4 theme is loaded so color values
are centralized in Tailwind rather than inline styles.
apps/web/src/hooks/useAuth.ts (1)

7-19: Recommended: source these input types from the shared types package.

The API now exports RegisterInput and LoginInput from apps/api/src/schemas/authSchema.ts via z.infer, and the project already maintains a @school-mgt/types workspace package (per ROADMAP.md). Re-declaring the shapes here means a future change to registerSchema (e.g. tightening the role union, see review on authSchema.ts) won't propagate to the client.

Suggest re-exporting the inferred types from @school-mgt/types and importing them here:

-export interface RegisterInput {
-  email: string;
-  nom: string;
-  prenom: string;
-  password: string;
-  role: "ADMIN" | "USER" | "PROF" | "ELEVE" | "PARENT";
-  inviteCode?: string;
-}
-
-export interface LoginInput {
-  email: string;
-  password: string;
-}
+import type { RegisterInput, LoginInput } from "@school-mgt/types";
+export type { RegisterInput, LoginInput };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/hooks/useAuth.ts` around lines 7 - 19, The local redeclaration
of RegisterInput and LoginInput in useAuth.ts diverges from the shared API
types; replace these local interfaces by importing the inferred types exported
from the shared package (e.g. `@school-mgt/types`) that are derived from
apps/api/src/schemas/authSchema.ts (the registerSchema/login schema exports),
then re-export or use those imported types in useAuth hooks so changes to
registerSchema (like the role union) propagate; update any references to
RegisterInput and LoginInput in this file to the imported names and remove the
duplicate interface declarations.
apps/api/src/schemas/authSchema.ts (2)

5-16: Schema-level role restriction is defensive but not critical — the route is already protected by authentication and RBAC middleware.

The /api/auth/register route requires both authenticate middleware and authorizeRoles(Role.SUDO_ADMIN, Role.ADMIN), preventing anonymous privilege escalation. While z.nativeEnum(Role) accepts all enum values, the route-level guards prevent abuse. Consider restricting the schema to self-registerable roles for defense in depth:

-  role: z.nativeEnum(Role),
+  role: z.enum(["USER", "PROF", "ELEVE", "PARENT"]),

Note: nom and prenom require min(3), which rejects valid short names like "Li", "Wu", or "Ali". Consider lowering to min(1) or min(2).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/schemas/authSchema.ts` around lines 5 - 16, The registerSchema
currently allows any Role via z.nativeEnum(Role) and enforces nom/prenom with
min(3); tighten defense-in-depth by restricting the role field to only
self-registerable roles (e.g., use a union or array of allowed Role values
instead of the full Role enum) in registerSchema and relax name validations by
changing nom and prenom to min(1) or min(2) to accept short names; update the
schema’s role validator and the nom/prenom min constraints in the registerSchema
definition to implement these changes.

6-12: Adopt Zod 4 idiomatic schema constructors for cleaner code.

Zod 4.3.6 introduced standalone format schemas and improved enum support. Replace z.string().email() with z.email() and z.nativeEnum() with z.enum():

Suggested changes
-  email: z.string().email("Email invalide"),
+  email: z.email("Email invalide"),
   ...
-  role: z.nativeEnum(Role),
+  role: z.enum(Role),

Both forms are functionally equivalent, but the v4 forms are more concise, offer better tree-shaking, and z.nativeEnum() is deprecated.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/schemas/authSchema.ts` around lines 6 - 12, Replace the older
chained constructors with Zod v4 idiomatic forms: change email:
z.string().email("Email invalide") to email: z.email("Email invalide"); keep
nom, prenom, password as-is (they use z.string().min(...)); and replace role:
z.nativeEnum(Role) with role: z.enum(Object.values(Role) as [string,
...string[]]) so the Role enum is expressed using z.enum while preserving the
enum values and type safety.
apps/web/src/pages/RegisterPage.tsx (2)

105-116: Brittle inline error type assertion.

The cast Error & { response?: { data?: { error?: string } } } is repeated inline and assumes an Axios-shaped error. Extracting a small helper (or typing the error in useRegister) keeps the JSX readable and makes future shape changes a one-line edit.

♻️ Example helper
// utils/errors.ts
export const getApiErrorMessage = (err: unknown, fallback: string): string => {
  if (typeof err === "object" && err !== null && "response" in err) {
    const data = (err as { response?: { data?: { error?: string } } }).response?.data;
    if (data?.error) return data.error;
  }
  return fallback;
};
-                  {(
-                    registerMutation.error as Error & {
-                      response?: { data?: { error?: string } };
-                    }
-                  )?.response?.data?.error || "Erreur lors de l'inscription"}
+                  {getApiErrorMessage(registerMutation.error, "Erreur lors de l'inscription")}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/pages/RegisterPage.tsx` around lines 105 - 116, The JSX contains
a brittle inline error type assertion for registerMutation.error; extract a
reusable helper (e.g., getApiErrorMessage in utils/errors.ts) or add a typed
accessor in useRegister to normalize axios-like shapes; update the RegisterPage
JSX to call that helper (passing registerMutation.error and a fallback message)
instead of the repeated cast so the error extraction logic lives in one place
and the component stays readable.

243-259: Loading button has no accessible state.

While registerMutation.isPending, the visible text is replaced by Loader2 only, leaving the button with no accessible name. Add aria-busy and a screen-reader label so assistive tech announces the pending state.

♻️ Proposed fix
               <button
                 type="submit"
                 disabled={registerMutation.isPending}
+                aria-busy={registerMutation.isPending}
                 className="w-full flex items-center justify-center gap-2 py-3.5 rounded-2xl font-bold text-white transition-all active:scale-95 disabled:opacity-60 mt-2"
                 style={{
                   background: "linear-gradient(135deg, `#2e7d32`, `#4caf50`)",
                 }}
               >
                 {registerMutation.isPending ? (
-                  <Loader2 className="animate-spin" size={20} />
+                  <>
+                    <Loader2 className="animate-spin" size={20} aria-hidden="true" />
+                    <span className="sr-only">Création du compte en cours…</span>
+                  </>
                 ) : (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/pages/RegisterPage.tsx` around lines 243 - 259, The submit
button currently replaces its visible label with only the Loader2 icon when
registerMutation.isPending, leaving no accessible name; update the button (the
element using registerMutation, Loader2 and UserPlus) to include accessible
state and labeling by adding aria-busy={registerMutation.isPending} and either
an aria-label that reflects the pending state (e.g., "Création du compte en
cours") or a visually hidden live region/span inside the button with
aria-live="polite" that reads "Création du compte en cours" when
registerMutation.isPending, while keeping the normal label ("Créer mon compte")
for the non-pending state so screen readers always have text to announce.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/api/prisma/migrations/20260409080426_make_invite_code_required/migration.sql`:
- Around line 7-8: The migration sets "School"."inviteCode" NOT NULL but doesn't
backfill existing NULLs; add a backfill step before the ALTER by running an
UPDATE on "School" to set inviteCode for rows WHERE inviteCode IS NULL to a
unique value (e.g., use gen_random_uuid()::text or uuid_generate_v4()::text or
another deterministic unique generator available in your DB) and then run ALTER
TABLE "School" ALTER COLUMN "inviteCode" SET NOT NULL; ensure the UPDATE uses a
generator that produces unique strings to avoid collisions and include any
necessary extension setup (pgcrypto/uuid-ossp) if your chosen function requires
it.

In
`@apps/api/prisma/migrations/20260409083218_add_nom_prenom_user_optionnal/migration.sql`:
- Line 1: The migration folder name contains a typo: rename the folder
`20260409083218_add_nom_prenom_user_optionnal` to
`20260409083218_add_nom_prenom_user_optional` (and update any references to it,
e.g., in the migration SQL file `migration.sql`) OR if this migration has
already been applied in any environment, do NOT rename — instead create a new
corrective migration with the correct spelling; be sure to consider Prisma's
tracking in the `_prisma_migrations` table when deciding (if renaming locally
before the migration is ever applied, update the folder name; otherwise leave it
and add a new migration).
- Around line 1-3: The DB migration added User.nom and User.prenom as nullable
while schema.prisma declares them required, causing schema drift; fix by either
(A) creating a follow-up SQL migration that backfills existing null values for
"nom" and "prenom" (e.g., set them to a safe default such as empty string) and
then ALTER TABLE "User" ALTER COLUMN "nom" SET NOT NULL, ALTER COLUMN "prenom"
SET NOT NULL so the DB matches the required fields in schema.prisma, or (B)
change schema.prisma to make nom and prenom optional (nom String? and prenom
String?) and update the corresponding validation/usage in authController.ts and
authSchema.ts to accept/handle null/undefined values; pick one approach and
implement the corresponding migration or schema + code changes so Prisma and the
database no longer drift.

In `@apps/api/prisma/schema.prisma`:
- Around line 41-46: The inviteCode field currently uses Prisma client-side
default `@default`(cuid()), which won't set a value for raw SQL inserts or
existing rows; change inviteCode to use a database-generated default (e.g.
replace `@default`(cuid()) with `@default`(dbgenerated('gen_random_uuid()')) or
`@default`(dbgenerated('uuid_generate_v4()')) depending on your Postgres setup) so
the DB will supply values for direct INSERTs, and update the migration
20260409080426_make_invite_code_required to (1) backfill existing School rows by
populating inviteCode where NULL and (2) alter the column to set the DB default
and NOT NULL constraint; reference the inviteCode field and the migration name
when making these changes.

In `@apps/api/src/schemas/authSchema.ts`:
- Around line 7-8: The schema currently enforces z.string().min(3) for the nom
and prenom fields which rejects valid short names; update the auth schema to use
z.string().trim().min(1, "...") (or min(2) if you prefer) for both nom and
prenom so whitespace-only values are rejected but short legitimate names like
"Li" are accepted; locate and modify the nom and prenom entries in the
authSchema (the z.string().min(3, ...) lines) to apply .trim() and a reduced min
length and keep appropriate error messages.

In `@apps/web/src/hooks/useAuth.ts`:
- Around line 44-56: The mutation's response is currently untyped so
onSuccess(data) and data.user/data.token are implicitly any; update the call to
api.post inside useMutation's mutationFn to use Axios generics for the login
response shape (e.g., LoginResponse) and ensure the useMutation generic reflects
that response type so onSuccess receives a properly typed object; then verify
setAuth(user: User, token: string) is called with the correctly typed fields
(data.user, data.token) in onSuccess.

In `@apps/web/src/index.css`:
- Around line 4-6: Remove the legacy Tailwind v3 directives "@tailwind base",
"@tailwind components", and "@tailwind utilities" from index.css (these
`@tailwind` rules cause unknown at-rule errors in Tailwind v4); rely on the v4
import that pulls base/components/utilities instead, so delete those three lines
where they appear in the stylesheet.

In `@apps/web/src/pages/LoginPage.tsx`:
- Around line 77-103: Add matching id attributes to the email and password input
elements and connect each label via htmlFor so screen readers and clicks target
the inputs; specifically, update the label elements for "Email professionnel"
and "Mot de passe" to include htmlFor values that match new id attributes on the
inputs bound to email/setEmail and password/setPassword (e.g., ids like "email"
and "password" or similarly unique names), and apply the identical change in
RegisterPage.tsx for its corresponding email/password fields.

In `@apps/web/src/pages/RegisterPage.tsx`:
- Around line 124-148: The Nom field uses a misleading full-name placeholder
("Jean Rakoto"); update the placeholder for the input with name="nom" and value
bound to formData.nom to a surname-only example like "Rakoto" so it clearly
represents a family name and matches the Prénom field which uses "Jean"; locate
the input element with name="nom" and change its placeholder accordingly.
- Around line 216-224: The invite code is only visually uppercased via CSS but
stored as-typed, causing backend exact-match failures; update the form handling
so invite codes are normalized to a canonical form (e.g., uppercase + trimmed)
before sending or storing. Fix by detecting the inviteCode field in handleChange
and set the stored value to e.target.value.toUpperCase().trim() (so
formData.inviteCode is always normalized), or alternatively normalize when
building the submit payload in handleSubmit by doing payload.inviteCode =
formData.inviteCode.toUpperCase().trim(); ensure you modify the existing
handleChange/handleSubmit functions referenced here so the controlled input
value and the submitted value match.
- Around line 120-207: Each label is not associated with its input; add matching
id attributes to each input/select (e.g., id="nom", "prenom", "email",
"password", "role", "invitationCode") and set the corresponding label htmlFor to
the same id so clicking the label focuses the field and screen readers announce
them; update RegisterPage's inputs and the select that use name attributes
(name="nom", name="prenom", name="email", name="password", name="role") to
include those ids while keeping value={formData.xxx} and onChange={handleChange}
and apply the same fix for the invitation code field if present.
- Around line 197-205: The option element rendering roles currently sets
className="bg-imperial-800" while also specifying style={{ backgroundColor:
"#1a237e" }}, causing the inline style to override the Tailwind class; update
the <option> rendering (the element using role.value and role.label) to remove
the conflicting inline style and instead use the correct Tailwind class that
matches the desired color (either change className to bg-imperial-600 to match
"#1a237e" or remove the class and keep bg-imperial-800 by deleting the style
prop), ensuring only one source of truth for backgroundColor.

In `@apps/web/tailwind.config.js`:
- Around line 1-41: Tailwind v4 no longer auto-loads the JS config so the custom
tokens defined in apps/web/tailwind.config.js (the imperial and emerald color
scales and fontFamily keys display/body) are being ignored — add an explicit
reference to that config from your CSS (e.g. add `@config` "./tailwind.config.js";
at the top of apps/web/src/index.css) OR move those tokens into a CSS-first
`@theme` block in your CSS (defining --color-imperial-600, --font-display, etc.)
so classes like bg-imperial-600 and font-display resolve.

In `@CONTRIBUTING.md`:
- Around line 289-290: Replace the concrete dev credentials ('admin@ecole.com',
'admin_codeline401' and the 'dev_codeline401' seen in the DATABASE_URL example)
with generic placeholders (e.g., <your-dev-email>, <choose-a-strong-password>,
<your-db-password>) and add a short note pointing readers to .env.example (or
the environment config) as the source of truth for example values; ensure any
DATABASE_URL example uses a placeholder host/user/password rather than
real-looking secrets.

---

Outside diff comments:
In `@apps/api/src/controllers/authController.ts`:
- Around line 75-79: Replace the incorrect Zod validation check that uses
error.errors with proper narrowing to ZodError and access to error.issues: in
the catch blocks inside the auth controller (e.g., in the signup/register and
the other validation-handling catch where error.errors is checked) import
ZodError from 'zod' (or check Array.isArray(error.issues)) and change the branch
to if (error instanceof ZodError || Array.isArray((error as any).issues)) return
res.status(400).json({ error: (error as ZodError).issues || (error as
any).issues }); apply the same change to both occurrences in authController.ts
so validation failures return 400 with error.issues instead of falling through
to the 500 handler.

In `@apps/web/index.html`:
- Line 2: Update the document language on the root HTML element by changing the
lang attribute on the <html> tag from "en" to "fr" to match the French UI (the
<html lang="..."> element), and review/localize the page <title> text to French
as appropriate so screen readers, hyphenation, and SEO reflect the correct
language.

---

Nitpick comments:
In `@apps/api/src/controllers/authController.ts`:
- Around line 47-50: Remove the dead `|| ""` fallback from the prisma query so
prisma.school.findUnique({ where: { inviteCode: data.inviteCode } }) uses the
guaranteed non-empty value (data.inviteCode is already validated earlier), and
correct the inline comment typo from "corrspondante" to "correspondante" to keep
intent clear.
- Around line 31-39: The code compares data.role to string literals; instead
import the Prisma-generated Role enum and use it for comparisons (e.g., replace
checks like data.role === "ADMIN" / "SUDO_ADMIN" with data.role === Role.ADMIN /
Role.SUDO_ADMIN) so the comparisons align with registerSchema's
z.nativeEnum(Role) typing and prevent typos; add the import for Role from your
Prisma client (the same module used elsewhere) and update the conditional
branches in authController (where data.role is used) to use the Role enum
values.

In `@apps/api/src/schemas/authSchema.ts`:
- Around line 5-16: The registerSchema currently allows any Role via
z.nativeEnum(Role) and enforces nom/prenom with min(3); tighten defense-in-depth
by restricting the role field to only self-registerable roles (e.g., use a union
or array of allowed Role values instead of the full Role enum) in registerSchema
and relax name validations by changing nom and prenom to min(1) or min(2) to
accept short names; update the schema’s role validator and the nom/prenom min
constraints in the registerSchema definition to implement these changes.
- Around line 6-12: Replace the older chained constructors with Zod v4 idiomatic
forms: change email: z.string().email("Email invalide") to email: z.email("Email
invalide"); keep nom, prenom, password as-is (they use z.string().min(...)); and
replace role: z.nativeEnum(Role) with role: z.enum(Object.values(Role) as
[string, ...string[]]) so the Role enum is expressed using z.enum while
preserving the enum values and type safety.

In `@apps/web/src/components/ProtectedRoute.tsx`:
- Around line 4-8: ProtectedRouteProps currently types allowedRoles as string[]
which permits typos; change allowedRoles to the exact role union from your user
model (e.g., use User['role'][] or a exported Role union) so mismatched role
names become compile errors. Export the User interface or the Role union from
authStore.ts (or move Role to a shared types module), update the
ProtectedRouteProps definition (allowedRoles) to reference that exported type,
and then update callers (like App.tsx) to use the tightened type.

In `@apps/web/src/hooks/useAuth.ts`:
- Around line 7-19: The local redeclaration of RegisterInput and LoginInput in
useAuth.ts diverges from the shared API types; replace these local interfaces by
importing the inferred types exported from the shared package (e.g.
`@school-mgt/types`) that are derived from apps/api/src/schemas/authSchema.ts (the
registerSchema/login schema exports), then re-export or use those imported types
in useAuth hooks so changes to registerSchema (like the role union) propagate;
update any references to RegisterInput and LoginInput in this file to the
imported names and remove the duplicate interface declarations.

In `@apps/web/src/index.css`:
- Around line 22-34: The .bg-texture class is declared as a plain CSS rule and
won't be registered as a Tailwind v4 utility or accept variants; convert it to a
Tailwind utility by registering it with the `@utility` directive (declare the same
radial-gradient background under an `@utility` for .bg-texture) so it participates
in Tailwind's cascade and supports variants like hover:bg-texture or
md:bg-texture; update the declaration that currently defines .bg-texture to use
`@utility` with the identical background-image values and ensure it is included in
your Tailwind-processed CSS file.

In `@apps/web/src/pages/LoginPage.tsx`:
- Around line 67-72: Extract the inline error-shape cast into a reusable helper
function (e.g. getApiErrorMessage) and use it from LoginPage and RegisterPage
instead of casting loginMutation.error; implement getApiErrorMessage(err:
unknown, fallback: string) to detect Axios errors (using AxiosError instanceof
check or axios.isAxiosError) and return err.response?.data?.error ?? fallback,
and update the span to call getApiErrorMessage(loginMutation.error,
"Identifiants incorrects") so the same logic is centralized and reusable.
- Around line 17-46: The LoginPage.tsx contains inline style gradients on the
outer container, decorative circles, and logo wrapper (the divs in the LoginPage
component that currently use style={{ background: "..."}}, e.g. the outer
container linear-gradient, the two radial-gradient circles, and the logo's
linear-gradient); replace those inline style objects with Tailwind utility
classes using your theme tokens (for example use bg-gradient-to-br plus
from-*/via-*/to-* classes like from-imperial-900 via-imperial-600 to-emerald-800
and radial gradient utilities or bg-[radial-gradient(...)] variants if you’ve
added plugin support), remove the duplicated hex literals from LoginPage.tsx,
and ensure the classes match the tokens defined in tailwind.config.js once the
v4 theme is loaded so color values are centralized in Tailwind rather than
inline styles.

In `@apps/web/src/pages/RegisterPage.tsx`:
- Around line 105-116: The JSX contains a brittle inline error type assertion
for registerMutation.error; extract a reusable helper (e.g., getApiErrorMessage
in utils/errors.ts) or add a typed accessor in useRegister to normalize
axios-like shapes; update the RegisterPage JSX to call that helper (passing
registerMutation.error and a fallback message) instead of the repeated cast so
the error extraction logic lives in one place and the component stays readable.
- Around line 243-259: The submit button currently replaces its visible label
with only the Loader2 icon when registerMutation.isPending, leaving no
accessible name; update the button (the element using registerMutation, Loader2
and UserPlus) to include accessible state and labeling by adding
aria-busy={registerMutation.isPending} and either an aria-label that reflects
the pending state (e.g., "Création du compte en cours") or a visually hidden
live region/span inside the button with aria-live="polite" that reads "Création
du compte en cours" when registerMutation.isPending, while keeping the normal
label ("Créer mon compte") for the non-pending state so screen readers always
have text to announce.

In `@CONTRIBUTING.md`:
- Line 98: Update the fenced code blocks in CONTRIBUTING.md that show the
directory tree, branch examples, commit message template, and Git flow diagram
to include a language hint (e.g., replace ``` with ```text or another
appropriate language) so markdown renderers can properly syntax-highlight or
disable highlighting; search for the unlabeled fences around the "directory
tree", "branch examples", "commit message template", and "Git flow diagram"
sections and add the appropriate language tag to each opening fence.
- Around line 9-17: The TOC links contain leading hyphens that don't match
GitHub's generated heading anchors (emojis are stripped), so update each anchor
in the list (e.g., the entries for "Prérequis", "Installation", "Structure du
projet", "Conventions de code", "Travailler sur le backend", "Travailler sur le
frontend", "Base de données & Prisma", "Workflow Git", "Variables
d'environnement") to match GitHub anchor rules — remove the leading dash and
ensure lowercased, spaces converted to hyphens and special characters handled
(for example change [Prérequis](`#-prérequis`) to [Prérequis](`#prérequis`)); verify
the rendered preview to confirm all links resolve.

In `@ROADMAP.md`:
- Around line 123-134: The fenced code block containing the role hierarchy (the
block with SUDO_ADMIN, ADMIN, PROF / ELEVE / PARENT / USER) is missing a
language identifier and triggers markdownlint MD040; update that fenced block in
ROADMAP.md to start with ```text (i.e., add the language identifier "text") so
the block remains non-highlighted and the linter warning is silenced while
keeping the content unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5bece847-7ac8-4b5e-bc66-5a3bfa60134b

📥 Commits

Reviewing files that changed from the base of the PR and between 66c611f and ecfff24.

⛔ Files ignored due to path filters (7)
  • apps/api/src/generated/prisma/commonInputTypes.ts is excluded by !**/generated/**
  • apps/api/src/generated/prisma/internal/class.ts is excluded by !**/generated/**
  • apps/api/src/generated/prisma/internal/prismaNamespace.ts is excluded by !**/generated/**
  • apps/api/src/generated/prisma/internal/prismaNamespaceBrowser.ts is excluded by !**/generated/**
  • apps/api/src/generated/prisma/models/School.ts is excluded by !**/generated/**
  • apps/api/src/generated/prisma/models/User.ts is excluded by !**/generated/**
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (17)
  • CONTRIBUTING.md
  • ROADMAP.md
  • apps/api/prisma/migrations/20260409080304_add_invite_code_optional/migration.sql
  • apps/api/prisma/migrations/20260409080426_make_invite_code_required/migration.sql
  • apps/api/prisma/migrations/20260409083218_add_nom_prenom_user_optionnal/migration.sql
  • apps/api/prisma/schema.prisma
  • apps/api/src/controllers/authController.ts
  • apps/api/src/schemas/authSchema.ts
  • apps/web/index.html
  • apps/web/package.json
  • apps/web/src/App.tsx
  • apps/web/src/components/ProtectedRoute.tsx
  • apps/web/src/hooks/useAuth.ts
  • apps/web/src/index.css
  • apps/web/src/pages/LoginPage.tsx
  • apps/web/src/pages/RegisterPage.tsx
  • apps/web/tailwind.config.js

Comment on lines +7 to +8
-- AlterTable
ALTER TABLE "School" ALTER COLUMN "inviteCode" SET NOT NULL;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Add a backfill step before SET NOT NULL, otherwise this migration will fail on any existing School row.

The previous migration added inviteCode as nullable, and @default(cuid()) in schema.prisma is generated by the Prisma client at insert time — it is not a database-level default. So every pre-existing School row currently has inviteCode = NULL, and this SET NOT NULL will fail in any environment that already has data (staging/prod, and any dev DB that was seeded before this migration).

Recommended fix — backfill with a unique value before constraining:

+-- Backfill any existing rows with a unique invite code before enforcing NOT NULL
+CREATE EXTENSION IF NOT EXISTS pgcrypto;
+UPDATE "School" SET "inviteCode" = encode(gen_random_bytes(12), 'hex')
+WHERE "inviteCode" IS NULL;
+
 -- AlterTable
 ALTER TABLE "School" ALTER COLUMN "inviteCode" SET NOT NULL;

If the project intent is "always start from a fresh DB" please document that explicitly in CONTRIBUTING.md, since the migration's own warning comment already acknowledges this failure mode.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
-- AlterTable
ALTER TABLE "School" ALTER COLUMN "inviteCode" SET NOT NULL;
-- Backfill any existing rows with a unique invite code before enforcing NOT NULL
CREATE EXTENSION IF NOT EXISTS pgcrypto;
UPDATE "School" SET "inviteCode" = encode(gen_random_bytes(12), 'hex')
WHERE "inviteCode" IS NULL;
-- AlterTable
ALTER TABLE "School" ALTER COLUMN "inviteCode" SET NOT NULL;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/api/prisma/migrations/20260409080426_make_invite_code_required/migration.sql`
around lines 7 - 8, The migration sets "School"."inviteCode" NOT NULL but
doesn't backfill existing NULLs; add a backfill step before the ALTER by running
an UPDATE on "School" to set inviteCode for rows WHERE inviteCode IS NULL to a
unique value (e.g., use gen_random_uuid()::text or uuid_generate_v4()::text or
another deterministic unique generator available in your DB) and then run ALTER
TABLE "School" ALTER COLUMN "inviteCode" SET NOT NULL; ensure the UPDATE uses a
generator that produces unique strings to avoid collisions and include any
necessary extension setup (pgcrypto/uuid-ossp) if your chosen function requires
it.

@@ -0,0 +1,3 @@
-- AlterTable

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Typo in migration folder name: optionnaloptional.

The directory 20260409083218_add_nom_prenom_user_optionnal contains a misspelling. Once a migration has been applied anywhere, renaming it is risky (Prisma tracks the folder name in _prisma_migrations); flagging as a heads-up so future migration names avoid this and you can decide whether it's still safe to rename locally before merge.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/api/prisma/migrations/20260409083218_add_nom_prenom_user_optionnal/migration.sql`
at line 1, The migration folder name contains a typo: rename the folder
`20260409083218_add_nom_prenom_user_optionnal` to
`20260409083218_add_nom_prenom_user_optional` (and update any references to it,
e.g., in the migration SQL file `migration.sql`) OR if this migration has
already been applied in any environment, do NOT rename — instead create a new
corrective migration with the correct spelling; be sure to consider Prisma's
tracking in the `_prisma_migrations` table when deciding (if renaming locally
before the migration is ever applied, update the folder name; otherwise leave it
and add a new migration).

Comment on lines +1 to +3
-- AlterTable
ALTER TABLE "User" ADD COLUMN "nom" TEXT,
ADD COLUMN "prenom" TEXT;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Schema/migration drift: nom and prenom are NOT NULL in schema.prisma but NULLable in the DB.

The Prisma schema declares nom String / prenom String (required) at apps/api/prisma/schema.prisma Lines 27–28, but this migration adds them as nullable. Any subsequent prisma migrate dev will detect this drift and attempt to generate a SET NOT NULL migration — which will fail on any existing User row whose nom/prenom is NULL, and will also fail in CI/prod if rows exist.

Either:

  • Add a follow-up migration that backfills and then SET NOT NULL:
UPDATE "User" SET "nom" = COALESCE("nom", ''), "prenom" = COALESCE("prenom", '');
ALTER TABLE "User" ALTER COLUMN "nom" SET NOT NULL,
                   ALTER COLUMN "prenom" SET NOT NULL;
  • Or relax the schema to nom String? / prenom String? to match the current DB state (and update authController.ts / authSchema.ts accordingly).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/api/prisma/migrations/20260409083218_add_nom_prenom_user_optionnal/migration.sql`
around lines 1 - 3, The DB migration added User.nom and User.prenom as nullable
while schema.prisma declares them required, causing schema drift; fix by either
(A) creating a follow-up SQL migration that backfills existing null values for
"nom" and "prenom" (e.g., set them to a safe default such as empty string) and
then ALTER TABLE "User" ALTER COLUMN "nom" SET NOT NULL, ALTER COLUMN "prenom"
SET NOT NULL so the DB matches the required fields in schema.prisma, or (B)
change schema.prisma to make nom and prenom optional (nom String? and prenom
String?) and update the corresponding validation/usage in authController.ts and
authSchema.ts to accept/handle null/undefined values; pick one approach and
implement the corresponding migration or schema + code changes so Prisma and the
database no longer drift.

Comment on lines +41 to +46
id String @id @default(uuid())
nom String

// Code unique généré automatiquement que l'ADMIN partagera à ses collaborateurs
inviteCode String @unique @default(cuid())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Schema-level @default(cuid()) is applied client-side, not by Postgres — heads-up for raw SQL inserts and the existing-row case.

@default(cuid()) only takes effect when records are created through the Prisma client. Any direct INSERT INTO "School" in raw SQL, seed scripts, or future migrations will not auto-populate inviteCode and will violate the NOT NULL constraint set by 20260409080426_make_invite_code_required. This is also why pre-existing School rows need a backfill (see comment on that migration).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/prisma/schema.prisma` around lines 41 - 46, The inviteCode field
currently uses Prisma client-side default `@default`(cuid()), which won't set a
value for raw SQL inserts or existing rows; change inviteCode to use a
database-generated default (e.g. replace `@default`(cuid()) with
`@default`(dbgenerated('gen_random_uuid()')) or
`@default`(dbgenerated('uuid_generate_v4()')) depending on your Postgres setup) so
the DB will supply values for direct INSERTs, and update the migration
20260409080426_make_invite_code_required to (1) backfill existing School rows by
populating inviteCode where NULL and (2) alter the column to set the DB default
and NOT NULL constraint; reference the inviteCode field and the migration name
when making these changes.

Comment thread apps/api/src/schemas/authSchema.ts Outdated
Comment on lines +7 to +8
nom: z.string().min(3, "Le nom est requis"),
prenom: z.string().min(3, "Le prénom est requis"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Minor: min(3) on nom/prenom rejects valid short names.

Names like "Li", "Wu", "Jo", "Ali" are common and only 2–3 characters. Suggest relaxing to min(1) (or min(2) at most) and using .trim() to avoid whitespace-only entries.

-  nom: z.string().min(3, "Le nom est requis"),
-  prenom: z.string().min(3, "Le prénom est requis"),
+  nom: z.string().trim().min(1, "Le nom est requis"),
+  prenom: z.string().trim().min(1, "Le prénom est requis"),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
nom: z.string().min(3, "Le nom est requis"),
prenom: z.string().min(3, "Le prénom est requis"),
nom: z.string().trim().min(1, "Le nom est requis"),
prenom: z.string().trim().min(1, "Le prénom est requis"),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/schemas/authSchema.ts` around lines 7 - 8, The schema currently
enforces z.string().min(3) for the nom and prenom fields which rejects valid
short names; update the auth schema to use z.string().trim().min(1, "...") (or
min(2) if you prefer) for both nom and prenom so whitespace-only values are
rejected but short legitimate names like "Li" are accepted; locate and modify
the nom and prenom entries in the authSchema (the z.string().min(3, ...) lines)
to apply .trim() and a reduced min length and keep appropriate error messages.

Comment on lines +124 to +148
<input
type="text"
name="nom"
placeholder="Jean Rakoto"
className="w-full px-4 py-3.5 bg-white/10 border border-white/20 rounded-2xl text-white placeholder:text-blue-300/50 focus:ring-2 focus:ring-emerald-400 focus:border-transparent outline-none transition-all"
value={formData.nom}
onChange={handleChange}
required
/>
</div>

{/* Prénom */}
<div>
<label className="block text-sm font-semibold text-blue-100 mb-2">
Prénom
</label>
<input
type="text"
name="prenom"
placeholder="Jean"
className="w-full px-4 py-3.5 bg-white/10 border border-white/20 rounded-2xl text-white placeholder:text-blue-300/50 focus:ring-2 focus:ring-emerald-400 focus:border-transparent outline-none transition-all"
value={formData.prenom}
onChange={handleChange}
required
/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Misleading "Nom" placeholder.

In French, Nom is the surname and Prénom is the given name. The Nom placeholder is "Jean Rakoto" (full name) while Prénom is "Jean". Users will likely type the full name into Nom. Suggest using just "Rakoto" for Nom to be consistent and unambiguous.

✏️ Proposed fix
                 <input
                   type="text"
                   name="nom"
-                  placeholder="Jean Rakoto"
+                  placeholder="Rakoto"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<input
type="text"
name="nom"
placeholder="Jean Rakoto"
className="w-full px-4 py-3.5 bg-white/10 border border-white/20 rounded-2xl text-white placeholder:text-blue-300/50 focus:ring-2 focus:ring-emerald-400 focus:border-transparent outline-none transition-all"
value={formData.nom}
onChange={handleChange}
required
/>
</div>
{/* Prénom */}
<div>
<label className="block text-sm font-semibold text-blue-100 mb-2">
Prénom
</label>
<input
type="text"
name="prenom"
placeholder="Jean"
className="w-full px-4 py-3.5 bg-white/10 border border-white/20 rounded-2xl text-white placeholder:text-blue-300/50 focus:ring-2 focus:ring-emerald-400 focus:border-transparent outline-none transition-all"
value={formData.prenom}
onChange={handleChange}
required
/>
<input
type="text"
name="nom"
placeholder="Rakoto"
className="w-full px-4 py-3.5 bg-white/10 border border-white/20 rounded-2xl text-white placeholder:text-blue-300/50 focus:ring-2 focus:ring-emerald-400 focus:border-transparent outline-none transition-all"
value={formData.nom}
onChange={handleChange}
required
/>
</div>
{/* Prénom */}
<div>
<label className="block text-sm font-semibold text-blue-100 mb-2">
Prénom
</label>
<input
type="text"
name="prenom"
placeholder="Jean"
className="w-full px-4 py-3.5 bg-white/10 border border-white/20 rounded-2xl text-white placeholder:text-blue-300/50 focus:ring-2 focus:ring-emerald-400 focus:border-transparent outline-none transition-all"
value={formData.prenom}
onChange={handleChange}
required
/>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/pages/RegisterPage.tsx` around lines 124 - 148, The Nom field
uses a misleading full-name placeholder ("Jean Rakoto"); update the placeholder
for the input with name="nom" and value bound to formData.nom to a surname-only
example like "Rakoto" so it clearly represents a family name and matches the
Prénom field which uses "Jean"; locate the input element with name="nom" and
change its placeholder accordingly.

Comment on lines +197 to +205
<option
key={role.value}
value={role.value}
className="bg-imperial-800 text-white"
style={{ backgroundColor: "#1a237e" }}
>
{role.label}
</option>
))}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm whether `imperial` is defined as a custom Tailwind color anywhere.
fd -e ts -e js -e mjs -e cjs -e css 'tailwind|theme|index\.css|app\.css' | xargs rg -n -i 'imperial' 2>/dev/null
rg -nP --type=css '\bimperial\b'
rg -nP '"colors"\s*:|colors\s*:\s*\{' -C 5 | rg -n -i 'imperial' || echo "no 'imperial' palette token found"

Repository: codeline401/school-mgt-saas

Length of output: 379


🏁 Script executed:

cat apps/web/tailwind.config.js | head -30

Repository: codeline401/school-mgt-saas

Length of output: 856


bg-imperial-800 is defined but conflicts with the inline style.

bg-imperial-800 is a valid Tailwind class that resolves to #101558, but the inline style sets backgroundColor: "#1a237e" (which corresponds to imperial-600). The inline style will override the class, making the className ineffective and providing the wrong color value. Remove the className and either apply the correct shade via Tailwind or keep the inline style.

♻️ Suggested cleanup
                   <option
                     key={role.value}
                     value={role.value}
-                    className="bg-imperial-800 text-white"
+                    className="text-white"
                     style={{ backgroundColor: "#1a237e" }}
                   >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<option
key={role.value}
value={role.value}
className="bg-imperial-800 text-white"
style={{ backgroundColor: "#1a237e" }}
>
{role.label}
</option>
))}
<option
key={role.value}
value={role.value}
className="text-white"
style={{ backgroundColor: "#1a237e" }}
>
{role.label}
</option>
))}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/pages/RegisterPage.tsx` around lines 197 - 205, The option
element rendering roles currently sets className="bg-imperial-800" while also
specifying style={{ backgroundColor: "#1a237e" }}, causing the inline style to
override the Tailwind class; update the <option> rendering (the element using
role.value and role.label) to remove the conflicting inline style and instead
use the correct Tailwind class that matches the desired color (either change
className to bg-imperial-600 to match "#1a237e" or remove the class and keep
bg-imperial-800 by deleting the style prop), ensuring only one source of truth
for backgroundColor.

Comment on lines +216 to +224
<input
type="text"
name="inviteCode"
placeholder="Ex: ECOLE2026"
className="w-full px-4 py-3.5 bg-white/10 border border-white/20 rounded-2xl text-white placeholder:text-blue-300/50 focus:ring-2 focus:ring-emerald-400 focus:border-transparent outline-none transition-all uppercase tracking-widest"
value={formData.inviteCode}
onChange={handleChange}
required
/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Invite code visually uppercased but value not transformed — backend lookup will fail.

The input uses CSS uppercase (line 220) so the user sees ECOLE2026, but formData.inviteCode stores whatever was typed. The backend performs an exact match (where: { inviteCode: data.inviteCode || "" } in apps/api/src/controllers/authController.ts), so a user who types ecole2026 will see uppercase in the field, submit lowercase, and get "Code d'invitation invalide" with no obvious cause.

Either normalize on input or normalize in the submit payload.

🛠️ Proposed fix (normalize on change)
                   <input
                     type="text"
                     name="inviteCode"
                     placeholder="Ex: ECOLE2026"
                     className="w-full px-4 py-3.5 bg-white/10 border border-white/20 rounded-2xl text-white placeholder:text-blue-300/50 focus:ring-2 focus:ring-emerald-400 focus:border-transparent outline-none transition-all uppercase tracking-widest"
                     value={formData.inviteCode}
-                    onChange={handleChange}
+                    onChange={(e) =>
+                      setFormData((prev) => ({
+                        ...prev,
+                        inviteCode: e.target.value.toUpperCase().trim(),
+                      }))
+                    }
                     required
                   />

Alternatively, apply .toUpperCase().trim() to formData.inviteCode when building payload in handleSubmit.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<input
type="text"
name="inviteCode"
placeholder="Ex: ECOLE2026"
className="w-full px-4 py-3.5 bg-white/10 border border-white/20 rounded-2xl text-white placeholder:text-blue-300/50 focus:ring-2 focus:ring-emerald-400 focus:border-transparent outline-none transition-all uppercase tracking-widest"
value={formData.inviteCode}
onChange={handleChange}
required
/>
<input
type="text"
name="inviteCode"
placeholder="Ex: ECOLE2026"
className="w-full px-4 py-3.5 bg-white/10 border border-white/20 rounded-2xl text-white placeholder:text-blue-300/50 focus:ring-2 focus:ring-emerald-400 focus:border-transparent outline-none transition-all uppercase tracking-widest"
value={formData.inviteCode}
onChange={(e) =>
setFormData((prev) => ({
...prev,
inviteCode: e.target.value.toUpperCase().trim(),
}))
}
required
/>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/pages/RegisterPage.tsx` around lines 216 - 224, The invite code
is only visually uppercased via CSS but stored as-typed, causing backend
exact-match failures; update the form handling so invite codes are normalized to
a canonical form (e.g., uppercase + trimmed) before sending or storing. Fix by
detecting the inviteCode field in handleChange and set the stored value to
e.target.value.toUpperCase().trim() (so formData.inviteCode is always
normalized), or alternatively normalize when building the submit payload in
handleSubmit by doing payload.inviteCode =
formData.inviteCode.toUpperCase().trim(); ensure you modify the existing
handleChange/handleSubmit functions referenced here so the controlled input
value and the submitted value match.

Comment on lines 1 to 41
export default {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {},
extend: {
colors: {
// Bleu impérial foncé
imperial: {
50: "#e8eaf6",
100: "#c5cae9",
200: "#9fa8da",
300: "#7986cb",
400: "#5c6bc0",
500: "#3949ab",
600: "#1a237e", // Couleur principale
700: "#151c6b",
800: "#101558",
900: "#0a0e45",
},
// Vert émeraude
emerald: {
50: "#e8f5e9",
100: "#c8e6c9",
200: "#a5d6a7",
300: "#81c784",
400: "#66bb6a",
500: "#4caf50",
600: "#2e7d32", // Couleur principale
700: "#276128",
800: "#1b5e20",
900: "#114114",
},
},
fontFamily: {
// Police élégante et distinctive
display: ["'Playfair Display'", "Georgia", "serif"],
body: ["'DM Sans'", "sans-serif"],
},
},
},
plugins: [],
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Does Tailwind CSS v4 automatically load tailwind.config.js, or is the @config directive required?

💡 Result:

No, Tailwind CSS v4 does not automatically load tailwind.config.js without the @config directive. JavaScript config files are supported for backward compatibility but are no longer detected automatically. To use a tailwind.config.js file, you must explicitly load it using the @config directive in your CSS file, such as @config "./tailwind.config.js"; after @import "tailwindcss";. Tailwind v4 primarily uses CSS-first configuration with directives like @theme, and automatic detection of JS configs was removed, though upgrade tools may inject the @config for convenience during migration.

Citations:


Tailwind v4 requires explicit @config directive — imperial-600 and font-display will silently not exist without it.

In Tailwind v4, the JS config file is no longer auto-discovered by the Vite or PostCSS plugins. Without an explicit @config "./tailwind.config.js"; directive in apps/web/src/index.css (or migrating these tokens to a CSS @theme block), the imperial/emerald color scales and display/body font families defined here will be ignored. Any class such as bg-imperial-600 or font-display will not resolve.

Option A — Reference the JS config from CSS:

Add `@config` directive
 `@import` "tailwindcss";
+@config "./tailwind.config.js";
 `@plugin` "daisyui";

Option B — Migrate to CSS-first @theme (recommended):

Move tokens to `@theme` block
`@import` "tailwindcss";
`@plugin` "daisyui";

`@theme` {
  --color-imperial-50: `#e8eaf6`;
  --color-imperial-100: `#c5cae9`;
  --color-imperial-200: `#9fa8da`;
  --color-imperial-300: `#7986cb`;
  --color-imperial-400: `#5c6bc0`;
  --color-imperial-500: `#3949ab`;
  --color-imperial-600: `#1a237e`;
  --color-imperial-700: `#151c6b`;
  --color-imperial-800: `#101558`;
  --color-imperial-900: `#0a0e45`;

  --color-emerald-50: `#e8f5e9`;
  --color-emerald-100: `#c8e6c9`;
  --color-emerald-200: `#a5d6a7`;
  --color-emerald-300: `#81c784`;
  --color-emerald-400: `#66bb6a`;
  --color-emerald-500: `#4caf50`;
  --color-emerald-600: `#2e7d32`;
  --color-emerald-700: `#276128`;
  --color-emerald-800: `#1b5e20`;
  --color-emerald-900: `#114114`;

  --font-display: "Playfair Display", Georgia, serif;
  --font-body: "DM Sans", sans-serif;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/tailwind.config.js` around lines 1 - 41, Tailwind v4 no longer
auto-loads the JS config so the custom tokens defined in
apps/web/tailwind.config.js (the imperial and emerald color scales and
fontFamily keys display/body) are being ignored — add an explicit reference to
that config from your CSS (e.g. add `@config` "./tailwind.config.js"; at the top
of apps/web/src/index.css) OR move those tokens into a CSS-first `@theme` block in
your CSS (defining --color-imperial-600, --font-display, etc.) so classes like
bg-imperial-600 and font-display resolve.

Comment thread CONTRIBUTING.md Outdated
Comment on lines +289 to +290
- Email : `admin@ecole.com`
- Mot de passe : `admin_codeline401`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid documenting concrete dev credentials.

Hardcoding admin@ecole.com / admin_codeline401 (and dev_codeline401 in the DATABASE_URL example at Line 359) in a contributor doc encourages reuse of the same secret across local dev environments and increases the chance these strings end up in real .env files or hosted configs. Consider replacing with placeholders like <choose-a-strong-password> and pointing readers to .env.example for the source of truth.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CONTRIBUTING.md` around lines 289 - 290, Replace the concrete dev credentials
('admin@ecole.com', 'admin_codeline401' and the 'dev_codeline401' seen in the
DATABASE_URL example) with generic placeholders (e.g., <your-dev-email>,
<choose-a-strong-password>, <your-db-password>) and add a short note pointing
readers to .env.example (or the environment config) as the source of truth for
example values; ensure any DATABASE_URL example uses a placeholder
host/user/password rather than real-looking secrets.

…sistency

feat(auth): refactor user registration and login logic with improved error handling
fix(auth): enhance validation schema for registration inputs
fix(web): update HTML language attribute and improve accessibility in forms

Co-authored-by: Copilot <copilot@github.com>
@codeline401 codeline401 merged commit 15aaa56 into main Apr 27, 2026
1 check passed
codeline401 added a commit that referenced this pull request Jun 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant