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` ## 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` (keep the existing `trackEvent` telemetry side-effect + `reset` wiring). `not-found.tsx`
already uses `NotFoundPage` — leave it. 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. `/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. `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 ## 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 ⬜ 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 **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 { useEffect, useState, useCallback } from 'react';
import Link from 'next/link'; import Link from 'next/link';
import { PageHeader } from '@bytelyst/dashboard-components';
import { useAuth } from '@/lib/auth-context'; import { useAuth } from '@/lib/auth-context';
import { listItems, updateItemStatus, type TrackerItem } from '@/lib/tracker-client'; import { listItems, updateItemStatus, type TrackerItem } from '@/lib/tracker-client';
@ -56,10 +57,11 @@ export default function BoardPage() {
return ( return (
<div className="space-y-6"> <div className="space-y-6">
<div> <PageHeader
<h1 className="text-2xl font-bold tracking-tight">Board</h1> title="Board"
<p className="text-sm text-muted-foreground">Kanban view of all items</p> breadcrumbs={[{ label: 'Dashboard', href: '/dashboard' }, { label: 'Board' }]}
</div> />
<p className="-mt-4 text-sm text-muted-foreground">Kanban view of all items</p>
{error && ( {error && (
<div className="rounded-md bg-destructive/10 px-4 py-3 text-sm text-destructive"> <div className="rounded-md bg-destructive/10 px-4 py-3 text-sm text-destructive">

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
'use client'; 'use client';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { ErrorPage } from '@bytelyst/dashboard-components';
import { trackEvent } from '@/lib/telemetry'; import { trackEvent } from '@/lib/telemetry';
export default function GlobalError({ export default function GlobalError({
@ -19,19 +20,7 @@ export default function GlobalError({
return ( return (
<div className="flex min-h-screen items-center justify-center p-4"> <div className="flex min-h-screen items-center justify-center p-4">
<div className="mx-auto max-w-md text-center"> <ErrorPage message={error.message || 'An unexpected error occurred.'} onRetry={reset} />
<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>
</div> </div>
); );
} }