feat(tracker-web): page chrome via @bytelyst/dashboard-components (UX-10)

- error.tsx now renders ErrorPage (keeps trackEvent + reset wiring) (10.1)
- add PageHeader (title + breadcrumbs) to /dashboard, /dashboard/items,
  /dashboard/board and the item detail page for a consistent header band (10.2)
- replace ad-hoc loading text with LoadingSpinner on overview, items, detail (10.3)

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
saravanakumardb1 2026-05-28 20:45:00 -07:00
parent 73d2891d8e
commit 3a9621f0c2
6 changed files with 65 additions and 72 deletions

View File

@ -186,14 +186,14 @@ pnpm build # final gate
## UX-10 — Page chrome via `@bytelyst/dashboard-components`
- [ ] **10.1** `src/app/error.tsx`: render `ErrorPage` from `@bytelyst/dashboard-components`
- [x] **10.1** `src/app/error.tsx`: render `ErrorPage` from `@bytelyst/dashboard-components`
(keep the existing `trackEvent` telemetry side-effect + `reset` wiring). `not-found.tsx`
already uses `NotFoundPage` — leave it.
- [ ] **10.2** Add `PageHeader` (title + breadcrumbs) to the top of `/dashboard`, `/dashboard/items`,
- [x] **10.2** Add `PageHeader` (title + breadcrumbs) to the top of `/dashboard`, `/dashboard/items`,
`/dashboard/board`, and the item detail page for a consistent header band.
- [ ] **10.3** Replace ad-hoc loading text with `LoadingSpinner`/`LoadingSkeleton` where a full
- [x] **10.3** Replace ad-hoc loading text with `LoadingSpinner`/`LoadingSkeleton` where a full
`SkeletonGroup` (UX-2) is overkill.
**Verify:** `pnpm typecheck && pnpm lint && pnpm build`.
**Verify:** `pnpm typecheck && pnpm lint && pnpm build`. (UX-10 verified: tc/lint/test 159 ✓/build/e2e 18 ✓; no new color literals)
## UX-11 — Adopt `@bytelyst/auth-ui` on the login surface
@ -253,7 +253,7 @@ pnpm build # final gate
```
Core : UX-1 ✅ UX-2 ⬜ UX-3 ⬜ UX-4 ⬜ UX-5 ⬜ UX-6 ⬜ UX-7 ⬜ UX-8 ⬜
Expand : UX-9 ✅ UX-10 UX-11 ⬜ UX-12 ⬜ UX-13 ⬜ (stretch: 12.3, 13.*)
Expand : UX-9 ✅ UX-10 UX-11 ⬜ UX-12 ⬜ UX-13 ⬜ (stretch: 12.3, 13.*)
```
**UX-1 is done** (token bridge + Primitives adapter, commit `dc01dd02`) — the `--bl-*` bridge is

View File

