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:
parent
73d2891d8e
commit
3a9621f0c2
@ -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
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user