Story CP-021: Module Gating Frontend: Navigation + Upsell Cards¶
Module: control-plane Slice: Slice 7c from architecture (split from CP-007) Brand context: Papillon HR Suite (upsell display is Papillon-only) Priority: 7 (after CP-020) Depends on: CP-020 Estimated complexity: M
Objective¶
Implement the frontend module gating experience: locked modules appear in the navigation with distinct styling, and tapping them shows an upsell card. Admins see a subscribe button; employees see a "contact your admin" message. This slice consumes the module catalog API from CP-020 and the module gating backend from CP-007.
Backend Scope¶
No new backend work in this slice. Consumes:
- GET /api/v1/tenant/modules (from CP-007) — tenant's module statuses
- GET /api/v1/tenant/modules/catalog (from CP-020) — full catalog with pricing
Frontend Scope¶
Pages & Routes¶
- No dedicated page. Module gating manifests as:
- Navigation items (locked modules in sidebar/bottom nav)
- Upsell cards (when locked module is tapped)
Components¶
ModuleCard.tsx— Component spec in design §19.2. Props: name, description, icon, price, available, selected, locked, comingSoonDate, onSelect.ModuleUpsellSheet.tsx— Bottom sheet (mobile) / popover (desktop) with module description, feature list, and subscribe CTA.- Navigation integration: modify sidebar/bottom nav to show locked modules with styling.
Module descriptions are hardcoded per module (not DB-driven for MVP). Each module has: name, icon, one-line description, feature bullet list, starting price.
UI States (ALL REQUIRED)¶
Module Upsell Card:
| State | Behavior | French Copy |
|---|---|---|
| Loading | Skeleton card | — |
| Available, unselected | White bg, standard border | — |
| Available, selected | Primary-light bg, primary border, checkmark | — |
| Locked (released) | 60% opacity, lock icon | Module description + "Souscrire" (admin) or "Contactez votre administrateur pour activer ce module." (employee) |
| Locked (coming soon) | 60% opacity, sparkle badge | "Bientot . {date}" |
| Error | Toast | "Une erreur est survenue." |
Responsive Behavior¶
- 360px (mobile): Locked modules in "Plus" bottom sheet with lock icon. Tap → upsell bottom sheet.
- 1024px+ (desktop): Locked modules in sidebar with 50% opacity and lock icon. Click → upsell popover from sidebar.
Interactions¶
- Admin taps locked module: Shows upsell card with "Souscrire ce module" button → navigates to billing (CP-009 subscription management).
- Employee taps locked module: Shows upsell card with "Contactez votre administrateur pour activer ce module." text (no button).
French micro-copy for upsell:
| Key | French Text |
|---|---|
modules.subscribe |
"Souscrire" |
modules.subscribe_cta |
"Souscrire ce module" |
modules.contact_admin |
"Contactez votre administrateur pour activer ce module." |
modules.coming_soon |
"Bientot . {date}" |
modules.error |
"Une erreur est survenue." |
Acceptance Criteria¶
AC-057: Navigation shows locked modules
Given my tenant has ABSMGT=ACTIVE and PAYROL=INACTIVE
When I view the sidebar navigation
Then ABSMGT appears with full color and is clickable
And PAYROL appears at 50% opacity with lock icon
AC-058: Upsell card for admin
Given I am DG and tap a locked module
When the upsell card appears
Then I see the module description, feature list, price
And a "Souscrire ce module" button
AC-059: Upsell card for employee
Given I am EMPLOYE and tap a locked module
When the upsell card appears
Then I see the module description
And the text "Contactez votre administrateur pour activer ce module."
And no subscribe button
OHADA & Regulatory Rules¶
- No direct OHADA regulatory requirements for module gating UI.
Standards & Conventions¶
docs/standards/react-typescript-guidelines.md— §Navigation integration, §Bottom sheet pattern, §Component props typingdocs/design/control-plane-design.md— §19.2 ModuleCard, §Module upsell
Testing Requirements¶
Unit Tests¶
- ModuleCard: renders correctly for each state (available, selected, locked, coming soon)
- ModuleUpsellSheet: shows subscribe button for admin role
- ModuleUpsellSheet: shows contact admin text for employee role (no button)
- Navigation: locked modules rendered with opacity + lock icon
Integration Tests¶
- Navigation fetches module catalog and renders active/locked modules correctly
- Upsell card content matches hardcoded module metadata
What QA Will Validate¶
- Locked modules visible in navigation with correct styling (50% opacity, lock icon)
- Upsell card shows correct content for admin vs employee
- Mobile: bottom sheet upsell behavior
- Desktop: popover upsell behavior
- Admin subscribe button navigates to billing (CP-009)
Out of Scope¶
- Module activation/deactivation UI — CP-009 (Billing/Subscription management)
- Per-module suspension display — V2
- Admin module override — CP-012
Definition of Done¶
- [ ] All backend endpoints implemented and returning correct responses — 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 (frontend reads tenant-scoped API)
- [ ] Offline write+sync works (Papillon) — module catalog cached for offline read
- [ ] No TypeScript
any— all types explicit - [ ] No Java raw types — N/A (frontend-only)
- [ ] API responses follow standard envelope format — N/A (consumer only)
- [ ] Audit trail entries created for every data mutation — N/A (no mutations)