Files

92 lines
3.9 KiB
Markdown
Raw Permalink Normal View History

# 14 — Support staff UI
**Agent:** 14/20
**Status:** Implemented
**Coordinates with:** `06-staff-roles` (middleware + `staff_role`), `11-support-design` / `12-support-backend` (claim/scope/agents), `07-admin-shell`, `10-admin-orgs-ui` (grant role)
---
## PROBLEM
Platform support work lived behind full `is_platform_admin`. Staff need an inbox with claim/unassign, assignment, and filters — without billing/settings access. Platform admins must be able to grant `support_staff` from the orgs/users UI.
---
## WHAT SHIPPED
### Access model
| Actor | Gate | Surfaces |
|-------|------|----------|
| Full admin (`admin` / `developer` / legacy platform admin) | `RequirePlatformAdmin` / `staff.full_admin` | Full `/admin/*` |
| `support_staff` | `RequireSupportDesk` / `staff.support_desk` | `/admin/support/**` (+ Overview in nav) |
| Customers | company session | `/support/**` (unchanged) |
- API: `/api/admin/support/*` mounted under `RequireSupportDesk` (not full admin).
- Web: `requireSupportDesk()` on support pages; `requirePlatformAdmin()` elsewhere.
- `AdminNav` hides billing/settings/users/analytics for support-only staff.
- Layout shows admin shell when `staff_access.support_desk` or `full_admin`.
### Queue + ticket UI (`/admin/support`)
- **Scopes:** Inbox · Mine · Unassigned · All (All = full admin only). Persisted in `?scope=`.
- **Status chips** + search (subject / email / company).
- **Claim** on unassigned open/pending rows and on ticket detail.
- **Unassign / release** when assignee is self (or force for full admin).
- **Assign to staff** (full admin): agent picker from `GET /api/admin/support/agents`.
- Assignee column shows email / “You” / Unassigned.
- Visual language matches reworked admin shell (`PageShell`, `Card`, `TableShell`, filter chips).
### Role grant coordination (orgs UI)
Platform admins grant roles via:
- `PATCH /api/admin/users/{id}/staff-role` body `{ "staff_role": "support_staff" | "admin" | "developer" | null }`
- Admin Users UI (`apps/web/src/routes/admin/users/+page.svelte` + `$lib/admin-orgs.ts`) — role dialog owned with agent 10.
- Optional alias: `PUT /api/admin/support/agents/{id}` `{ "is_support_agent": true }` → sets `staff_role=support_staff`.
`/api/auth/me` returns `staff_access` + `staff_capabilities` for client gating.
---
## FILES (primary)
| Area | Path |
|------|------|
| Client API | `apps/web/src/lib/support/admin-api.ts` |
| Gates | `apps/web/src/lib/admin-gate.ts` (`requireSupportDesk`) |
| Types / session | `apps/web/src/lib/types.ts`, `auth-session.svelte.ts` |
| Nav / layout | `AdminNav.svelte`, `routes/+layout.svelte` |
| Queue / detail | `routes/admin/support/+page.svelte`, `[id]/+page.svelte` |
| Handlers | `apps/api/internal/httpapi/support_handlers.go` (scope, claim, release, agents) |
| Desk domain | `apps/api/internal/support/desk.go`, `agents.go` |
---
## VERIFICATION
```text
cd apps/api && go test ./internal/support/ ./internal/auth/ ./internal/httpapi/ -count=1
```
Manual:
1. As platform admin: Users → set a user `staff_role=support_staff`.
2. Sign in as that user → admin shell shows Support (not billing).
3. Open `/admin/support?scope=unassigned` → Claim → appears under Mine.
4. Unassign → returns to Unassigned.
5. As full admin: open ticket → Assign to staff → Apply.
6. Confirm `/admin/billing` returns 403 for support_staff (API + UI).
---
## ASSUMPTIONS
- ASSUMPTION: Agent 6 `ResolveStaffAccess``support_staff` never gets `full_admin` even if `is_platform_admin` column is true.
- ASSUMPTION: Least-privilege visibility for agents (inbox / claim) is enforced server-side in list/get/update (agent 12).
- CSAT ratings UI is owned by sibling agents; this doc does not cover rating widgets.
## ROLLBACK
Revert support route UI/gate/nav changes and remount support APIs under `RequirePlatformAdmin` only if needed. Staff role column (`029_staff_roles`) is additive — leave in place.