From 94ef3f1c202b98c6213f7133c36586e0b875868b Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Fri, 29 May 2026 14:09:40 -0700 Subject: [PATCH] feat(admin-web): adopt @bytelyst/dashboard-components page chrome (UX-4) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - error.tsx -> ErrorPage (keep telemetry on mount; retry wired to Next reset). - (dashboard)/loading.tsx -> LoadingSpinner inside the existing skeleton. - not-found.tsx already used NotFoundPage (confirmed, unchanged). - dashboard overview page.tsx header -> PageHeader (Refresh as actions; the subtitle/last-updated line preserved directly below). Rich detail headers (e.g. users/[id] back-button + plan/status badges) left bespoke on purpose: PageHeader has no subtitle/badge slot, so forcing it would regress them (additive-only rule). dashboard-components reads --color-* which admin maps via @theme inline, so it themes in light + dark. Verify: typecheck+lint+build green (123 routes); vitest 20 files / 168 tests (+3 happy-dom chrome render tests); format:check no new failures; e2e 11 passed / 80 failed (unchanged vs UX-1 baseline — environmental). Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../docs/roadmaps/UX_INTEGRATION_ADMIN.md | 12 +++- .../src/__tests__/page-chrome.test.tsx | 66 +++++++++++++++++++ .../admin-web/src/app/(dashboard)/loading.tsx | 4 +- .../admin-web/src/app/(dashboard)/page.tsx | 41 +++++++----- dashboards/admin-web/src/app/error.tsx | 19 ++---- dashboards/admin-web/vitest.config.ts | 2 +- 6 files changed, 110 insertions(+), 34 deletions(-) create mode 100644 dashboards/admin-web/src/__tests__/page-chrome.test.tsx diff --git a/dashboards/admin-web/docs/roadmaps/UX_INTEGRATION_ADMIN.md b/dashboards/admin-web/docs/roadmaps/UX_INTEGRATION_ADMIN.md index 2b0d9b16..1d78ab4d 100644 --- a/dashboards/admin-web/docs/roadmaps/UX_INTEGRATION_ADMIN.md +++ b/dashboards/admin-web/docs/roadmaps/UX_INTEGRATION_ADMIN.md @@ -152,9 +152,17 @@ pnpm --filter @bytelyst/admin-web test:e2e # Playwright + @axe-core (no new fa happy-dom ⌘K/Ctrl-K interaction test (`react-dom/client` + `act`, no new deps; react deduped). test **19 files / 165 tests** (+6); typecheck+lint+build green (123 routes); format:check no new failures; e2e unchanged. -- [ ] **UX-4 — Page chrome:** use `@bytelyst/dashboard-components` (`PageHeader`/`ErrorPage`/ +- [x] **UX-4 — Page chrome:** use `@bytelyst/dashboard-components` (`PageHeader`/`ErrorPage`/ `NotFoundPage`/`LoadingSpinner`) on `error.tsx`/`not-found.tsx`/`loading.tsx` + a few high-traffic surfaces where chrome is bespoke. Keep it additive. + — **DONE** `` · `error.tsx`→`ErrorPage` (telemetry kept; retry→`reset`); + `loading.tsx`→`LoadingSpinner` inside the existing skeleton; `not-found.tsx` already used + `NotFoundPage` (confirmed); dashboard overview `page.tsx` header→`PageHeader` (Refresh as + `actions`, subtitle preserved below). Rich detail headers (users/[id] back-button + badges) + intentionally left bespoke — `PageHeader` has no subtitle/badge slot, so forcing it would + regress them (additive rule). dashboard-components reads `--color-*` (admin `@theme inline`), + so themes correctly. test **20 files / 168 tests** (+3, happy-dom render of error/not-found/ + loading chrome); typecheck+lint+build green; format:check no new failures; e2e unchanged. - [ ] **UX-5 — Motion:** add `@bytelyst/motion`; subtle `Reveal`/`Stagger` on the overview dashboard cards + key tables; respect `prefers-reduced-motion`. (Note tracker-web's lesson: do NOT apply motion to surfaces an offline axe gate scans synchronously if transient opacity trips contrast.) @@ -173,7 +181,7 @@ pnpm --filter @bytelyst/admin-web test:e2e # Playwright + @axe-core (no new fa ``` Setup : UX-1 ✅ -Adopt : UX-2 ✅ UX-3 ✅ UX-4 ⬜ UX-5 ⬜ UX-6 ⬜(cond) +Adopt : UX-2 ✅ UX-3 ✅ UX-4 ✅ UX-5 ⬜ UX-6 ⬜(cond) Cross : CC.1 ⬜ CC.2 ⬜ CC.3 ⬜ CC.4 ⬜ CC.5 ⬜ CC.6 ⬜ ``` diff --git a/dashboards/admin-web/src/__tests__/page-chrome.test.tsx b/dashboards/admin-web/src/__tests__/page-chrome.test.tsx new file mode 100644 index 00000000..db301858 --- /dev/null +++ b/dashboards/admin-web/src/__tests__/page-chrome.test.tsx @@ -0,0 +1,66 @@ +// @vitest-environment happy-dom +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { act } from 'react'; +import { createRoot, type Root } from 'react-dom/client'; + +// error.tsx fires telemetry on mount — stub it so the chrome can render in isolation. +vi.mock('@/lib/telemetry', () => ({ trackEvent: vi.fn() })); + +import GlobalError from '@/app/error'; +import NotFound from '@/app/not-found'; +import DashboardLoading from '@/app/(dashboard)/loading'; + +/** + * UX-4 — page-chrome adoption guard. Verifies admin's error / not-found / + * loading surfaces render the shared `@bytelyst/dashboard-components` chrome + * and that the error retry handler is wired to Next's `reset`. + */ +describe('page chrome (dashboard-components)', () => { + let container: HTMLDivElement; + let root: Root; + + beforeEach(() => { + (globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = + true; + container = document.createElement('div'); + document.body.appendChild(container); + root = createRoot(container); + }); + + afterEach(() => { + act(() => root.unmount()); + container.remove(); + }); + + it('error.tsx renders ErrorPage with the message and a working retry', () => { + const reset = vi.fn(); + act(() => { + root.render( + + ); + }); + expect(container.textContent).toContain('Something went wrong'); + expect(container.textContent).toContain('kaboom'); + + const retry = Array.from(container.querySelectorAll('button')).find(b => + /try again|retry/i.test(b.textContent ?? '') + ); + expect(retry).toBeTruthy(); + act(() => retry!.dispatchEvent(new MouseEvent('click', { bubbles: true }))); + expect(reset).toHaveBeenCalledTimes(1); + }); + + it('not-found.tsx renders the NotFoundPage chrome', () => { + act(() => { + root.render(); + }); + expect(container.querySelector('a[href="/"]')).toBeTruthy(); + }); + + it('loading.tsx renders an accessible LoadingSpinner', () => { + act(() => { + root.render(); + }); + expect(container.querySelector('[role="status"]')).toBeTruthy(); + }); +}); diff --git a/dashboards/admin-web/src/app/(dashboard)/loading.tsx b/dashboards/admin-web/src/app/(dashboard)/loading.tsx index 157b0415..9809638e 100644 --- a/dashboards/admin-web/src/app/(dashboard)/loading.tsx +++ b/dashboards/admin-web/src/app/(dashboard)/loading.tsx @@ -1,4 +1,4 @@ -import { Loader2 } from 'lucide-react'; +import { LoadingSpinner } from '@bytelyst/dashboard-components'; export default function DashboardLoading() { return ( @@ -29,7 +29,7 @@ export default function DashboardLoading() {
- +
))} diff --git a/dashboards/admin-web/src/app/(dashboard)/page.tsx b/dashboards/admin-web/src/app/(dashboard)/page.tsx index 680a6287..da56a67a 100644 --- a/dashboards/admin-web/src/app/(dashboard)/page.tsx +++ b/dashboards/admin-web/src/app/(dashboard)/page.tsx @@ -34,6 +34,7 @@ import { type ApiUsageRecord, type RevenueAnalytics, } from '@/lib/api'; +import { PageHeader } from '@bytelyst/dashboard-components'; import { AreaChart, BarChart } from '@/components/charts'; import { seriesValues, dateBars } from '@/lib/chart-data'; @@ -275,22 +276,30 @@ export default function DashboardPage() { return (
{/* Header */} -
-
-

Dashboard

-

- Platform overview and key metrics - {lastUpdated && ( - - · Updated {lastUpdated.toLocaleTimeString()} - - )} -

-
- +
+ fetchData(true)} + disabled={refreshing} + > + + Refresh + + } + /> +

