Skip to content

feat(admin-web): ship operator dashboard for phase 5#15

Merged
imKXNNY merged 2 commits into
mainfrom
feat/tec-40-admin-dashboard
Apr 13, 2026
Merged

feat(admin-web): ship operator dashboard for phase 5#15
imKXNNY merged 2 commits into
mainfrom
feat/tec-40-admin-dashboard

Conversation

@imKXNNY

@imKXNNY imKXNNY commented Apr 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • replace the static admin placeholder with a working operator dashboard in apps/admin-web
  • add a session bootstrap seam so the dashboard can hydrate actor/permissions context safely
  • wire dashboard data/interaction surfaces for disputes, payouts, and invoices within the Phase 5 scope

Validation

  • 36 tests passing (carried from TEC-40 validation run)

Traceability

  • source commit: 0b63525
  • source task: TEC-40

Summary by CodeRabbit

  • New Features

    • Operator dashboard replaced static landing with an async operator dashboard showing provider verification and dispute queues, session bootstrap details, and conditional dispute actions.
    • Form-based review and dispute transition actions to submit decisions from the UI.
    • Visual helpers and summary badges for queue status and counts.
  • Tests

    • Added tests for operator session resolution, queue presenters, and queue action behaviors.

@coderabbitai

coderabbitai Bot commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9cf09ffa-e74c-4d76-9092-d3058ddc9d15

📥 Commits

Reviewing files that changed from the base of the PR and between 0b63525 and c2aa2a2.

📒 Files selected for processing (1)
  • apps/admin-web/src/app/page.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/admin-web/src/app/page.js

📝 Walkthrough

Walkthrough

The admin landing page is replaced with an async operator dashboard that resolves operator session (env token or bootstrap sign-in), concurrently loads verification and dispute queue states, and adds server actions to review verifications and advance dispute transitions, plus presenter helpers and tests.

Changes

Cohort / File(s) Summary
Operator Session
apps/admin-web/src/features/operator-session/operator-session.ts, apps/admin-web/src/features/operator-session/operator-session.test.ts
New module to resolve operator sessions via env token validation or bootstrap sign‑in against the platform API. Exports ResolvedOperatorSession type and resolveOperatorSession(fetchImpl?). Tests cover missing config, env-token flow, and bootstrap-sign-in flow with mocked fetch.
Dashboard Presenter
apps/admin-web/src/features/dashboard/dashboard-presenter.ts, apps/admin-web/src/features/dashboard/dashboard-presenter.test.ts
New presenter exposing QueueStatusSummary and functions describeVerificationQueue and describeDisputeQueue to map queue states to badge, headline, and detail. Tests cover loading, empty, error, and loaded scenarios.
Admin Dashboard Page & Server Actions
apps/admin-web/src/app/page.js
Converted AdminHomePage to an async page. Resolves operator session, concurrently loads verification and dispute queue states, renders VerificationQueueSection and DisputeQueueSection, adds local UI helpers, and implements server actions reviewVerificationAction and advanceDisputeAction for submitting review decisions and dispute transitions.
Verification Queue Tests
apps/admin-web/src/features/provider-review/verification-queue-actions.test.ts
Added tests verifying loadQueueState returns a loaded verification queue and that submitReviewDecision posts the decision and updates the queue state and reviewAction result.

Sequence Diagram

sequenceDiagram
    participant Browser as Browser/Client
    participant Page as AdminHomePage
    participant SessionMgr as resolveOperatorSession
    participant QueueMgr as Queue Loaders
    participant API as Platform API

    Browser->>Page: Request dashboard
    Page->>SessionMgr: resolveOperatorSession()
    SessionMgr->>API: GET /api/v1/auth/session or POST /api/v1/auth/sign-in
    API-->>SessionMgr: session payload / token
    SessionMgr-->>Page: resolved session (ok:true) or error
    Page->>QueueMgr: load verification & dispute queues (concurrent)
    QueueMgr->>API: Fetch queue endpoints
    API-->>QueueMgr: queue items / errors
    QueueMgr-->>Page: queue states
    Page->>Browser: Render sections and forms
    Browser->>Page: Submit review / dispute form (server action)
    Page->>API: POST decision / transition (server action)
    API-->>Page: confirmation
    Page->>Browser: Revalidate / refresh dashboard
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A dashboard wakes with morning light,
Sessions checked, the queues in sight,
Buttons hop to push a change,
Verifications, disputes arranged,
Async tails twitch — the tasks take flight! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(admin-web): ship operator dashboard for phase 5' directly aligns with the main change: replacing a static admin page with a functional operator dashboard that includes session bootstrap, verification queue handling, and dispute queue management.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/tec-40-admin-dashboard

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (5)
apps/admin-web/src/app/page.js (2)

