Skip to content

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 or null, filled at startup by getAuthStatus()
  • cart — array of locally added items, synchronized with the backend
  • loading — 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

export const API_BASE_URL =
  import.meta.env.VITE_API_URL || 'https://perfshop-api.perfshop.io';

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 (submitAddresssubmitShippingsubmitPaymentcreateOrder) 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/status every 15 seconds and returns { state, refresh }. It accepts a skipPollingRef to suspend polling during cultural note animations.
  • PedagogiqueOrchestrator.jsx — entry point mounted once in AppShell. Renders nothing if the journey is inactive. Automatically navigates to /s/:token when the journey is completed, via a useEffect dependent on state.completed. Resets its local flags (localTimerExpired, navigatedRef) when state.level changes — via a useEffect to avoid any ref mutation in the component body (React 18 Concurrent Mode anti-pattern).
  • PedagogiqueTimer.jsx — persistent visual counter, receives timerRemaining and timerTotal from the hook and notifies the orchestrator via onExpired.
  • PedagogiqueOverlay.jsx — non-blocking modal window that displays the current enigma, its statement, its answer field, hints and stars. Accepts hintsEnabled with backward compatibility via !== false (old server payloads without this field remain valid).
  • pedagogique/succes/PedagogiqueSucces.jsx — standalone success page, loaded via the /s/:token route.

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 key
  • tErr(msg) — resolves an error message that can be either an i18n key (like those thrown by services/api.js) or a raw server string. This function avoids displaying api.error.loadProducts to 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.

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.ymlperfshop-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.