# 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).