feat(loyalty): cross-persona page alignment with shared components
Align loyalty pages across admin, merchant, and store personas so each sees the same page set scoped to their access level. Admin acts as a superset of merchant with "on behalf" capabilities. New pages: - Store: Staff PINs management (CRUD) - Merchant: Cards, Card Detail, Transactions, Staff PINs (CRUD), Settings (read-only) - Admin: Merchant Cards, Card Detail, Transactions, PINs (read-only) Architecture: - 4 shared Jinja2 partials (cards-list, card-detail, transactions, pins) - 4 shared JS factory modules parameterized by apiPrefix/scope - Persona templates are thin wrappers including shared partials - PinDetailResponse schema for cross-store PIN listings API: 17 new endpoints (11 merchant, 6 admin on-behalf) Tests: 38 new integration tests, arch-check green i18n: ~130 new keys across en/fr/de/lb Docs: pages-and-navigation.md with full page matrix Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -45,6 +45,22 @@ See [Data Model](data-model.md) for full entity relationships.
|
||||
- **MerchantLoyaltySettings** — Admin-controlled merchant settings
|
||||
- **AppleDeviceRegistration** — Apple Wallet push notification tokens
|
||||
|
||||
## Pages & Navigation
|
||||
|
||||
See [Pages & Navigation](pages-and-navigation.md) for the full cross-persona page matrix, URL patterns, sidebar navigation, and shared component architecture.
|
||||
|
||||
All personas share the same page set with scope-appropriate access:
|
||||
|
||||
| Page | Admin | Merchant | Store | Storefront |
|
||||
|---|:---:|:---:|:---:|:---:|
|
||||
| Program | Any merchant | Own | Own | Public |
|
||||
| Analytics | Any merchant | Multi-store | Store-scoped | -- |
|
||||
| Cards/Members | Any merchant | All stores | Store-scoped | Own card |
|
||||
| Transactions | Any merchant | Store filter | Store-scoped | Own history |
|
||||
| Staff PINs | Read-only | Full CRUD | Full CRUD | -- |
|
||||
| Settings | Edit | Read-only | -- | -- |
|
||||
| Terminal | -- | -- | POS | -- |
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Store Endpoints (`/api/v1/store/loyalty/`)
|
||||
@@ -53,15 +69,34 @@ See [Data Model](data-model.md) for full entity relationships.
|
||||
|--------|----------|-------------|
|
||||
| `GET` | `/program` | Get store's loyalty program |
|
||||
| `POST` | `/program` | Create loyalty program |
|
||||
| `PATCH` | `/program` | Update loyalty program |
|
||||
| `PUT` | `/program` | Update loyalty program |
|
||||
| `DELETE` | `/program` | Delete loyalty program |
|
||||
| `GET` | `/stats` | Get program statistics |
|
||||
| `GET` | `/stats/merchant` | Get merchant-wide statistics |
|
||||
| `GET` | `/cards` | List customer cards |
|
||||
| `POST` | `/cards/enroll` | Enroll customer in program |
|
||||
| `POST` | `/stamp` | Add stamp to card |
|
||||
| `POST` | `/stamp/redeem` | Redeem stamps for reward |
|
||||
| `POST` | `/points` | Earn points from purchase |
|
||||
| `POST` | `/stamp/void` | Void stamps |
|
||||
| `POST` | `/points/earn` | Earn points from purchase |
|
||||
| `POST` | `/points/redeem` | Redeem points for reward |
|
||||
| `*` | `/pins/*` | Staff PIN management |
|
||||
| `POST` | `/points/void` | Void points |
|
||||
| `POST` | `/cards/{id}/points/adjust` | Manual point adjustment (owner) |
|
||||
| `*` | `/pins/*` | Staff PIN management (list, create, update, delete, unlock) |
|
||||
|
||||
### Merchant Endpoints (`/api/v1/merchants/loyalty/`)
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `GET/POST/PATCH/DELETE` | `/program` | Program CRUD |
|
||||
| `GET` | `/stats` | Merchant-wide statistics |
|
||||
| `GET` | `/cards` | List cards across all stores |
|
||||
| `GET` | `/cards/{id}` | Card detail |
|
||||
| `GET` | `/cards/{id}/transactions` | Card transaction history |
|
||||
| `GET` | `/transactions` | Merchant-wide transactions |
|
||||
| `*` | `/pins/*` | Staff PIN management across stores |
|
||||
| `GET` | `/settings` | Loyalty settings (read-only) |
|
||||
| `GET` | `/locations` | Store list for filter dropdowns |
|
||||
|
||||
### Admin Endpoints (`/api/v1/admin/loyalty/`)
|
||||
|
||||
@@ -70,6 +105,15 @@ See [Data Model](data-model.md) for full entity relationships.
|
||||
| `GET` | `/programs` | List all loyalty programs |
|
||||
| `GET` | `/programs/{id}` | Get specific program |
|
||||
| `GET` | `/stats` | Platform-wide statistics |
|
||||
| `GET` | `/merchants/{mid}/cards` | Merchant's cards (on behalf) |
|
||||
| `GET` | `/merchants/{mid}/cards/{cid}` | Card detail (on behalf) |
|
||||
| `GET` | `/merchants/{mid}/transactions` | Merchant's transactions |
|
||||
| `GET` | `/merchants/{mid}/pins` | Merchant's PINs (read-only) |
|
||||
| `GET` | `/merchants/{mid}/locations` | Merchant's stores |
|
||||
| `GET` | `/merchants/{mid}/stats` | Merchant statistics |
|
||||
| `GET/PATCH` | `/merchants/{mid}/settings` | Merchant settings |
|
||||
| `GET` | `/wallet-status` | Wallet integration status |
|
||||
| `*` | `/debug/*` | Wallet debug endpoints (super admin) |
|
||||
|
||||
### Storefront Endpoints (`/api/v1/storefront/loyalty/`)
|
||||
|
||||
@@ -78,6 +122,7 @@ See [Data Model](data-model.md) for full entity relationships.
|
||||
| `GET` | `/card` | Get customer's loyalty card |
|
||||
| `GET` | `/transactions` | Transaction history |
|
||||
| `POST` | `/enroll` | Self-enrollment |
|
||||
| `GET` | `/program` | Public program info |
|
||||
|
||||
## Scheduled Tasks
|
||||
|
||||
@@ -105,6 +150,7 @@ Environment variables (prefix: `LOYALTY_`):
|
||||
|
||||
- [Data Model](data-model.md) — Entity relationships and database schema
|
||||
- [Business Logic](business-logic.md) — Anti-fraud system, wallet integration, enrollment flow
|
||||
- [Pages & Navigation](pages-and-navigation.md) — Cross-persona page matrix, URL patterns, shared components
|
||||
- [User Journeys](user-journeys.md) — Detailed user journey flows with dev/prod URLs
|
||||
- [Program Analysis](program-analysis.md) — Business analysis and platform vision
|
||||
- [UI Design](ui-design.md) — Admin and store interface mockups and implementation roadmap
|
||||
|
||||
191
app/modules/loyalty/docs/pages-and-navigation.md
Normal file
191
app/modules/loyalty/docs/pages-and-navigation.md
Normal file
@@ -0,0 +1,191 @@
|
||||
# Pages & Navigation
|
||||
|
||||
Cross-persona page architecture for the loyalty module. Every persona sees the same set of pages but scoped to their access level.
|
||||
|
||||
## Page Matrix
|
||||
|
||||
| Page | Admin (on behalf) | Merchant | Store | Storefront |
|
||||
|---|:---:|:---:|:---:|:---:|
|
||||
| Program | Any merchant | Own | Own (owner edits) | Public info |
|
||||
| Analytics | Any merchant | Own (multi-store) | Store-scoped | -- |
|
||||
| Cards/Members | Any merchant | Own (all stores) | Store-scoped | Own card |
|
||||
| Card Detail | Any card | Own merchant | Store-scoped | Own card |
|
||||
| Transactions | Any merchant | Own (store filter) | Store-scoped | Own history |
|
||||
| Staff PINs | Read-only | Full CRUD | Full CRUD | -- |
|
||||
| Settings | Edit | Read-only | -- | -- |
|
||||
| Terminal | -- | -- | POS interface | -- |
|
||||
| Wallet Debug | Super admin only | -- | -- | -- |
|
||||
| Enrollment | -- | -- | Staff-initiated | Self-service |
|
||||
|
||||
## URL Patterns
|
||||
|
||||
### Store Pages
|
||||
|
||||
```
|
||||
/store/{store_code}/loyalty/terminal # POS terminal
|
||||
/store/{store_code}/loyalty/cards # Customer cards list
|
||||
/store/{store_code}/loyalty/cards/{id} # Card detail
|
||||
/store/{store_code}/loyalty/pins # Staff PIN management
|
||||
/store/{store_code}/loyalty/program # Program view (owner: edit)
|
||||
/store/{store_code}/loyalty/analytics # Store analytics
|
||||
/store/{store_code}/loyalty/enroll # Staff-initiated enrollment
|
||||
```
|
||||
|
||||
### Merchant Pages
|
||||
|
||||
```
|
||||
/merchants/loyalty/program # Program view/edit
|
||||
/merchants/loyalty/cards # Cards across all stores
|
||||
/merchants/loyalty/cards/{id} # Card detail
|
||||
/merchants/loyalty/analytics # Merchant-wide analytics
|
||||
/merchants/loyalty/transactions # Transactions (store filter)
|
||||
/merchants/loyalty/pins # PINs across all stores (CRUD)
|
||||
/merchants/loyalty/settings # Loyalty settings (read-only)
|
||||
```
|
||||
|
||||
### Admin Pages (On Behalf)
|
||||
|
||||
```
|
||||
/admin/loyalty/programs # All programs list
|
||||
/admin/loyalty/analytics # Platform-wide analytics
|
||||
/admin/loyalty/wallet-debug # Wallet diagnostics (super admin)
|
||||
/admin/loyalty/merchants/{mid} # Merchant detail hub
|
||||
/admin/loyalty/merchants/{mid}/program # Program edit
|
||||
/admin/loyalty/merchants/{mid}/settings # Merchant settings (edit)
|
||||
/admin/loyalty/merchants/{mid}/cards # Merchant's cards
|
||||
/admin/loyalty/merchants/{mid}/cards/{cid} # Card detail
|
||||
/admin/loyalty/merchants/{mid}/transactions # Merchant's transactions
|
||||
/admin/loyalty/merchants/{mid}/pins # Merchant's PINs (read-only)
|
||||
```
|
||||
|
||||
### Storefront Pages
|
||||
|
||||
```
|
||||
/platforms/loyalty/storefront/{store_code}/loyalty/join # Self-enrollment
|
||||
/platforms/loyalty/storefront/{store_code}/loyalty/join/success # Enrollment success
|
||||
/platforms/loyalty/storefront/{store_code}/account/loyalty # Dashboard
|
||||
/platforms/loyalty/storefront/{store_code}/account/loyalty/history # History
|
||||
```
|
||||
|
||||
## Sidebar Navigation
|
||||
|
||||
### Store Menu
|
||||
|
||||
| Order | Item | Icon | Route |
|
||||
|---|---|---|---|
|
||||
| 10 | Terminal | `gift` | `/store/{code}/loyalty/terminal` |
|
||||
| 20 | Customer Cards | `identification` | `/store/{code}/loyalty/cards` |
|
||||
| 22 | Staff PINs | `key` | `/store/{code}/loyalty/pins` |
|
||||
| 25 | Program | `cog` | `/store/{code}/loyalty/program` |
|
||||
| 30 | Analytics | `chart-bar` | `/store/{code}/loyalty/analytics` |
|
||||
|
||||
All require `loyalty.view_programs` permission.
|
||||
|
||||
### Merchant Menu
|
||||
|
||||
| Order | Item | Icon | Route |
|
||||
|---|---|---|---|
|
||||
| 10 | Program | `gift` | `/merchants/loyalty/program` |
|
||||
| 15 | Customer Cards | `identification` | `/merchants/loyalty/cards` |
|
||||
| 20 | Analytics | `chart-bar` | `/merchants/loyalty/analytics` |
|
||||
| 25 | Transactions | `clock` | `/merchants/loyalty/transactions` |
|
||||
| 30 | Staff PINs | `key` | `/merchants/loyalty/pins` |
|
||||
| 35 | Settings | `cog` | `/merchants/loyalty/settings` |
|
||||
|
||||
### Admin Menu
|
||||
|
||||
| Order | Item | Icon | Route |
|
||||
|---|---|---|---|
|
||||
| 10 | Loyalty Programs | `gift` | `/admin/loyalty/programs` |
|
||||
| 20 | Loyalty Analytics | `chart-bar` | `/admin/loyalty/analytics` |
|
||||
| 30 | Wallet Debug | `beaker` | `/admin/loyalty/wallet-debug` (super admin) |
|
||||
|
||||
Admin sub-pages (cards, transactions, PINs) are accessed via the merchant detail hub — no separate menu items.
|
||||
|
||||
## API Endpoints by Persona
|
||||
|
||||
### Store API (`/api/v1/store/loyalty/`)
|
||||
|
||||
| Group | Endpoints |
|
||||
|---|---|
|
||||
| Program | `GET/POST/PUT/DELETE /program`, `GET /stats`, `GET /stats/merchant` |
|
||||
| Cards | `GET /cards`, `GET/POST /cards/lookup`, `GET /cards/{id}`, `POST /cards/enroll`, `GET /cards/{id}/transactions`, `GET /transactions` |
|
||||
| Stamps | `POST /stamp`, `POST /stamp/redeem`, `POST /stamp/void` |
|
||||
| Points | `POST /points/earn`, `POST /points/redeem`, `POST /points/void`, `POST /cards/{id}/points/adjust` |
|
||||
| PINs | `GET /pins`, `POST /pins`, `PATCH /pins/{id}`, `DELETE /pins/{id}`, `POST /pins/{id}/unlock` |
|
||||
|
||||
### Merchant API (`/api/v1/merchants/loyalty/`)
|
||||
|
||||
| Group | Endpoints |
|
||||
|---|---|
|
||||
| Program | `GET/POST/PATCH/DELETE /program`, `GET /stats` |
|
||||
| Cards | `GET /cards`, `GET /cards/{id}`, `GET /cards/{id}/transactions` |
|
||||
| Transactions | `GET /transactions` |
|
||||
| PINs | `GET /pins`, `POST /pins`, `PATCH /pins/{id}`, `DELETE /pins/{id}`, `POST /pins/{id}/unlock` |
|
||||
| Settings | `GET /settings` |
|
||||
| Locations | `GET /locations` |
|
||||
|
||||
### Admin API (`/api/v1/admin/loyalty/`)
|
||||
|
||||
| Group | Endpoints |
|
||||
|---|---|
|
||||
| Programs | `GET /programs`, `GET /programs/{id}`, `GET /programs/{id}/stats`, `POST /merchants/{mid}/program`, `PATCH /programs/{id}`, `DELETE /programs/{id}`, `POST /programs/{id}/activate`, `POST /programs/{id}/deactivate` |
|
||||
| Merchant Mgmt | `GET /merchants/{mid}/stats`, `GET /merchants/{mid}/settings`, `PATCH /merchants/{mid}/settings` |
|
||||
| On-Behalf | `GET /merchants/{mid}/cards`, `GET /merchants/{mid}/cards/{cid}`, `GET /merchants/{mid}/cards/{cid}/transactions`, `GET /merchants/{mid}/transactions`, `GET /merchants/{mid}/pins` (read-only), `GET /merchants/{mid}/locations` |
|
||||
| Platform | `GET /stats`, `GET /wallet-status` |
|
||||
| Wallet Debug | `GET /debug/config`, `GET /debug/classes`, `POST /debug/classes/{pid}/create`, `GET /debug/cards/{cid}`, `POST /debug/cards/{cid}/generate-url`, `GET /debug/recent-enrollments` (super admin) |
|
||||
|
||||
## Architecture: Shared Components
|
||||
|
||||
All persona pages reuse the same shared partials and JS modules to ensure consistent behavior:
|
||||
|
||||
### Shared Templates (`templates/loyalty/shared/`)
|
||||
|
||||
| Partial | Variables | Used By |
|
||||
|---|---|---|
|
||||
| `cards-list.html` | `cards_api_prefix`, `cards_base_url`, `show_store_filter`, `show_enroll_button` | Store, Merchant, Admin |
|
||||
| `card-detail-view.html` | `card_detail_api_prefix`, `card_detail_back_url` | Store, Merchant, Admin |
|
||||
| `transactions-list.html` | `transactions_api_prefix`, `show_store_filter` | Merchant, Admin |
|
||||
| `pins-list.html` | `pins_api_prefix`, `show_store_filter`, `show_crud` | Store, Merchant, Admin |
|
||||
| `analytics-stats.html` | `show_programs_card`, `show_locations`, `show_merchants_metric` | Store, Merchant, Admin |
|
||||
| `program-form.html` | `show_delete`, `show_status` | Store, Merchant, Admin |
|
||||
| `program-view.html` | -- | Store, Merchant, Admin |
|
||||
|
||||
### Shared JS Modules (`static/shared/js/`)
|
||||
|
||||
| Module | Factory Function | Config |
|
||||
|---|---|---|
|
||||
| `loyalty-cards-list.js` | `loyaltyCardsList(config)` | `apiPrefix`, `baseUrl`, `showStoreFilter`, `currentPage` |
|
||||
| `loyalty-card-detail-view.js` | `loyaltyCardDetailView(config)` | `apiPrefix`, `backUrl`, `currentPage` |
|
||||
| `loyalty-transactions-list.js` | `loyaltyTransactionsList(config)` | `apiPrefix`, `showStoreFilter`, `currentPage` |
|
||||
| `loyalty-pins-list.js` | `loyaltyPinsList(config)` | `apiPrefix`, `showStoreFilter`, `showCrud`, `currentPage` |
|
||||
|
||||
Each persona has thin wrapper JS files that call the shared factory with persona-specific config (API prefix, base URL, currentPage).
|
||||
|
||||
## Scoping Rules
|
||||
|
||||
| Concern | Admin | Merchant | Store |
|
||||
|---|---|---|---|
|
||||
| **Data scope** | Any merchant (via `merchant_id` path param) | Own merchant (from auth token) | Own store (from auth token), cards visible merchant-wide |
|
||||
| **PINs** | Read-only across merchant | CRUD across all stores | CRUD for own store only |
|
||||
| **Settings** | Full edit | Read-only view | Not visible |
|
||||
| **Card operations** | View only | View only | Stamp/points/enroll/void |
|
||||
| **Program edit** | Any merchant | Own program | Owner role only |
|
||||
|
||||
## Schemas
|
||||
|
||||
### PinDetailResponse (new)
|
||||
|
||||
Extends `PinResponse` with store context for cross-store listings:
|
||||
|
||||
```python
|
||||
class PinDetailResponse(PinResponse):
|
||||
store_id: int | None
|
||||
store_name: str | None
|
||||
|
||||
class PinDetailListResponse(BaseModel):
|
||||
pins: list[PinDetailResponse]
|
||||
total: int
|
||||
```
|
||||
|
||||
Used by merchant `GET /pins` and admin `GET /merchants/{mid}/pins` endpoints. Store endpoints continue using `PinResponse` / `PinListResponse` (single-store context).
|
||||
Reference in New Issue
Block a user