Notifications, help, and settings (Vendor)
Executive summary
Section titled “Executive summary”The notifications inbox uses GET /auth/vendor/notifications (JWT only; no notification module on the API). Store-originated user_notifications rows include status: 0 = read, 1 = unread; POST /auth/vendor/notifications/read persists read state. Broadcast rows have status: null; the client uses sessionStorage “seen” IDs for unread styling on those only. Email/push preference toggles use GET/PATCH /auth/vendor/settings/notifications (JWT only; no settings module—same pattern as admin). Help is static. Profile uses /dashboard/profile?tab= (profile | notifications | password) for account info, prefs, and change password; PATCH /auth/vendor/password returns 400 with auth-004 when the current password is wrong (not 401, so the client does not force logout). Settings uses path segments under /dashboard/settings/* (e.g. /dashboard/settings/general, …/devices) with a shared DashboardTabbedSectionLayout; legacy ?tab= on /dashboard/settings redirects to the matching segment.
Goals and non-goals
Section titled “Goals and non-goals”| Goals | Non-goals |
|---|---|
| Clarify real API vs static help | Mobile push guarantees |
| Explain seen-state behavior (sessionStorage) | Cross-device read sync (not implied) |
Current platform behavior
Section titled “Current platform behavior”- Notifications — merged server list with client seen IDs (Notifications section). The list API is available to any authenticated vendor; UI may still gate
/dashboard/notificationsby modulenotification. - Notification prefs (
PATCH /auth/vendor/settings/notifications) — Saved per logged-in user:vendors.indochina_notification_prefsfor owners andvendor_employees.indochina_notification_prefsfor staff (JSON slice of the samepush*/email*keys as before). Reads fall back tostores.dashboard_prefsfor the JWT’s active store when the user column is still empty (legacy until they save once). Writers that gate in-app inserts use the owner column with the same store fallback where applicable; per-actor POSnew_orderuses owner vs employee columns as documented below. - Help — static content.
- Settings — URL-driven sections (
NavLinktabs +Outlet);DashboardTabbedSectionLayout(@indochina/shared) so navigation is a horizontal bar belowlgand a left rail on desktop.
Sequence: happy path
Section titled “Sequence: happy path”- User opens Notifications → marks items → seen state in sessionStorage for session.
- Help for FAQs; Settings for prefs.
Security and operations
Section titled “Security and operations”- Notifications may contain order PII—lock screens on shared devices.
Test scenarios (UAT / QA)
Section titled “Test scenarios (UAT / QA)”| ID | Scenario | Expected |
|---|---|---|
| NH1 | Fetch notifications | API items render |
| NH2 | Mark seen | Styling updates; survives navigation until storage clear |
| NH3 | Settings tabs | ?tab= updates and persists view state |
| NH4 | Delete selected (user rows) | Rows removed after success; broadcast rows have no delete checkbox |
Routes
Section titled “Routes”/dashboard/notifications/dashboard/help/dashboard/profile— user profile (separate from settings tabs)/dashboard/settings— tabs via?tab=query (general,notifications,devices,team; navigation updates the URL)
Notifications
Section titled “Notifications”Access (UI): /dashboard/notifications may require module notification in navigation; TopBar can still fetch the list when the user is authenticated.
API: GET /auth/vendor/notifications — fetchVendorNotifications in vendor-notifications.api.ts (Laravel VendorController@get_notifications parity per file comment).
Bulk delete (store / user rows only): POST /auth/vendor/notifications/delete with body { "ids": string[] } — deletes up to 100 IDs scoped to the authenticated vendor’s user_notifications rows. Broadcast items from the merged list are not deletable via this endpoint. The inbox UI paginates the filtered list client-side and calls deleteVendorNotifications after multi-select.
Mark read (user_notifications only): POST /auth/vendor/notifications/read with { "ids": string[] } — sets status to 0 for up to 100 rows. The vendor web client calls markVendorNotificationsRead when marking store rows read; broadcast rows still rely on sessionStorage seen IDs (vendor-notification-seen-ids).
The page merges server status for user rows with sessionStorage “seen” IDs for broadcast unread styling. Category/icon helpers live in lib/vendor-notification-display.ts. Preference toggles do not hide existing inbox rows: when e.g. New orders is turned off, the API stops creating new user_notifications of that type, but rows already stored still appear in GET /auth/vendor/notifications until they age out of the server window or the user deletes them. The list scopes user_notifications by vendor_id, time window, and recipient (recipient_audience / recipient_user_id vs JWT sub) so targeted rows do not leak across staff accounts.
In-app notification types (user_notifications)
Section titled “In-app notification types (user_notifications)”Rows are created by Nest writers (e.g. VendorStoreInAppNotificationsService, VendorOrderInAppNotificationsService, PosService for new_order, AdminOpsNotificationsService for new_store) when the effective toggles allow it (owner vendors column with store dashboard_prefs fallback when the column is unset). Each in-app row stores data as JSON with at least title, description, type, and source (vendor or pos).
Recipient columns (user_notifications): recipient_audience (string, e.g. vendor_account, vendor_employee) and optional recipient_user_id make the intended audience explicit. For vendor JWTs, recipient_user_id matches sub on the token (vendors.id for owners, vendor_employees.id for staff). vendor_account with a null user id means “everyone on that vendor JWT”; a non-null recipient_user_id limits the row to that login only. The merged GET /auth/vendor/notifications list applies this filter so targeted rows do not leak across staff accounts. Legacy rows with a null audience behave as before (visible to any vendor login for that vendor_id).
new_order (POS): Owner prefs (vendors.notificationPrefs with store dashboard_prefs fallback) drive the vendor_account row via pushOrders / pushOrdersOwner (resolvePushOrdersForOwner). When a vendor employee places the order, the vendor_employee row is gated separately by that employee’s prefs + store fallback (pushOrdersEmployee, resolvePushOrdersForEmployee). A vendor owner actor only gets the vendor_account row when owner prefs allow. An employee actor can get zero, one, or two rows depending on ownerWants and employeeWants independently.
data.type | Preference key (must be true to emit) | When it fires (summary) |
|---|---|---|
low_stock | pushLowStock | Aggregated item stock crosses into low stock (was not low, then is). |
receivable_due | pushReceivableDue | POS flow records an outstanding receivable with a due reminder (e.g. after placing an order with BNPL terms). |
new_discount | pushNewDiscount | Vendor creates a coupon for the store. |
discount_duplicated | pushNewDiscount | Vendor duplicates an existing coupon (same toggle as new discount). |
new_customer | pushNewCustomer | Vendor creates a store customer record. |
return_request | pushReturnRequest | A return/refund request is successfully submitted for an order (e.g. refundRequest path). |
Email for the same domains uses parallel keys where implemented (emailReturnRequest, emailReceivableDue, emailNewDiscount, emailNewCustomer, etc.); in-app rows above only honor the push* flags.
Vendor UI filters: the notifications page groups items into existing tabs — roughly low_stock / new_discount / discount_duplicated → System, receivable_due → Payments, return_request → Orders, new_customer → Customers.
/dashboard/help is a static marketing-style help hub (search box and topic cards). There is no CMS or API fetch in the page component.
Settings
Section titled “Settings”Service: apps/vendor-web/src/services/vendor-settings.api.ts.
| Tab / area | API (representative) |
|---|---|
| Context bootstrap | GET /vendor/settings/context |
| General | GET/PATCH /vendor/settings/general |
Profile (any authenticated vendor; no settings module) | GET/PATCH /auth/vendor/profile |
| Notification prefs | GET/PATCH /auth/vendor/settings/notifications (JWT only; same pattern as admin) |
| Billing | GET /vendor/settings/billing (read-focused in UI) |
| Devices / terminals | GET/PATCH /vendor/settings/devices, GET .../devices/:deviceId (payment terminal detail), GET .../devices/discover, POST .../devices/test-connect, POST .../devices/payment-terminal/close-batch, terminal API key regenerate |
The legacy Team tab inside /dashboard/settings was retired — staff accounts and invitations now live under Employees, Managers, and Roles in the dashboard sidebar. The backend endpoints GET /vendor/settings/team and POST /vendor/settings/team/invites still exist for compatibility but are no longer surfaced by the vendor web UI.
Access: /dashboard/settings maps to module settings.
Notification prefs (vendor JWT): GET/PATCH /auth/vendor/settings/notifications merges vendors.notificationPrefs or vendor_employees.notificationPrefs with store dashboard_prefs when the user column is empty (same keys as StoreNotificationPrefs). In-app insert paths for New store, Order completed, Low stock, Return requests, Receivable due, New discount, New customer all gate on owner prefs (resolveVendorOwnerNotificationPrefsForStore), like the owner-targeted new_order row. Vendor employees do not use New store added or New customer in-app (no vendor_employee rows for those types; settings API returns those flags as off for employees and the UI hides the toggles).
Language and theme, if present, follow i18next and any global theme toggles elsewhere in the app; the settings tabs above reflect what is wired to /vendor/settings/* in code.
Related
Section titled “Related”- Sign-in and account — login and token bootstrap.
- Vendor panel overview — modules
notificationandsettings.