E-commerce frontend¶
The e-commerce frontend is the main application exposed to visitors and students. It is a React 18 Single Page Application served by an Nginx container, which exclusively consumes the Spring Boot backend REST APIs. It plays three superimposed roles: realistic commercial storefront, target of backend and frontend chaos, and playing surface for the pedagogical journey.
Sources
frontend/src/App.jsx, frontend/src/main.jsx, frontend/src/services/api.js, frontend/src/pages/*.jsx, frontend/src/i18n/I18nContext.jsx
Stack¶
| Component | Version / choice | Role |
|---|---|---|
| React | 18 (createRoot, Concurrent Mode) |
Rendering |
| React Router | v6 (BrowserRouter, Routes) |
Client-side navigation |
| Vite | 5 | Bundling, dev server, HMR |
| In-house I18nContext | zero dependency | FR/EN translation via static import |
Native fetch |
— | API calls with credentials: 'include' |
| Nginx (runtime) | 1.27 Alpine | Static server + env-inject.sh |
The main.jsx entry point mounts <App> inside an <I18nProvider> and a <React.StrictMode>. App.jsx declares the <Router> and an AppShell that manages the navbar, the footer, and mounts <PedagogiqueOrchestrator> outside the <Routes> so that the pedagogical overlay survives all navigations.
Routes¶
Routes are declared in App.jsx. All routes except /s/:token are rendered inside the standard AppShell (navbar + footer).
| Path | Component | Protection |
|---|---|---|
/ |
Redirect to /products |
— |
/products |
Products |
Public |
/products/:id |
ProductDetail |
Public |
/login |
Login |
Redirects to /products if already logged in |
/register |
Register |
Public |
/cart |
Cart |
Public (anonymous cart supported) |
/checkout |
Checkout |
Requires a logged-in user, otherwise redirect to /login |
/order-confirmation |
OrderConfirmation |
Public (arrival after order creation) |
/orders |
MyOrders |
Requires a logged-in user |
/profile |
Profile |
Requires a logged-in user |
/admin |
AdminPortal |
Intentionally public — S10-S12 security portal |
/s/:token |
PedagogiqueSucces |
Standalone page — no navbar, no footer, no orchestrator |
Routes whose path starts with /s/ are identified by the STANDALONE_PREFIXES constant and rendered without the application chrome. The token in the URL serves as opaque authentication to the success page: it is non-guessable and unique per session.
Global state¶
App.jsx manages three local states that act as global state:
user—{ id }object ornull, filled at startup bygetAuthStatus()cart— array of locally added items, synchronized with the backendloading— startup boolean, while the HTTP session is being resolved
Four callbacks are passed as props to all pages that touch the cart: addToCart, removeFromCart, updateCartQuantity, clearCart. Adding to the cart first calls apiAddToCart() on the API side then updates the local state — an "optimistic" approach that masks network latency.
API service (services/api.js)¶
The services/api.js file concentrates all the frontend's network calls. No React page makes a direct fetch: they systematically go through the functions exported from this module.
Base URL¶
VITE_API_URL is injected at Nginx container startup by the env-inject.sh script (same mechanism as VITE_LANG): this allows the image to be built once and deployed to several targets (dev, NAS prod, VPS) without rebuilding.
Chaos Scripting tokens¶
The module maintains an internal _tokens store that holds the five HTTP headers needed by the Chaos Scripting levels (X-Session-Token, X-Action-Token, X-CSRF-Token, X-Step-Token, X-Signature). The lifecycle is centralized:
| Function | Role |
|---|---|
_extractTokens(response) |
Reads response headers and updates _tokens |
_buildCheckoutHeaders() |
Builds the headers to send on each checkout step |
_clearTokens() |
Empties the store at logout |
_throwScriptingError(status, err) |
Propagates errors prefixed [Chaos Scripting CODE] |
Only the checkout funnel (submitAddress → submitShipping → submitPayment → createOrder) injects these headers. Cart and catalog endpoints remain unprotected — this is a functional requirement of Chaos Scripting: "the cart is always free, only the checkout is protected". See Chaos Scripting for details.
Exposed function families¶
| Family | Main functions |
|---|---|
| Authentication | login, logout, getAuthStatus |
| Catalog | getProducts, getProduct, getCategories, searchProducts |
| Cart | addToCart, getCart, updateCartItem, removeFromCart, clearCart |
| Checkout | submitAddress, submitShipping, submitPayment, createOrder |
| Profile / countries | getProfile, updateProfile, getCountries |
| Orders | verifyCheckoutAccess, getUserOrders, cancelOrder |
All functions return promises and throw an Error whose message is either an i18n key (e.g., 'api.error.loadProducts') or a raw string if the origin is the server. The resolution of these keys is delegated to the useT().tErr() hook described below.
Pedagogical token propagation¶
createOrder reads localStorage.getItem('ped_student_token') and, if it exists, sends it in the X-Student-Token header. The backend then recognizes the order as coming from a pedagogical journey and responds with the agentCode that will be displayed on the confirmation page.
Application pages¶
Pages are all functional components that consume useT() for i18n and the functions from services/api.js for network calls.
| Page | Main role |
|---|---|
Home.jsx |
Commercial landing, redirected to /products by default |
Products.jsx |
Paginated catalog list, category filter, text search |
ProductDetail.jsx |
Product page, "Add to cart" button |
SearchBar.jsx |
Search component, used in the catalog header |
CategorySidebar.jsx |
Category filter panel |
Cart.jsx |
Cart, quantity editing, switching to checkout |
Checkout.jsx |
Multi-step funnel: address, shipping, payment, validation |
OrderConfirmation.jsx |
Post-order summary, display of the agentCode if a journey is active |
MyOrders.jsx |
Order history of the logged-in account |
Profile.jsx |
User profile editing (title, date of birth, address) |
Login.jsx |
Login form |
Register.jsx |
Account creation |
AdminPortal.jsx |
S10-S12 pedagogical admin portal — see Admin portal |
Pedagogical journey pages¶
Three components and one hook form the pedagogical subsystem:
usePedagogiqueState.js— hook that polls/api/chaos/student/statusevery 15 seconds and returns{ state, refresh }. It accepts askipPollingRefto suspend polling during cultural note animations.PedagogiqueOrchestrator.jsx— entry point mounted once inAppShell. Renders nothing if the journey is inactive. Automatically navigates to/s/:tokenwhen the journey is completed, via auseEffectdependent onstate.completed. Resets its local flags (localTimerExpired,navigatedRef) whenstate.levelchanges — via auseEffectto avoid any ref mutation in the component body (React 18 Concurrent Mode anti-pattern).PedagogiqueTimer.jsx— persistent visual counter, receivestimerRemainingandtimerTotalfrom the hook and notifies the orchestrator viaonExpired.PedagogiqueOverlay.jsx— non-blocking modal window that displays the current enigma, its statement, its answer field, hints and stars. AcceptshintsEnabledwith backward compatibility via!== false(old server payloads without this field remain valid).pedagogique/succes/PedagogiqueSucces.jsx— standalone success page, loaded via the/s/:tokenroute.
See Pedagogical journey concept and architecture for how it works in detail.
Internationalization¶
The I18nContext.jsx provider statically loads the fr.json and en.json files at build time, and resolves the active language from the VITE_LANG Vite variable (default fr). The language is set at startup and does not change during the session — this choice is aligned with the global PerfShop strategy (see Internationalization).
Two functions are exposed by the useT() hook:
t(key, replacements)— translates a key; the fallback is the French dictionary, then the raw keytErr(msg)— resolves an error message that can be either an i18n key (like those thrown byservices/api.js) or a raw server string. This function avoids displayingapi.error.loadProductsto the end user when the translation is not found.
A useEffect updates document.documentElement.lang on every lang change, which allows accessibility tools and SEO to know the document language.
Navigation diagram¶
flowchart TD
Root["/"] --> Products["/products"]
Products --> Detail["/products/:id"]
Detail --> Cart["/cart"]
Products --> Cart
Cart --> CheckoutGuard{User<br/>logged in?}
CheckoutGuard -- no --> Login["/login"]
Login -- after login --> Checkout["/checkout"]
CheckoutGuard -- yes --> Checkout
Checkout --> Confirm["/order-confirmation"]
Confirm --> MyOrders["/orders"]
Products --> Profile["/profile"]
Login --> Register["/register"]
Products --> AdminPortal["/admin<br/>S10-S12 portal"]
Confirm -. "if journey<br/>completed" .-> Success["/s/:token<br/>standalone"]
Frontend Chaos Agent¶
App.jsx imports chaos-agent.js for its side effects — this script runs immediately on the browser side and installs the handlers necessary for Frontend Chaos (CPU burn, memory leak, DOM flood, fetch flood). It polls /api/chaos/frontend/state every 5 seconds and enables or disables client-side disturbances accordingly. See Frontend Chaos for the details of the injected behaviors.
Build and deployment¶
The frontend is distributed as a Docker image (docker-compose.yml → perfshop-frontend service). The Dockerfile performs a Vite build then copies the assets into Nginx. The env-inject.sh script executed at the ENTRYPOINT replaces the __VITE_API_URL__ and __VITE_LANG__ markers in the compiled JavaScript files — this allows publishing a generic image and configuring it at runtime.
For the local build, see Frontend build.