+ Platform overview and key metrics + {lastUpdated && ( + + · Updated {lastUpdated.toLocaleTimeString()} + + )} +

{/* KPI Cards */} diff --git a/dashboards/admin-web/src/app/error.tsx b/dashboards/admin-web/src/app/error.tsx index 83d116e0..3995d1f5 100644 --- a/dashboards/admin-web/src/app/error.tsx +++ b/dashboards/admin-web/src/app/error.tsx @@ -1,6 +1,7 @@ 'use client'; import { useEffect } from 'react'; +import { ErrorPage } from '@bytelyst/dashboard-components'; import { trackEvent } from '@/lib/telemetry'; export default function GlobalError({ @@ -19,19 +20,11 @@ export default function GlobalError({ return (
-
-
-

Something went wrong

-

- {error.message || 'An unexpected error occurred.'} -

- -
+
); } diff --git a/dashboards/admin-web/vitest.config.ts b/dashboards/admin-web/vitest.config.ts index 882cbcbc..734eb6b6 100644 --- a/dashboards/admin-web/vitest.config.ts +++ b/dashboards/admin-web/vitest.config.ts @@ -13,7 +13,7 @@ export default defineConfig({ // The real Next/webpack build already dedupes these to admin-web's React. server: { deps: { - inline: [/@bytelyst\/(charts|data-viz|command-palette)/], + inline: [/@bytelyst\/(charts|data-viz|command-palette|dashboard-components)/], }, }, coverage: {