From 8b26393d4288f746e287e9942f83adae35747937 Mon Sep 17 00:00:00 2001 From: sa-salami Date: Tue, 6 Oct 2026 21:39:40 +0100 Subject: [PATCH] fix: address Parallax issue #61 --- scripts/smoke-test.mjs | 10 ++++++++++ src/App.tsx | 3 ++- src/components/NotFound.tsx | 31 +++++++++++++++++++++++++++++++ src/pages/issue.tsx | 5 +++-- src/styles/pages.css | 4 ++++ 5 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 src/components/NotFound.tsx diff --git a/scripts/smoke-test.mjs b/scripts/smoke-test.mjs index a9df592..24d0f4a 100644 --- a/scripts/smoke-test.mjs +++ b/scripts/smoke-test.mjs @@ -264,6 +264,16 @@ try { await page.locator('.rail.open').getByRole('link', { name: 'Connect a repo' }).click(); await page.waitForURL('**/maintainer/submit'); + // ------------------------------------------------------------- not-found UI + await go('/missing-route'); + await page.getByRole('heading', { name: 'Page not found' }).waitFor(); + assert.equal(await page.title(), 'Page not found | Parallax'); + assert.ok(await page.getByRole('link', { name: /Explore issues/ }).isVisible()); + assert.ok(await page.getByRole('link', { name: 'Home', exact: true }).isVisible()); + await go('/issue/not-a-real-issue'); + await page.getByRole('heading', { name: 'Issue not found' }).waitFor(); + assert.equal(await page.title(), 'Issue not found | Parallax'); + assert.deepEqual(errors, []); console.log('Passed: public explore (no dashboard chrome), search/tabs/filters, contributor apply and persistence, separate maintainer sign-in, connect-then-verify gate, per-repo dashboards scoped by owner and verification, proposal assignment, posting a funded issue, payout address validation, merge-and-release writing a receipt, theme persistence, 15 routes at 5 widths with no overflow, and the mobile drawer.'); } catch (error) { diff --git a/src/App.tsx b/src/App.tsx index ed2cf1e..18cf5e6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -12,6 +12,7 @@ import { canOpenRepoDashboard, escrowedTotal, formatMoney, repoName, reposOwnedB import { PLATFORM } from './lib/platform'; import { Avatar, Brand, EASE } from './components/ui'; import { GooeyNav } from './components/bits'; +import { NotFound } from './components/NotFound'; import { Landing } from './pages/landing'; import { Explore } from './pages/explore'; import { IssuePage } from './pages/issue'; @@ -381,7 +382,7 @@ function Routed() { } /> } /> - } /> + } /> ); } diff --git a/src/components/NotFound.tsx b/src/components/NotFound.tsx new file mode 100644 index 0000000..8113f1e --- /dev/null +++ b/src/components/NotFound.tsx @@ -0,0 +1,31 @@ +import { useEffect } from 'react'; +import { Link } from 'react-router-dom'; +import { ArrowLeft, Home } from 'lucide-react'; +import { Page } from './ui'; + +export function NotFound({ issue = false }: { issue?: boolean }) { + const title = issue ? 'Issue not found' : 'Page not found'; + + useEffect(() => { + document.title = `${title} | Parallax`; + return () => { document.title = 'Parallax — bounties settled on Stellar'; }; + }, [title]); + + return ( + +
+

404

+

{title}

+

+ {issue + ? 'That issue is not in the current Parallax preview.' + : 'That address does not match a Parallax page.'} +

+
+ Explore issues + Home +
+
+
+ ); +} diff --git a/src/pages/issue.tsx b/src/pages/issue.tsx index 9a387ff..1315149 100644 --- a/src/pages/issue.tsx +++ b/src/pages/issue.tsx @@ -1,10 +1,11 @@ import { useState } from 'react'; -import { Link, Navigate, useNavigate, useParams } from 'react-router-dom'; +import { Link, useNavigate, useParams } from 'react-router-dom'; import { ArrowUpRight, Check, GitPullRequest, Lock } from 'lucide-react'; import { applicantName, formatMoney, isOwnApplication, repoName, statusTone } from '../lib/model'; import { PLATFORM } from '../lib/platform'; import { useApp } from '../lib/store'; import { Avatar, Chip, Crumbs, Empty, ModalHost, Page } from '../components/ui'; +import { NotFound } from '../components/NotFound'; export function IssuePage() { const { issueId } = useParams(); @@ -15,7 +16,7 @@ export function IssuePage() { const [err, setErr] = useState(''); const issue = state.issues.find(i => i.id === issueId); - if (!issue) return ; + if (!issue) return ; const repo = state.repos.find(r => r.id === issue.repoId)!; const me = state.session.contributor; diff --git a/src/styles/pages.css b/src/styles/pages.css index bd4552d..824051e 100644 --- a/src/styles/pages.css +++ b/src/styles/pages.css @@ -127,6 +127,10 @@ a.row-title:hover { text-decoration: underline; text-underline-offset: 2px; } ============================================================ */ .detail-grid { display: grid; grid-template-columns: minmax(0, 1fr) 280px; gap: var(--s7); align-items: start; } .detail-head { padding-bottom: var(--s4); border-bottom: 1px solid var(--line); margin-bottom: var(--s5); } +.not-found-page { min-height: 65vh; display: grid; place-items: center; padding: var(--s8) var(--s4); } +.not-found-card { width: min(100%, 520px); padding: var(--s7); border: 1px solid var(--line); border-radius: var(--r4); background: var(--surface); } +.not-found-card h1 { margin: var(--s2) 0; } +.not-found-card > p:not(.label) { margin: 0 0 var(--s5); } .detail-head h1 { margin: var(--s3) 0 var(--s3); font-size: var(--t7); line-height: 1.25; } .prose h2 { font-size: var(--t5); margin-bottom: var(--s2); } .prose h2 + p { font-size: var(--t4); color: var(--ink-2); }