Docs creation#4
Conversation
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
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThis 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
Sequence DiagramssequenceDiagram
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 | 🟡 MinorSet
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 | 🔴 CriticalFix Zod 4 error property access—currently using incorrect
error.errorsinstead oferror.issues.The project uses Zod 4.3.6, which changed the validation error property from
errorstoissues. The current code at lines 76 and 113 accesseserror.errors, which will beundefinedin 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
400at Line 41-45 whendata.inviteCodeis falsy, so by Line 49data.inviteCodeis guaranteed to be a non-empty string. The|| ""fallback can never fire and would, if ever reached, query for a school withinviteCode = ""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:
corrspondante→correspondante.🤖 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 theRoleenum over string literals for role comparisons.
registerSchemausesz.nativeEnum(Role), sodata.roleis typed as the PrismaRoleenum. Comparing against string literals works but bypasses the type system — a typo like"SUDOADMIN"would compile silently. ImportingRolefrom 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.
markdownlintflagged 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. Usetextto 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: TightenallowedRolesto the role union fromUser.
string[]lets typos like"ADMI"slip through at the call site (App.tsxlines 38). Reusing the role union fromauthStore.Usermakes 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
exporttheUserinterface fromauthStore.ts, or hoistRoleto 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-texturevia the v4@utilitydirective.Tailwind v4 introduced
@utilityto 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 inRegisterPage.tsxand any future mutation. Consider a small helper (or useaxios.isAxiosError) so each page just callsgetApiErrorMessage(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 inlinestyle={{ background: "..." }}blocks can be expressed with the newimperial/emeraldscales (e.g.bg-gradient-to-br from-imperial-900 via-imperial-600 to-emerald-800), removing duplicated hex literals betweenLoginPage.tsx,index.cssandtailwind.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
RegisterInputandLoginInputfromapps/api/src/schemas/authSchema.tsviaz.infer, and the project already maintains a@school-mgt/typesworkspace package (perROADMAP.md). Re-declaring the shapes here means a future change toregisterSchema(e.g. tightening theroleunion, see review onauthSchema.ts) won't propagate to the client.Suggest re-exporting the inferred types from
@school-mgt/typesand 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/registerroute requires bothauthenticatemiddleware andauthorizeRoles(Role.SUDO_ADMIN, Role.ADMIN), preventing anonymous privilege escalation. Whilez.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:
nomandprenomrequiremin(3), which rejects valid short names like "Li", "Wu", or "Ali". Consider lowering tomin(1)ormin(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()withz.email()andz.nativeEnum()withz.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 inuseRegister) 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 byLoader2only, leaving the button with no accessible name. Addaria-busyand 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
⛔ Files ignored due to path filters (7)
apps/api/src/generated/prisma/commonInputTypes.tsis excluded by!**/generated/**apps/api/src/generated/prisma/internal/class.tsis excluded by!**/generated/**apps/api/src/generated/prisma/internal/prismaNamespace.tsis excluded by!**/generated/**apps/api/src/generated/prisma/internal/prismaNamespaceBrowser.tsis excluded by!**/generated/**apps/api/src/generated/prisma/models/School.tsis excluded by!**/generated/**apps/api/src/generated/prisma/models/User.tsis excluded by!**/generated/**package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (17)
CONTRIBUTING.mdROADMAP.mdapps/api/prisma/migrations/20260409080304_add_invite_code_optional/migration.sqlapps/api/prisma/migrations/20260409080426_make_invite_code_required/migration.sqlapps/api/prisma/migrations/20260409083218_add_nom_prenom_user_optionnal/migration.sqlapps/api/prisma/schema.prismaapps/api/src/controllers/authController.tsapps/api/src/schemas/authSchema.tsapps/web/index.htmlapps/web/package.jsonapps/web/src/App.tsxapps/web/src/components/ProtectedRoute.tsxapps/web/src/hooks/useAuth.tsapps/web/src/index.cssapps/web/src/pages/LoginPage.tsxapps/web/src/pages/RegisterPage.tsxapps/web/tailwind.config.js
| -- AlterTable | ||
| ALTER TABLE "School" ALTER COLUMN "inviteCode" SET NOT NULL; |
There was a problem hiding this comment.
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.
| -- 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 | |||
There was a problem hiding this comment.
Typo in migration folder name: optionnal → optional.
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).
| -- AlterTable | ||
| ALTER TABLE "User" ADD COLUMN "nom" TEXT, | ||
| ADD COLUMN "prenom" TEXT; |
There was a problem hiding this comment.
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 updateauthController.ts/authSchema.tsaccordingly).
🤖 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.
| id String @id @default(uuid()) | ||
| nom String | ||
|
|
||
| // Code unique généré automatiquement que l'ADMIN partagera à ses collaborateurs | ||
| inviteCode String @unique @default(cuid()) | ||
|
|
There was a problem hiding this comment.
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.
| nom: z.string().min(3, "Le nom est requis"), | ||
| prenom: z.string().min(3, "Le prénom est requis"), |
There was a problem hiding this comment.
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.
| 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.
| <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 | ||
| /> |
There was a problem hiding this comment.
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.
| <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.
| <option | ||
| key={role.value} | ||
| value={role.value} | ||
| className="bg-imperial-800 text-white" | ||
| style={{ backgroundColor: "#1a237e" }} | ||
| > | ||
| {role.label} | ||
| </option> | ||
| ))} |
There was a problem hiding this comment.
🧩 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 -30Repository: 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.
| <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.
| <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 | ||
| /> |
There was a problem hiding this comment.
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.
| <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.
| 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: [], | ||
| }; |
There was a problem hiding this comment.
🧩 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:
- 1: https://tailwindcss.com/docs/upgrade-guide
- 2: https://tailwindcss.com/docs/functions-and-directives
- 3: Support loading config files via
@configtailwindlabs/tailwindcss#14239 - 4: Inject
@config "..."when atailwind.config.{js,ts,...}is detected tailwindlabs/tailwindcss#14635 - 5: https://github.com/tailwindlabs/tailwindcss/blob/8feb6a758ab75ea63d22a59b5530f68d35b34ab8/CHANGELOG.md
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.
| - Email : `admin@ecole.com` | ||
| - Mot de passe : `admin_codeline401` |
There was a problem hiding this comment.
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>
Summary by CodeRabbit
Release Notes
New Features
UI & Styling
Documentation