Next.js 15 app (App Router) using XState 5 to guide users through a short questionnaire and generate tailored recommendations with partner cards and resources.
Prerequisites
- Node.js 18+
- yarn (recommended)
Install & run
- Install dependencies:
yarn install
- Start the dev server:
yarn dev
- Accessible French UI with proper metadata and a global 404 page
src/
app/
layout.tsx
page.tsx
not-found.tsx
eco-portal/
page.tsx # Redirects to /eco-portal/status
[step]/
page.tsx # Async server route; guards & renders client form
result/
page.tsx # Results page (server) with Suspense
components/
layout/
Header/ # Header ("Portail Économique")
Footer/ # Footer (copyright)
cards/
BrochureDownload/ # PDF download link card
...cards contents
RecommendationCard/ # RECOMMENDATION_CONTENT mapping
Results/ # Results display recommended cards
Recap/ # Recap sidebar component
Loader/ # Loader (spinner + customizable label)
OptionButton/ # Option button component
OptionsList/ # Options list component
containers/
forms/
EcoPortalForm/
index.tsx # Client form UI
useEcoPortalForm.ts # Form logic (hooks/machine wiring)
machines/
ecoPortalMachine/
index.ts # State machine + guards/actions
constants.ts # Labels and copy
types.ts # Types/enums
ecoPortalMachine.test.ts # State machine tests
public/
-
File:
src/machines/ecoPortalMachine/index.ts
-
Guards map user choices to next step or direct recommendations
-
Actions build the
recommendations
array and record aresponses
trail -
Events:
SelectStatus
,SelectNeed
,SelectFinancing
,SelectInvest
,Back
,Restart
-
Question & options helpers from
getEcoQuestion/getEcoOptions
-
Responses are appended to
context.responses
for the recap trail and are forwarded to the results page via aresp
query param.
Recommendations content
- File:
src/components/RecommendationCard/index.ts
- Export:
RECOMMENDATION_CONTENT
{ Cards: { title?: string; card: React.ComponentType; logos: string[] }[] }
- Results page:
src/components/Results/index.tsx
- Parses
recs
andresp
from the URL - Renders grouped partner cards and a recap sidebar using the shared
Recap
component - Normalizes logo paths by stripping a leading
/public/
- Parses
-
Accueil:
src/app/page.tsx
— intro + logos grid (SVGs in/public
) -
Questionnaire entry:
src/app/eco-portal/page.tsx
— redirects to/eco-portal/status
-
Step pages:
src/app/eco-portal/[step]/page.tsx
— server-validated step with asyncparams
/searchParams
and Suspense-wrapped client form -
Résultats:
src/app/eco-portal/result/page.tsx
— Suspense + Results -
404:
src/app/not-found.tsx
— friendly French not-found page- « tous droit réservés à l’Ambassade de France à Tunis et le Comité Tunisie des Conseillers du commerce extérieur de la France. »
-
Loader:
src/components/Loader
(spinner + customizable label)
- Files under
/public
are served from root (e.g./logo.svg
, not/public/logo.svg
) - Logos are rendered via
next/image
with increased intrinsic size for crispness
- Site-wide metadata:
src/app/layout.tsx
- French locale, title template, keywords, description
- Page-specific metadata for Accueil, Questionnaire, Résultats
- Framework: Vitest
- Tests:
src/machines/ecoPortalMachine/ecoPortalMachine.test.ts
(12 tests)
Run
yarn test
yarn lint
- Do not link to
/public/...
; use/<filename>
instead - When adding logos, prefer SVGs; for PNGs, keep adequate intrinsic sizes
- If you add a new recommendation key, update both the machine and
RECOMMENDATION_CONTENT
- The form uses a “select then Next” interaction:
- Clicking an option highlights it (no immediate navigation)
- A “Suivant” button stays disabled until a choice is selected
- On Next, the choice is committed to the XState machine and routing proceeds to the next step (or results when complete)
- The recap sidebar is always visible:
- It shows on all steps and on the results page
- Layout is consistent: right sidebar on large screens, stacked below on small screens
- Results also display the recap using the
resp
query param to persist answers
- Install deps and run
yarn dev
- Make changes and add tests when editing machine behavior
yarn test
andyarn lint
before commit
Made with Next.js, XState, Tailwind, and TypeScript.