@ -2,6 +2,7 @@
import { useEffect, useState, useCallback } from 'react';
import Link from 'next/link';
import { PageHeader } from '@bytelyst/dashboard-components';
import { useAuth } from '@/lib/auth-context';
import { listItems, updateItemStatus, type TrackerItem } from '@/lib/tracker-client';
@ -56,10 +57,11 @@ export default function BoardPage() {
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-bold tracking-tight">Board</h1>
<p className="text-sm text-muted-foreground">Kanban view of all items</p>
</div>
<PageHeader
title="Board"
breadcrumbs={[{ label: 'Dashboard', href: '/dashboard' }, { label: 'Board' }]}
/>
<p className="-mt-4 text-sm text-muted-foreground">Kanban view of all items</p>
{error && (
<div className="rounded-md bg-destructive/10 px-4 py-3 text-sm text-destructive">

View File

@ -1,7 +1,8 @@
'use client';
import { useEffect, useState } from 'react';
import { useParams, useRouter } from 'next/navigation';
import { useParams } from 'next/navigation';
import { PageHeader, LoadingSpinner } from '@bytelyst/dashboard-components';
import { useAuth } from '@/lib/auth-context';
import {
getItem,
@ -20,7 +21,6 @@ const VISIBILITIES = ['internal', 'public'] as const;
export default function ItemDetailPage() {
const { id } = useParams<{ id: string }>();
const router = useRouter();
const { token } = useAuth();
const [item, setItem] = useState<TrackerItem | null>(null);
@ -109,21 +109,35 @@ export default function ItemDetailPage() {
if (!item) {
return (
<div className="flex min-h-[400px] items-center justify-center text-muted-foreground">
{error || 'Loading...'}
<div className="mx-auto flex min-h-[400px] max-w-3xl items-center justify-center">
{error ? <p className="text-sm text-destructive">{error}</p> : <LoadingSpinner />}
</div>
);
}
return (
<div className="mx-auto max-w-3xl space-y-6">
{/* Back */}
<button
onClick={() => router.back()}
className="text-sm text-muted-foreground hover:text-foreground"
>
Back
</button>
<PageHeader
title={item.title}
breadcrumbs={[
{ label: 'Dashboard', href: '/dashboard' },
{ label: 'Items', href: '/dashboard/items' },
]}
actions={
editing ? undefined : (
<button
onClick={() => {
setEditTitle(item.title);
setEditDescription(item.description);
setEditing(true);
}}
className="rounded-md px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent"
>
Edit
</button>
)
}
/>
{error && (
<div className="rounded-md bg-destructive/10 px-4 py-3 text-sm text-destructive">
@ -131,7 +145,7 @@ export default function ItemDetailPage() {
</div>
)}
{/* Header */}
{/* Title/description editor */}
<div className="space-y-2">
{editing ? (
<div className="space-y-3">
@ -163,26 +177,9 @@ export default function ItemDetailPage() {
</div>
</div>
) : (
<>
<div className="flex items-start justify-between">
<h1 className="text-2xl font-bold tracking-tight">{item.title}</h1>
<button
onClick={() => {
setEditTitle(item.title);
setEditDescription(item.description);
setEditing(true);
}}
className="rounded-md px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent"
>
Edit
</button>
</div>
{item.description && (
<p className="whitespace-pre-wrap text-sm text-muted-foreground">
{item.description}
</p>
)}
</>
item.description && (
<p className="whitespace-pre-wrap text-sm text-muted-foreground">{item.description}</p>
)
)}
</div>

View File

@ -3,6 +3,7 @@
import { useEffect, useState, useCallback, useMemo } from 'react';
import Link from 'next/link';
import { DataTable, type ColumnDef } from '@bytelyst/data-table';
import { PageHeader, LoadingSpinner } from '@bytelyst/dashboard-components';
import { useAuth } from '@/lib/auth-context';
import { listItems, createItem, deleteItem, type TrackerItem } from '@/lib/tracker-client';
@ -191,18 +192,19 @@ export default function ItemsListPage() {
return (
<div className="space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold tracking-tight">Items</h1>
<p className="text-sm text-muted-foreground">{total} items total</p>
</div>
<button
onClick={() => setShowCreate(true)}
className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90"
>
+ New Item
</button>
</div>
<PageHeader
title="Items"
breadcrumbs={[{ label: 'Dashboard', href: '/dashboard' }, { label: 'Items' }]}
actions={
<button
onClick={() => setShowCreate(true)}
className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90"
>
+ New Item
</button>
}
/>
<p className="-mt-4 text-sm text-muted-foreground">{total} items total</p>
{error && (
<div className="rounded-md bg-destructive/10 px-4 py-3 text-sm text-destructive">
@ -256,7 +258,9 @@ export default function ItemsListPage() {
{/* Items table — @bytelyst/data-table (Wave 9.C.9) */}
{loading ? (
<div className="text-muted-foreground">Loading...</div>
<div className="flex justify-center py-10">
<LoadingSpinner />
</div>
) : (
<DataTable
ariaLabel="Tracker items"

View File

@ -1,6 +1,7 @@
'use client';
import { useEffect, useState } from 'react';
import { PageHeader, LoadingSpinner } from '@bytelyst/dashboard-components';
import { useAuth } from '@/lib/auth-context';
import { getStats, type TrackerStats } from '@/lib/tracker-client';
@ -54,10 +55,8 @@ export default function DashboardOverview() {
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-bold tracking-tight">Dashboard</h1>
<p className="text-sm text-muted-foreground">Overview of all tracked items</p>
</div>
<PageHeader title="Dashboard" />
<p className="-mt-4 text-sm text-muted-foreground">Overview of all tracked items</p>
{error && (
<div className="rounded-md bg-destructive/10 px-4 py-3 text-sm text-destructive">
@ -81,7 +80,9 @@ export default function DashboardOverview() {
</div>
</div>
) : !error ? (
<div className="text-muted-foreground">Loading stats...</div>
<div className="flex justify-center py-10">
<LoadingSpinner />
</div>
) : null}
</div>
);

View File

@ -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,7 @@ export default function GlobalError({
return (
<div className="flex min-h-screen items-center justify-center p-4">
<div className="mx-auto max-w-md text-center">
<div className="mb-4 text-5xl"></div>
<h2 className="mb-2 text-xl font-semibold">Something went wrong</h2>
<p className="mb-6 text-sm text-muted-foreground">
{error.message || 'An unexpected error occurred.'}
</p>
<button
onClick={reset}
className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90"
>
Try again
</button>
</div>
<ErrorPage message={error.message || 'An unexpected error occurred.'} onRetry={reset} />
</div>
);
}