Admin page /admin/jersey-orders — review and reconcile #244

Open
opened 2026-04-10 21:53:23 +00:00 by forgejo_admin · 1 comment
Contributor

Type

Feature

Lineage

Depends on basketball-api#432 (GET admin endpoint). Part of System B production rollout. Architecture in arch-jersey-intake.

Repo

forgejo_admin/westside-landing

User Story

As Marcus (admin)
I want an admin page listing public jersey submissions with filters
So that I can review new submissions, reconcile them against existing parents/players, and track fulfillment status

Context

Admin-facing counterpart to (app)/jersey-public. Lives inside (app)/admin/ to inherit the existing admin-role guard in (app)/+layout.svelte (if (path.startsWith('/admin') && !hasRole('admin')) → redirect to role dashboard).

Read-only in this ticket. Write actions (status updates, reconciliation) are a follow-up once Marcus confirms the read-only view is the right shape.

File Targets

Files to create:

  • src/routes/(app)/admin/jersey-orders/+page.svelte — list view
  • src/routes/(app)/admin/jersey-orders/+page.js — client loader that calls basketball-api with the admin's JWT via $lib/keycloak.js::getToken()

Files the agent should NOT touch:

  • Other admin routes
  • src/routes/(app)/jersey-public/* — that's T1 (issue #243), hands off
  • src/routes/(app)/+layout.svelte — auth guard lives here, read-only reference

Acceptance Criteria

  • Visiting /admin/jersey-orders as an unauthenticated user redirects to /signin (existing layout guard)
  • Visiting as an authenticated non-admin redirects to the role dashboard (existing layout guard)
  • Visiting as admin renders a list ordered by most recent first
  • Status filter (pending / reviewed / fulfilled / cancelled / all)
  • Date range filter (from / to)
  • Each row shows: player_name, email, team, kq, tier, top_size, short_size, preferred numbers, created_at, status
  • Clicking a row expands to show: submitter_keycloak_sub, linked_parent_id, linked_player_id, reviewed_at, reviewed_by
  • Empty state message
  • Pagination (next/prev) using offset + limit
  • Loading state during fetch
  • Error state if basketball-api returns non-200

Test Expectations

  • Component test: renders list of mocked submissions
  • Component test: status filter updates request
  • Component test: empty state
  • Run command: westside-landing test runner

Constraints

  • Reuse existing admin guard (already wired in (app)/+layout.svelte) — do NOT build new auth
  • Use Svelte 5 $state runes
  • No Tailwind
  • Pass JWT to basketball-api via Authorization: Bearer <token> header; read token from $lib/keycloak.js
  • Mobile-usable but desktop-primary (Marcus uses his laptop for admin work)
  • No mutation UI in this ticket

Checklist

  • PR opened against westside-landing main
  • Depends on #432 merged first
  • Tests pass
  • Auth chain documented in PR body per feedback_funnel_requires_auth.md
  • westside-basketball — project
  • story:WS-S31 — admin public jersey intake link
  • arch-jersey-intake — architecture doc
  • Depends on: basketball-api#432
  • feedback_funnel_requires_auth.md
### Type Feature ### Lineage Depends on `basketball-api#432` (GET admin endpoint). Part of System B production rollout. Architecture in `arch-jersey-intake`. ### Repo `forgejo_admin/westside-landing` ### User Story As Marcus (admin) I want an admin page listing public jersey submissions with filters So that I can review new submissions, reconcile them against existing parents/players, and track fulfillment status ### Context Admin-facing counterpart to `(app)/jersey-public`. Lives inside `(app)/admin/` to inherit the existing admin-role guard in `(app)/+layout.svelte` (`if (path.startsWith('/admin') && !hasRole('admin')) → redirect to role dashboard`). Read-only in this ticket. Write actions (status updates, reconciliation) are a follow-up once Marcus confirms the read-only view is the right shape. ### File Targets Files to create: - `src/routes/(app)/admin/jersey-orders/+page.svelte` — list view - `src/routes/(app)/admin/jersey-orders/+page.js` — client loader that calls basketball-api with the admin's JWT via `$lib/keycloak.js::getToken()` Files the agent should NOT touch: - Other admin routes - `src/routes/(app)/jersey-public/*` — that's T1 (issue #243), hands off - `src/routes/(app)/+layout.svelte` — auth guard lives here, read-only reference ### Acceptance Criteria - [ ] Visiting `/admin/jersey-orders` as an unauthenticated user redirects to `/signin` (existing layout guard) - [ ] Visiting as an authenticated non-admin redirects to the role dashboard (existing layout guard) - [ ] Visiting as admin renders a list ordered by most recent first - [ ] Status filter (pending / reviewed / fulfilled / cancelled / all) - [ ] Date range filter (from / to) - [ ] Each row shows: player_name, email, team, kq, tier, top_size, short_size, preferred numbers, created_at, status - [ ] Clicking a row expands to show: submitter_keycloak_sub, linked_parent_id, linked_player_id, reviewed_at, reviewed_by - [ ] Empty state message - [ ] Pagination (next/prev) using offset + limit - [ ] Loading state during fetch - [ ] Error state if basketball-api returns non-200 ### Test Expectations - [ ] Component test: renders list of mocked submissions - [ ] Component test: status filter updates request - [ ] Component test: empty state - [ ] Run command: westside-landing test runner ### Constraints - Reuse existing admin guard (already wired in `(app)/+layout.svelte`) — do NOT build new auth - Use Svelte 5 `$state` runes - No Tailwind - Pass JWT to basketball-api via `Authorization: Bearer <token>` header; read token from `$lib/keycloak.js` - Mobile-usable but desktop-primary (Marcus uses his laptop for admin work) - No mutation UI in this ticket ### Checklist - [ ] PR opened against `westside-landing` main - [ ] Depends on #432 merged first - [ ] Tests pass - [ ] Auth chain documented in PR body per `feedback_funnel_requires_auth.md` ### Related - `westside-basketball` — project - `story:WS-S31` — admin public jersey intake link - `arch-jersey-intake` — architecture doc - Depends on: `basketball-api#432` - `feedback_funnel_requires_auth.md`
Author
Contributor

Scope Review: APPROVED

Review note: review-951-2026-04-10

Scope is solid and fits a single agent pass (<5 min). All file targets verified against the repo on Forgejo:

  • src/routes/(app)/+layout.svelte contains the exact guard if (authenticated && path.startsWith('/admin') && !hasRole('admin')) - no modification needed.
  • src/lib/keycloak.js exports getToken (verified).
  • src/routes/(app)/admin/jersey-orders/ does not exist yet (correct - this ticket creates it). Sibling admin routes (commerce/players/schedule/teams/users) confirmed present.
  • Dependency basketball-api#432 (T5) verified open on Forgejo.

Traceability: story:WS-S31 verified in project-westside-basketball stories-admin section. arch:jersey-intake label present but backing note missing.

One tracking gap (not a code blocker):

  • [SCOPE] Create architecture note arch-jersey-intake in pal-e-docs. Referenced by the ticket and the board label, but the note does not exist. Create in parallel; does not block dev.

No [BODY] or [LABEL] fixes required. Ready to advance to next_up once T5 merges.

## Scope Review: APPROVED Review note: `review-951-2026-04-10` Scope is solid and fits a single agent pass (<5 min). All file targets verified against the repo on Forgejo: - `src/routes/(app)/+layout.svelte` contains the exact guard `if (authenticated && path.startsWith('/admin') && !hasRole('admin'))` - no modification needed. - `src/lib/keycloak.js` exports `getToken` (verified). - `src/routes/(app)/admin/jersey-orders/` does not exist yet (correct - this ticket creates it). Sibling admin routes (commerce/players/schedule/teams/users) confirmed present. - Dependency `basketball-api#432` (T5) verified open on Forgejo. Traceability: `story:WS-S31` verified in `project-westside-basketball` stories-admin section. `arch:jersey-intake` label present but backing note missing. One tracking gap (not a code blocker): - `[SCOPE]` Create architecture note `arch-jersey-intake` in pal-e-docs. Referenced by the ticket and the board label, but the note does not exist. Create in parallel; does not block dev. No `[BODY]` or `[LABEL]` fixes required. Ready to advance to `next_up` once T5 merges.
Sign in to join this conversation.
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
ldraney/westside-app#244
No description provided.