19-26: Validate decision before loading queue state to avoid unnecessary API call.

The decision validation at Line 24 happens after loadQueueState is called. Moving validation earlier would skip the queue fetch when the decision is already invalid.

♻️ Reorder validation
   const verificationId = String(formData.get('verificationId') ?? '');
   const decision = String(formData.get('decision') ?? 'approved');
   const reviewNote = String(formData.get('reviewNote') ?? '').trim();
 
+  if (decision !== 'approved' && decision !== 'rejected') {
+    return;
+  }
+
   const currentState = await loadQueueState(sessionResult.sessionToken);
-  if (currentState.status !== 'loaded' || (decision !== 'approved' && decision !== 'rejected')) {
+  if (currentState.status !== 'loaded') {
     return;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/admin-web/src/app/page.js` around lines 19 - 26, Validate the decision
variable before calling loadQueueState to avoid an unnecessary API call: check
that decision is either 'approved' or 'rejected' (using the existing decision
variable) right after it's derived, and return early if invalid; only after that
validation call loadQueueState(sessionResult.sessionToken) and then use
currentState.status as before. Reference decision and loadQueueState to locate
where to reorder the logic.

135-135: Hardcoded 'de-AT' locale may not suit all operators.

The date formatting uses a fixed Austrian German locale. Consider deriving the locale from operator preferences, browser settings, or a configurable default.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/admin-web/src/app/page.js` at line 135, The date formatting in the page
component currently forces the 'de-AT' locale for the Submitted timestamp (the
expression using verification.submittedAt and toLocaleString('de-AT')); change
it to use a configurable locale (e.g., derived from an operator preference, a
prop/context value, or navigator.language as a fallback) and format via
Intl.DateTimeFormat or toLocaleString with that locale variable so the displayed
date respects operator/browser settings; update the rendering path where
verification.submittedAt is used to consume the new locale source
(prop/context/config) instead of the hardcoded 'de-AT'.
apps/admin-web/src/features/dashboard/dashboard-presenter.test.ts (1)

61-67: Consider adding coverage for error states within loaded queues.

The loaded tests only cover the happy path where reviewAction.status is idle. The presenter has a branch for state.reviewAction.status === 'error' (Lines 35-36 in dashboard-presenter.ts) that surfaces errorMessage in the detail field. Similarly for dispute's queueAction.status === 'error'.

📋 Example additional test case
it('describes verification loaded state with review error', () => {
  const state = {
    status: 'loaded' as const,
    verifications: [verification],
    reviewAction: { status: 'error' as const, verificationId: 'v-1', errorMessage: 'Review failed' },
  };
  expect(describeVerificationQueue(state)).toEqual({
    badge: '1 pending',
    headline: 'Provider verification queue is live.',
    detail: 'Review failed',
  });
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/admin-web/src/features/dashboard/dashboard-presenter.test.ts` around
lines 61 - 67, Add tests covering the loaded-but-failing branches so the
presenter surfaces error messages: create a loaded state for
describeVerificationQueue where reviewAction.status === 'error' with a
verificationId and errorMessage and assert the returned detail equals that
errorMessage; similarly create a loaded state for describeDisputeQueue where
queueAction.status === 'error' with disputeId and errorMessage and assert the
detail contains the errorMessage. Use the existing helper createLoadedQueueState
or construct the state object directly and reference describeVerificationQueue,
describeDisputeQueue, reviewAction, queueAction, and errorMessage when building
assertions.
apps/admin-web/src/features/operator-session/operator-session.test.ts (1)

5-20: Environment restoration looks correct but may be fragile in parallel test execution.

Capturing process.env values at module load time works for sequential test runs. If Vitest runs test files in parallel (default behavior), mutations to process.env could leak across files since they share the same process.

Consider using vi.stubEnv() for safer environment variable mocking if parallel isolation becomes an issue.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/admin-web/src/features/operator-session/operator-session.test.ts` around
lines 5 - 20, The test captures environment variables at module load
(previousSessionToken, previousBootstrapEmail) and restores them in afterEach,
which is fragile under Vitest's parallel test execution; update
operator-session.test.ts to use Vitest's vi.stubEnv (or capture/restore inside
beforeEach/afterEach per test) instead of module-level snapshots so env changes
are isolated—replace the module-level
previousSessionToken/previousBootstrapEmail usage with vi.stubEnv(...) when
setting test env or move the snapshot logic into a beforeEach that saves
process.env values and an afterEach that restores them, referencing the existing
afterEach block and the previousSessionToken/previousBootstrapEmail identifiers.
apps/admin-web/src/features/operator-session/operator-session.ts (1)

92-98: Fallback values for missing email/userId may mask API contract violations.

When payload.session.email or payload.session.userId is undefined, the code falls back to hardcoded values ('operator@quickwerk.local', 'operator-missing'). Given that the type guard already validates these fields exist as strings (Lines 44-46), this scenario shouldn't occur. If it does, it indicates a logic bug.

Consider logging a warning or removing the nullish coalescing since the type guard guarantees these fields are present.

📋 Remove unnecessary fallbacks
     return {
       ok: true,
       sessionToken,
-      operatorEmail: payload.session.email ?? 'operator@quickwerk.local',
-      operatorUserId: payload.session.userId ?? 'operator-missing',
+      operatorEmail: payload.session!.email!,
+      operatorUserId: payload.session!.userId!,
       source: 'env-token',
     };

The non-null assertions are safe here because Lines 85-90 already verify payload.session?.role !== 'operator' would return an error, meaning we only reach this point when session is defined with valid fields.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/admin-web/src/features/operator-session/operator-session.ts` around
lines 92 - 98, The return object uses fallback literals for
payload.session.email and payload.session.userId which can mask bugs despite the
earlier type guard; remove the nullish coalescing and return the values directly
(use payload.session!.email and payload.session!.userId or the non-null asserted
properties) in the function that constructs the session response, and optionally
add a processLogger.warn or throw an invariant if payload.session is
unexpectedly missing to fail fast. Ensure you update the return object keys
(sessionToken, operatorEmail, operatorUserId, source) to use the asserted
session fields (payload.session!.email, payload.session!.userId) instead of the
hardcoded defaults.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/admin-web/src/app/page.js`:
- Around line 11-37: The server action reviewVerificationAction currently
returns early on failures (when resolveOperatorSession() yields ok=false, when
loadQueueState() status isn't 'loaded', or when decision is invalid) which
silently discards errors; modify reviewVerificationAction to surface errors by
returning a structured result (eg. { ok: false, error: 'message' }) or by
throwing descriptive Errors instead of returning undefined—include contextual
messages for each failure point (session failure from resolveOperatorSession,
queue load failure from loadQueueState, invalid decision) and keep successful
path returning { ok: true } (or consistent success shape) so the
caller/component can use useFormState or similar to display feedback; ensure
calls to submitReviewDecision and revalidatePath remain in the success branch.

---

Nitpick comments:
In `@apps/admin-web/src/app/page.js`:
- Around line 19-26: Validate the decision variable before calling
loadQueueState to avoid an unnecessary API call: check that decision is either
'approved' or 'rejected' (using the existing decision variable) right after it's
derived, and return early if invalid; only after that validation call
loadQueueState(sessionResult.sessionToken) and then use currentState.status as
before. Reference decision and loadQueueState to locate where to reorder the
logic.
- Line 135: The date formatting in the page component currently forces the
'de-AT' locale for the Submitted timestamp (the expression using
verification.submittedAt and toLocaleString('de-AT')); change it to use a
configurable locale (e.g., derived from an operator preference, a prop/context
value, or navigator.language as a fallback) and format via Intl.DateTimeFormat
or toLocaleString with that locale variable so the displayed date respects
operator/browser settings; update the rendering path where
verification.submittedAt is used to consume the new locale source
(prop/context/config) instead of the hardcoded 'de-AT'.

In `@apps/admin-web/src/features/dashboard/dashboard-presenter.test.ts`:
- Around line 61-67: Add tests covering the loaded-but-failing branches so the
presenter surfaces error messages: create a loaded state for
describeVerificationQueue where reviewAction.status === 'error' with a
verificationId and errorMessage and assert the returned detail equals that
errorMessage; similarly create a loaded state for describeDisputeQueue where
queueAction.status === 'error' with disputeId and errorMessage and assert the
detail contains the errorMessage. Use the existing helper createLoadedQueueState
or construct the state object directly and reference describeVerificationQueue,
describeDisputeQueue, reviewAction, queueAction, and errorMessage when building
assertions.

In `@apps/admin-web/src/features/operator-session/operator-session.test.ts`:
- Around line 5-20: The test captures environment variables at module load
(previousSessionToken, previousBootstrapEmail) and restores them in afterEach,
which is fragile under Vitest's parallel test execution; update
operator-session.test.ts to use Vitest's vi.stubEnv (or capture/restore inside
beforeEach/afterEach per test) instead of module-level snapshots so env changes
are isolated—replace the module-level
previousSessionToken/previousBootstrapEmail usage with vi.stubEnv(...) when
setting test env or move the snapshot logic into a beforeEach that saves
process.env values and an afterEach that restores them, referencing the existing
afterEach block and the previousSessionToken/previousBootstrapEmail identifiers.

In `@apps/admin-web/src/features/operator-session/operator-session.ts`:
- Around line 92-98: The return object uses fallback literals for
payload.session.email and payload.session.userId which can mask bugs despite the
earlier type guard; remove the nullish coalescing and return the values directly
(use payload.session!.email and payload.session!.userId or the non-null asserted
properties) in the function that constructs the session response, and optionally
add a processLogger.warn or throw an invariant if payload.session is
unexpectedly missing to fail fast. Ensure you update the return object keys
(sessionToken, operatorEmail, operatorUserId, source) to use the asserted
session fields (payload.session!.email, payload.session!.userId) instead of the
hardcoded defaults.
🪄 Autofix (Beta)

✅ Autofix completed


ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 72970213-92dd-4e81-b455-177f2a05de04

📥 Commits

Reviewing files that changed from the base of the PR and between 37550e6 and 0b63525.

📒 Files selected for processing (6)
  • apps/admin-web/src/app/page.js
  • apps/admin-web/src/features/dashboard/dashboard-presenter.test.ts
  • apps/admin-web/src/features/dashboard/dashboard-presenter.ts
  • apps/admin-web/src/features/operator-session/operator-session.test.ts
  • apps/admin-web/src/features/operator-session/operator-session.ts
  • apps/admin-web/src/features/provider-review/verification-queue-actions.test.ts

Comment thread apps/admin-web/src/app/page.js
@coderabbitai

coderabbitai Bot commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Fixes Applied Successfully

Fixed 2 file(s) based on 1 unresolved review comment.

Files modified:

  • apps/admin-web/next-env.d.ts
  • apps/admin-web/src/app/page.js

Commit: c2aa2a26a50b377a4782ee50a4523bfd64ba9766

The changes have been pushed to the feat/tec-40-admin-dashboard branch.

Time taken: 4m 7s

Fixed 2 file(s) based on 1 unresolved review comment.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
@imKXNNY imKXNNY merged commit 4776d2a into main Apr 13, 2026
1 check passed
@imKXNNY imKXNNY deleted the feat/tec-40-admin-dashboard branch April 13, 2026 13:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant