feat(tracker-web): add public submission status page
Some checks failed
Publish @bytelyst/* packages / publish (push) Failing after 12s
CI — Common Platform / Build, Test & Typecheck (push) Successful in 42s

This commit is contained in:
Saravana Kumar 2026-05-30 19:50:15 +00:00
parent 5606ccf1f7
commit 3a6ed3a5f8
2 changed files with 191 additions and 0 deletions

View File

@ -109,6 +109,13 @@ async function mockRoadmap(page: Page): Promise<void> {
if (url.includes('/public/roadmap/stats')) { if (url.includes('/public/roadmap/stats')) {
return route.fulfill({ json: ROADMAP_STATS }); return route.fulfill({ json: ROADMAP_STATS });
} }
if (url.includes('/public/items/')) {
const id = url.split('/public/items/')[1]?.split(/[?#]/)[0];
const item = ROADMAP_ITEMS.find(entry => entry.id === id);
return item
? route.fulfill({ json: item })
: route.fulfill({ status: 404, json: { error: 'Item not found' } });
}
if (url.includes('/public/roadmap')) { if (url.includes('/public/roadmap')) {
return route.fulfill({ return route.fulfill({
json: { items: ROADMAP_ITEMS, total: ROADMAP_ITEMS.length, limit: 100, offset: 0 }, json: { items: ROADMAP_ITEMS, total: ROADMAP_ITEMS.length, limit: 100, offset: 0 },
@ -329,6 +336,23 @@ test.describe('Tracker — Public Roadmap', () => {
await expect(page.getByRole('heading', { name: /enter your email to vote/i })).toBeVisible(); await expect(page.getByRole('heading', { name: /enter your email to vote/i })).toBeVisible();
}); });
test('links submitted users to a public status page for an item', async ({ page }) => {
await page.goto('/status/1');
await expect(page.getByRole('heading', { name: 'Dark mode toggle' })).toBeVisible();
await expect(page.getByText('Submission status')).toBeVisible();
await expect(
page.getByText('We received this submission and it is waiting for triage.')
).toBeVisible();
await expect(page.getByText('12 votes')).toBeVisible();
await expect(page.getByRole('link', { name: /back to roadmap/i })).toBeVisible();
});
test('shows a helpful message for an unknown public status item', async ({ page }) => {
await page.goto('/status/missing');
await expect(page.getByRole('heading', { name: /submission not found/i })).toBeVisible();
await expect(page.getByRole('link', { name: /view roadmap/i })).toBeVisible();
});
test('has no serious accessibility violations', async ({ page }) => { test('has no serious accessibility violations', async ({ page }) => {
await page.goto('/roadmap'); await page.goto('/roadmap');
await expect(page.getByRole('heading', { name: 'Dark mode toggle' })).toBeVisible(); await expect(page.getByRole('heading', { name: 'Dark mode toggle' })).toBeVisible();

View File

@ -0,0 +1,167 @@
'use client';
import Link from 'next/link';
import { useParams } from 'next/navigation';
import { useEffect, useState } from 'react';
import { getPublicItem, type TrackerItem } from '@/lib/tracker-client';
const STATUS_COPY: Record<string, { label: string; description: string }> = {
open: {
label: 'Open',
description: 'We received this submission and it is waiting for triage.',
},
in_progress: {
label: 'In Progress',
description: 'This item is actively being worked on.',
},
done: {
label: 'Complete',
description: 'This item has been shipped or otherwise completed.',
},
closed: {
label: 'Closed',
description: 'This item has been closed.',
},
wont_fix: {
label: "Won't Fix",
description: 'This item was reviewed but is not currently planned.',
},
};
function formatStatus(status: string) {
return STATUS_COPY[status]?.label ?? status.replaceAll('_', ' ');
}
function formatDate(value: string) {
return new Intl.DateTimeFormat('en', { month: 'short', day: 'numeric', year: 'numeric' }).format(
new Date(value)
);
}
export default function SubmissionStatusPage() {
const params = useParams<{ id: string }>();
const id = params?.id;
const [item, setItem] = useState<TrackerItem | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let cancelled = false;
async function loadItem() {
if (!id) return;
setLoading(true);
setError(null);
try {
const nextItem = await getPublicItem(id);
if (!cancelled) setItem(nextItem);
} catch (_err) {
if (!cancelled) setError('not_found');
} finally {
if (!cancelled) setLoading(false);
}
}
void loadItem();
return () => {
cancelled = true;
};
}, [id]);
if (loading) {
return (
<main className="min-h-screen bg-slate-950 px-6 py-12 text-slate-100">
<div className="mx-auto max-w-3xl rounded-3xl border border-slate-800 bg-slate-900/70 p-8 shadow-2xl shadow-black/30">
<p className="text-sm uppercase tracking-[0.24em] text-cyan-300">Submission status</p>
<h1 className="mt-3 text-3xl font-bold">Loading submission</h1>
</div>
</main>
);
}
if (error || !item) {
return (
<main className="min-h-screen bg-slate-950 px-6 py-12 text-slate-100">
<div className="mx-auto max-w-3xl rounded-3xl border border-slate-800 bg-slate-900/70 p-8 shadow-2xl shadow-black/30">
<p className="text-sm uppercase tracking-[0.24em] text-cyan-300">Submission status</p>
<h1 className="mt-3 text-3xl font-bold">Submission not found</h1>
<p className="mt-3 text-slate-300">
We could not find a public roadmap item for this status link. It may have been made
internal, merged into another item, or removed.
</p>
<Link
href="/roadmap"
className="mt-6 inline-flex items-center gap-2 rounded-full bg-cyan-400 px-5 py-3 text-sm font-semibold text-slate-950 hover:bg-cyan-300"
>
View roadmap
</Link>
</div>
</main>
);
}
const status = STATUS_COPY[item.status] ?? {
label: formatStatus(item.status),
description: 'This submission is being tracked by the ByteLyst roadmap team.',
};
return (
<main className="min-h-screen bg-slate-950 px-6 py-12 text-slate-100">
<div className="mx-auto max-w-4xl">
<Link
href="/roadmap"
className="inline-flex items-center gap-2 text-sm text-cyan-300 hover:text-cyan-200"
>
Back to roadmap
</Link>
<section className="mt-6 rounded-3xl border border-slate-800 bg-slate-900/75 p-8 shadow-2xl shadow-black/30">
<p className="text-sm uppercase tracking-[0.24em] text-cyan-300">Submission status</p>
<div className="mt-4 flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
<div>
<h1 className="text-3xl font-bold tracking-tight sm:text-4xl">{item.title}</h1>
<p className="mt-3 max-w-2xl text-slate-300">{item.description}</p>
</div>
<span className="inline-flex w-fit items-center gap-2 rounded-full border border-cyan-300/40 bg-cyan-300/10 px-4 py-2 text-sm font-semibold text-cyan-100">
{status.label}
</span>
</div>
<div className="mt-8 grid gap-4 sm:grid-cols-3">
<div className="rounded-2xl border border-slate-800 bg-slate-950/60 p-4">
<p className="text-xs uppercase tracking-[0.16em] text-slate-500">Status</p>
<p className="mt-2 text-lg font-semibold">{status.label}</p>
<p className="mt-1 text-sm text-slate-400">{status.description}</p>
</div>
<div className="rounded-2xl border border-slate-800 bg-slate-950/60 p-4">
<p className="flex items-center gap-2 text-xs uppercase tracking-[0.16em] text-slate-500">
Votes
</p>
<p className="mt-2 text-lg font-semibold">{item.voteCount} votes</p>
<p className="mt-1 text-sm text-slate-400">Community interest signal.</p>
</div>
<div className="rounded-2xl border border-slate-800 bg-slate-950/60 p-4">
<p className="flex items-center gap-2 text-xs uppercase tracking-[0.16em] text-slate-500">
Activity
</p>
<p className="mt-2 text-lg font-semibold">{item.commentCount} comments</p>
<p className="mt-1 text-sm text-slate-400">
Last updated {formatDate(item.updatedAt)}.
</p>
</div>
</div>
<div className="mt-8 rounded-2xl border border-slate-800 bg-slate-950/40 p-5">
<p className="flex items-center gap-2 text-sm font-semibold text-slate-200">
What happens next?
</p>
<p className="mt-2 text-sm leading-6 text-slate-400">
Keep this link to check progress. Public roadmap status updates, votes, and completion
state will appear here without requiring an account.
</p>
</div>
</section>
</div>
</main>
);
}