feat(admin): consolidate Users + Memberships into one filterable users page (BUNYIP-410) #407

Merged
longjacksonle merged 1 commit from feat/BUNYIP-410-merge-users-memberships into main 2026-07-29 20:19:33 +02:00

What

Merges the near-duplicate admin Users and Memberships pages into a single filterable users list, and removes the standalone Memberships page.

Users list

  • Each row now shows the membership-tier badge (Lifetime / Free / Early Adopter / Standard) alongside the existing verified/unverified indicator - the tier the Memberships page surfaced is now visible at a glance.
  • A filter bar above the list filters by search, verification status, and tier. The selects auto-submit on change; filters are preserved across the pager and the Active/Suspended tabs.

Server-side filtering (correct pagination)

UserRepository::list_paginated gains tier (matched via COALESCE(subscription_tier,'standard') so NULL-tier rows count as standard) and verified filters, threaded through /v1/admin/users and the bunyip-web client. Both default to unfiltered, preserving pre-410 behaviour.

Memberships page

  • /admin/memberships redirects to the users list, preserving a tier filter as ?tier= (old links/bookmarks still land somewhere sensible). The nav entry is removed.
  • No capability lost: grant/revoke lifetime already live on the user-detail page (BUNYIP-405), and the admin-override grant/revoke routes (/admin/memberships/{id}/grant|revoke) are kept, now redirecting to the user detail. The dead members-by-tier list fetch + its AdminMembership DTO are removed.

Acceptance criteria

  • Users list shows verified status and membership tier.
  • Filter bar filters by at least verified status and tier (also search).
  • Memberships route redirected to the filtered users view.
  • No admin capability lost (grant/revoke lifetime on the detail; override grant/revoke routes preserved).
  • Tests covering the filter behavior and the redirect.

Verification

  • just check-container green: fmt + clippy -D warnings + all test binaries (129 web tests). New tests: tier badge on the row, tier-label mapping, tri-state verified-filter parsing, and the /admin/memberships -> filtered-users redirect (tier preservation + junk-tier fallback).
  • Filter SQL (COALESCE for NULL tiers, combined tier+verified) verified against a throwaway Postgres.
  • Headless render of the merged list attached below.

🤖 Generated with Claude Code

## What Merges the near-duplicate admin **Users** and **Memberships** pages into a single filterable users list, and removes the standalone Memberships page. ## Users list - Each row now shows the **membership-tier badge** (Lifetime / Free / Early Adopter / Standard) alongside the existing **verified/unverified** indicator - the tier the Memberships page surfaced is now visible at a glance. - A **filter bar** above the list filters by search, verification status, and tier. The selects auto-submit on change; filters are preserved across the pager and the Active/Suspended tabs. ## Server-side filtering (correct pagination) `UserRepository::list_paginated` gains `tier` (matched via `COALESCE(subscription_tier,'standard')` so NULL-tier rows count as standard) and `verified` filters, threaded through `/v1/admin/users` and the bunyip-web client. Both default to unfiltered, preserving pre-410 behaviour. ## Memberships page - `/admin/memberships` **redirects** to the users list, preserving a tier filter as `?tier=` (old links/bookmarks still land somewhere sensible). The nav entry is removed. - **No capability lost:** grant/revoke lifetime already live on the user-detail page (BUNYIP-405), and the admin-override grant/revoke routes (`/admin/memberships/{id}/grant|revoke`) are kept, now redirecting to the user detail. The dead members-by-tier list fetch + its `AdminMembership` DTO are removed. ## Acceptance criteria - [x] Users list shows verified status and membership tier. - [x] Filter bar filters by at least verified status and tier (also search). - [x] Memberships route redirected to the filtered users view. - [x] No admin capability lost (grant/revoke lifetime on the detail; override grant/revoke routes preserved). - [x] Tests covering the filter behavior and the redirect. ## Verification - `just check-container` green: fmt + clippy `-D warnings` + all test binaries (129 web tests). New tests: tier badge on the row, tier-label mapping, tri-state verified-filter parsing, and the `/admin/memberships` -> filtered-users redirect (tier preservation + junk-tier fallback). - Filter SQL (COALESCE for NULL tiers, combined tier+verified) verified against a throwaway Postgres. - Headless render of the merged list attached below. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(admin): consolidate Users + Memberships into one filterable users page (BUNYIP-410)
All checks were successful
E2E / Playwright against deployment (pull_request) Successful in 1m18s
Check / fmt + clippy + build + tests (pull_request) Successful in 12m54s
Create release / Create release from merged PR (pull_request) Has been skipped
0e1a6e2925
The admin panel had near-duplicate Users and Memberships pages. Merged them into a single users list that shows verification status and membership tier, with a filter bar, and removed the standalone Memberships page.

Users list:
- Each row now carries the membership-tier badge (Lifetime / Free / Early Adopter / Standard) alongside the existing verified/unverified indicator, so the tier the Memberships page showed is visible here at a glance.
- A filter bar above the list filters by search, verification status, and tier. The verified/tier selects auto-submit on change; filters are preserved across the pager and the Active/Suspended tabs.

Filtering is server-side for correct pagination: `UserRepository::list_paginated` gains `tier` (matched via `COALESCE(subscription_tier,'standard')` so NULL-tier rows count as standard) and `verified` filters, threaded through the `/v1/admin/users` query and the bunyip-web client. Both default to unfiltered, preserving pre-410 behaviour.

Memberships page:
- `/admin/memberships` now redirects to the users list, preserving a tier filter as `?tier=`, so old links/bookmarks land on the filtered view. The nav entry is removed.
- No capability is lost: the grant/revoke lifetime actions already live on the user-detail page (BUNYIP-405), and the admin-override grant/revoke routes (`/admin/memberships/{id}/grant|revoke`) are kept, now redirecting to the user detail. The dead members-by-tier list fetch (`admin_api::memberships`) and its `AdminMembership` DTO are removed.

Verified: `just check-container` green (fmt + clippy -D warnings + all test binaries, 129 web tests). New tests cover the tier badge on the row, the tier-label mapping, the tri-state verified-filter parsing, and the `/admin/memberships` -> filtered-users redirect (incl. tier preservation and junk-tier fallback). The tier/verified filter SQL (incl. COALESCE for NULL tiers and combined filters) was verified against a throwaway Postgres.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L5dcYueNHByRnWJDYoDX1W
longjacksonle scheduled this pull request to auto merge when all checks succeed 2026-07-29 20:10:18 +02:00
longjacksonle deleted branch feat/BUNYIP-410-merge-users-memberships 2026-07-29 20:19:33 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/bunyip!407
No description provided.