Story CP-023: Module Selection + Pack Detection UI¶
Module: control-plane Slice: Slice 8c from architecture (split from CP-008) Brand context: Papillon HR Suite (warm, guided registration UX) Priority: 8 (after CP-008) Depends on: CP-008 Estimated complexity: M
Objective¶
Build the module selection step (Step 2) of the self-registration flow. Users pick which modules they want, the UI auto-detects pack combinations for discounts, a sticky price summary bar updates in real-time, and at least one module must be selected to proceed. This slice reuses the ModuleCard component from CP-021 and the module catalog data structure from CP-020.
Backend Scope¶
No new backend endpoints in this slice. Module selection is stored as part of the SelfRegistrationRequest (selectedModuleCodes) already submitted in CP-008. This slice is frontend-only for the selection UX — the selected modules are passed forward to the registration request or stored client-side until the onboarding flow completes.
Pack Detection Logic (client-side)¶
Packs are predefined combinations of modules with a discount. Pack detection runs client-side for UX preview as modules are selected. The server-side PricingEngine (CP-009) is the authoritative source for billing — both use the same pack definitions.
| Pack Name | Modules | Discount |
|---|---|---|
| Pack Contrôle | ABSMGT + QRCONTR | ~12-30% depending on segment |
| Pack Finance | EXPMGT + COMMIT | ~5-15% |
| Pack Essentiel | HRCORE + ABSMGT | ~17-20% |
| Pack Standard | HRCORE + ABSMGT + QRCONTR | Incremental discount |
| Pack Complet | HRCORE + ABSMGT + QRCONTR + EXPMGT + PERFOB | Highest discount |
Pack detection algorithm: 1. On every module selection/deselection, check if current selection is a superset of any pack 2. If multiple packs match, show the one with the highest discount 3. Show animated price transition in summary bar when pack is detected/lost
Module Metadata (hardcoded — same as CP-021)¶
Each sellable module has: name, icon, one-line description (French), feature bullet list (French), starting price per employee count segment.
Sellable module codes: HRCORE, EMPMGT, ABSMGT, QRCONTR, TIMACT, EXPMGT, COMMIT, PAYROL, HSEMGT, PERFOB, GPEC, LTRAIN, ATSMGT, ADVDOC, CRMMGT
Frontend Scope¶
Pages & Routes¶
/inscription/modules— Module selection (Step 2 of registration)
Components¶
RegistrationStep2Page.tsx— Module selection grid with pack detectionModuleCard.tsx— Reuse from CP-021 (available, selected states only — no locked/coming soon in registration)PriceSummaryBar.tsx— Sticky bottom bar showing module count, total price, pack discount if detected. Component spec in design §19.6.StepIndicator.tsx— Reuse from CP-008 (shows 2/3 active)
UI States (ALL REQUIRED)¶
Registration Step 2 (Module Selection):
| State | Behavior | French Copy |
|---|---|---|
| Loading | Skeleton: 6 module cards | — |
| Empty selection | "Continuer" disabled, summary shows "0 modules" | "Selectionnez au moins 1 module. Vous pourrez en ajouter plus tard." |
| Modules selected | Summary bar shows count + price + pack if detected | "{count} module(s) . {price} FCFA/mois" |
| Pack detected | Animated price change in summary | "Pack {packName} detecte . -{discount}%" |
| Error | Toast | "Une erreur est survenue." |
PriceSummaryBar States:
| State | Behavior | French Copy |
|---|---|---|
| No selection | Disabled continue button, zero count | "0 modules . 0 FCFA/mois" |
| Selection without pack | Active continue button, sum of individual prices | "{count} module(s) . {price} FCFA/mois" |
| Selection with pack | Active continue button, strikethrough original + discounted price | "Pack {packName} detecte . -{discount}% . {discountedPrice} FCFA/mois" |
Responsive Behavior¶
- 360px (mobile): Stacked module cards (1 column). Sticky bottom summary bar.
- 768px (tablet): 2-column card grid.
- 1024px+ (desktop): 3-column card grid. Summary bar at bottom of card area.
Interactions¶
- Module card tap/click: Toggles selection. Card transitions between Available/Selected states with 200ms animation.
- Pack auto-detection: As modules are selected, pack detection runs client-side. If a pack is detected, the PriceSummaryBar animates the price change (strikethrough old price → show discounted price).
- Pack lost: If a module is deselected and the pack is broken, the discount disappears with reverse animation.
- "Continuer →" button: Disabled until at least 1 module is selected. On click, saves selection to registration state and proceeds to email verification (if free trial) or payment choice.
French micro-copy:
| Key | French Text |
|---|---|
modules.step_title |
"Choisissez vos modules" |
modules.step_subtitle |
"Selectionnez au moins 1 module. Vous pourrez en ajouter plus tard." |
modules.count |
"{count} module(s)" |
modules.price |
"{price} FCFA/mois" |
modules.pack_detected |
"Pack {packName} detecte . -{discount}%" |
modules.continue |
"Continuer →" |
modules.back |
"← Retour" |
Acceptance Criteria¶
AC-071: Module selection requires at least 1
Given I am on the module selection step
When I try to continue without selecting any module
Then the "Continuer" button is disabled
AC-072: Pack auto-detection
Given I select Absences + Presence QR on the module selection step
When both are selected
Then the summary shows "Pack Controle detecte . -12%"
And the price updates from individual sum to pack price
AC-073: Onboarding completes in under 5 minutes
Given I start the registration form
When I fill all fields, verify email, and reach the Dashboard
Then the total elapsed time is under 5 minutes (functional, not a strict test — UX validates)
OHADA & Regulatory Rules¶
- No direct OHADA regulatory requirements for module selection UI.
- Module prices are displayed in XOF with zero decimal places (e.g., "15 000 FCFA/mois").
Standards & Conventions¶
docs/standards/react-typescript-guidelines.md— §Multi-step forms, §Component composition, §Animation patternsdocs/design/control-plane-design.md— §19.2 ModuleCard, §19.6 PriceSummaryBar
Testing Requirements¶
Unit Tests¶
- Pack detection: all 5 packs detected when modules match
- Pack detection: highest discount wins when multiple packs match
- Pack detection: pack lost when required module deselected
- PriceSummaryBar: renders correct count and price
- PriceSummaryBar: shows strikethrough + discount when pack detected
- ModuleCard: toggle selection state on click
- Continue button disabled when 0 modules selected
Integration Tests¶
- Full Step 2 flow: select modules → pack detected → continue
- Module card rendering from catalog data
- Price calculation with and without pack discount
What QA Will Validate¶
- Module selection: card selection toggles correctly
- Pack detection: price summary animates on pack detection/loss
- "Continuer" button disabled until at least 1 module selected
- All 3 responsive breakpoints (360px, 768px, 1024px)
- Step indicator shows 2/3
- Module descriptions and prices displayed in French
Out of Scope¶
- Registration form (Step 1) — CP-008
- Email verification — CP-022
- Provisioning pipeline — CP-024
- Payment flow (Step 3) — CP-009
- Locked/coming soon module states — CP-021 (only available/selected used during registration)
Definition of Done¶
- [ ] All backend endpoints implemented — N/A (frontend-only)
- [ ] All frontend pages render correctly at 360px, 768px, 1024px
- [ ] All 5 UI states implemented (loading, empty, error, offline, success)
- [ ] French micro-copy matches design spec exactly
- [ ] All acceptance criteria pass manually
- [ ] Unit tests written and passing
- [ ] Integration tests written and passing
- [ ] Multi-tenant isolation verified — N/A (pre-tenant operation)
- [ ] Offline write+sync works — N/A (registration always online)
- [ ] No TypeScript
any— all types explicit - [ ] No Java raw types — N/A (frontend-only)
- [ ] API responses follow standard envelope format — N/A (frontend-only)
- [ ] Audit trail entries created for every data mutation — N/A (no mutations)