Imported from mMormin/weeb_Maxime_Mormin-Boudot (
AGENTS.md). Install upstream withnpx skills add mMormin/weeb_Maxime_Mormin-Boudot. Copyright stays with the author.
Project Overview
Weeb showcase site — vitrine, blog, and authentication area for the Weeb company. Frontend SPA built with React 19 + Vite, consumes the Django REST API in ../weeb_API/.
Stack: React 19 + Vite + TypeScript + Tailwind CSS v4 + react-router v7 + Formik/Yup + Axios + Motion
Commands
pnpm dev # Start Vite dev server
pnpm build # tsc -b && vite build (typecheck included in build)
pnpm lint # ESLint
pnpm test # Vitest run
pnpm test:watch # Vitest watch
pnpm preview # Preview the production build
Conventions (must follow)
- Language: All code is in ENGLISH — variables, functions, types, file names, commit messages. UI strings (visible to the user) are in FRENCH (the site is French-speaking). Validation messages, button labels, error copy → French. Identifiers, comments, JSDoc → English.
- No
useEffect: Never useuseEffectunless there is absolutely no alternative. Prefer derived state, event handlers,useMemo, refs in event callbacks. If you reach for it, justify why no alternative works. - HTTP via the central client: All API calls go through
apiandAPI_ENDPOINTSfromsrc/config/api.ts. Never callaxios.createelsewhere or hardcode URLs in components. When the backend exposes a new route, add it toAPI_ENDPOINTSfirst. - Forms: Formik + Yup. Pattern: see
src/components/form/LoginForm.tsx. Yup validation messages in French (user-facing). Show field errors inline directly below the input — never as toasts or alerts. - Inline feedback, not toasts: Mutation success/error renders inline at the point of interaction (the
submitStatusblock inLoginFormis the canonical pattern). No toast library, noalert(). - Async feedback required: Every async interaction must have a visible loading state — button label change (
"Connexion..."),disabled, or a spinner. Never fire-and-forget without visual feedback. - Styling: Tailwind v4 only. No CSS modules, no styled-components. Custom tokens (e.g.
bg-primary,font-roboto,max-w-8xl) are defined insrc/index.css— extend there if you need a new token rather than inlining magic values. - Icons: Both
lucide-reactandreact-iconsare installed. Pick one set per component for visual consistency; don't mix inside a single file. - Routing:
react-routerv7. Routes declared insrc/main.tsx. KeepHomeeager (above-the-fold), lazy-load every other route vialazy(() => import(...)). - SEO: Per-route metadata lives in
src/utils/routeMetas.tsx. The rootApp.tsxreads it viauseLocation()and injects via<Helmet>. Do not sprinkle<Helmet>inside individual page components — add the entry torouteMetasinstead. - Page structure: Pages live in
src/pages/<PageName>/index.tsx. If a page has sections, put them insrc/pages/<PageName>/sections/. - Components: Split into
components/ui/(primitives likeButton,ArrowLink),components/layout/(Header,Footer),components/form/(form components). Don't add a new top-level folder without a reason. - Tests: Co-locate next to the file (
Component.tsx+Component.test.tsx). Vitest +@testing-library/react, JSDOM env. Test the user-visible behavior, not the implementation. - Animations:
motion(framer-motion successor). Keep them subtle and performant — avoid scroll-driven heavy effects or animations on long lists. - Lint/Format: ESLint only (no Prettier). Run
pnpm lintbefore committing. - TypeScript: Strict mode. Use
as constfor literal unions (seeAPI_ENDPOINTS). Preferunknownoveranyand narrow. - Image optimization: Build-time via
sharpinscripts/convert-images.mjs. When adding new image sources, document any pipeline change in that script rather than adding ad-hoc conversion steps.
Architecture (key things to know)
- SPA only: No SSR. Everything mounts under
BrowserRouterinsrc/main.tsx. The rootApp.tsxis the layout (Header +<Outlet />+ Footer + dynamic Helmet). - Backend: Django REST API in
../weeb_API/(defaulthttp://localhost:8000, override viaVITE_API_URL). - Auth: JWT via the Django backend.
LoginFormPOSTs toAPI_ENDPOINTS.login(/api/token/), receives{ access, refresh }, and stores them viasetTokens()fromsrc/utils/auth.ts(localStorage). The Axios interceptor insrc/config/api.tsreadsgetAccessToken()and addsAuthorization: Bearer <token>to every request automatically. UseclearTokens()on logout. - No CSRF: We use JWT Bearer auth, not Django sessions.
withCredentialsis off and there is no CSRF token logic — don't reintroduce it. - Routing flow:
App.tsxreads the current pathname, looks up SEO meta inrouteMetas, and renders<Outlet />for the matched route. Page-level layout starts inside the page component.