diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx index 0c08cf9..0f5e04d 100644 --- a/src/app/not-found.tsx +++ b/src/app/not-found.tsx @@ -1,20 +1,11 @@ -'use client'; - -import Link from 'next/link'; -import { motion } from 'framer-motion'; -import { ArrowLeft, Leaf, Telescope } from 'lucide-react'; import Header from '@/components/Header'; -import SearchBox from '@/components/explore/SearchBox'; +import NotFoundPanel from '@/components/NotFoundPanel'; /** - * Global 404 page. Next.js falls back to its plain-text default when this - * file is absent; this version mirrors the site's hero treatment so a - * mistyped URL doesn't read as a regression to a different product. - * - * Bias toward "give the visitor a way out": leaf-anchored eyebrow, big - * serif headline, two-line explainer, the explorer's SearchBox (so a - * fat-fingered handle is one re-keystroke away from working), and a Home - * button next to a /explore link. + * Global 404 page. Lives at src/app/not-found.tsx so it covers every + * Next.js 404 — the notFound() helper, missing dynamic routes, mistyped + * paths. Shares NotFoundPanel with the client-side resolve-error states + * inside the explorer so every dead end reads the same. */ export default function NotFound() { return ( @@ -22,121 +13,7 @@ export default function NotFound() {
- - - {/* Eyebrow */} -
- - 404 · Not found -
- -

- - Lost in the Atmosphere. - -

- -

- The path you tried didn’t resolve. Search for a handle, DID, or - AT URI to keep looking — or head home. -

- - {/* Search */} -
- -
- - {/* Quick actions */} -
- - Home - - - Explore - -
-
+ ); } - -const primaryBtn: React.CSSProperties = { - display: 'inline-flex', - alignItems: 'center', - gap: '0.4rem', - padding: '0.55rem 1rem', - background: 'var(--accent-moss)', - color: 'var(--text-on-accent)', - border: '1px solid var(--accent-moss)', - fontFamily: 'var(--font-serif)', - fontSize: '0.875rem', - textDecoration: 'none', -}; - -const ghostBtn: React.CSSProperties = { - display: 'inline-flex', - alignItems: 'center', - gap: '0.4rem', - padding: '0.55rem 1rem', - background: 'var(--bg-tertiary)', - color: 'var(--text-primary)', - border: '1px solid var(--border-medium)', - fontFamily: 'var(--font-serif)', - fontSize: '0.875rem', - textDecoration: 'none', -}; diff --git a/src/components/NotFoundPanel.tsx b/src/components/NotFoundPanel.tsx new file mode 100644 index 0000000..7e40d28 --- /dev/null +++ b/src/components/NotFoundPanel.tsx @@ -0,0 +1,148 @@ +'use client'; + +import Link from 'next/link'; +import { motion } from 'framer-motion'; +import { ArrowLeft, Leaf, Telescope } from 'lucide-react'; +import SearchBox from '@/components/explore/SearchBox'; + +type Props = { + /** Small uppercase chip text. Defaults to "404 · NOT FOUND". */ + eyebrow?: string; + /** Big gradient-serif headline. */ + headline?: string; + /** Body copy under the headline. */ + body?: string; + /** + * Optional pre-filled value for the inline explorer SearchBox — useful + * on resolve-error pages where the visitor is likely about to retry + * what they just typed. + */ + initialQuery?: string; +}; + +/** + * Shared "we couldn't find what you asked for" panel. Used by the global + * Next.js not-found route AND by client-side resolve-error states inside + * the explorer (handle didn't resolve, record didn't load, etc.) so every + * dead end gets the same recovery affordance: the explorer SearchBox + * inline, plus Home + Explore buttons. + */ +export default function NotFoundPanel({ + eyebrow = '404 · Not found', + headline = 'Lost in the Atmosphere.', + body = "The path you tried didn't resolve. Search for a handle, DID, or AT URI to keep looking — or head home.", + initialQuery, +}: Props) { + return ( + +
+ + {eyebrow} +
+ +

+ + {headline} + +

+ +

+ {body} +

+ +
+ +
+ +
+ + Home + + + Explore + +
+
+ ); +} + +const primaryBtn: React.CSSProperties = { + display: 'inline-flex', + alignItems: 'center', + gap: '0.4rem', + padding: '0.55rem 1rem', + background: 'var(--accent-moss)', + color: 'var(--text-on-accent)', + border: '1px solid var(--accent-moss)', + fontFamily: 'var(--font-serif)', + fontSize: '0.875rem', + textDecoration: 'none', +}; + +const ghostBtn: React.CSSProperties = { + display: 'inline-flex', + alignItems: 'center', + gap: '0.4rem', + padding: '0.55rem 1rem', + background: 'var(--bg-tertiary)', + color: 'var(--text-primary)', + border: '1px solid var(--border-medium)', + fontFamily: 'var(--font-serif)', + fontSize: '0.875rem', + textDecoration: 'none', +}; diff --git a/src/components/explore/CollectionExplorer.tsx b/src/components/explore/CollectionExplorer.tsx index adf73bb..1569bb4 100644 --- a/src/components/explore/CollectionExplorer.tsx +++ b/src/components/explore/CollectionExplorer.tsx @@ -14,6 +14,7 @@ import { } from '@/utils/atproto/jetstream'; import AppearIn from './AppearIn'; import Breadcrumb from './Breadcrumb'; +import NotFoundPanel from '@/components/NotFoundPanel'; type Props = { repo: string; @@ -41,7 +42,14 @@ export default function CollectionExplorer({ repo, collection }: Props) { }, [repo]); if (identityError) { - return

{identityError}

; + return ( + + ); } if (!identity) { return ( diff --git a/src/components/explore/RecordExplorer.tsx b/src/components/explore/RecordExplorer.tsx index a41fc75..394bd41 100644 --- a/src/components/explore/RecordExplorer.tsx +++ b/src/components/explore/RecordExplorer.tsx @@ -15,6 +15,7 @@ import RichRecordPreview, { previewRendersGeneric } from './RichRecordPreview'; import BacklinksTab from './tabs/BacklinksTab'; import RecordEditor from './RecordEditor'; import SignInPanel from './SignInPanel'; +import NotFoundPanel from '@/components/NotFoundPanel'; import { useAtprotoSession } from '@/components/AtprotoSessionProvider'; type Props = { @@ -78,7 +79,14 @@ export default function RecordExplorer({ repo, collection, rkey }: Props) { }, [identity, collection, decodedRkey]); if (identityError) { - return

{identityError}

; + return ( + + ); } if (!identity) { return ( diff --git a/src/components/explore/RepoExplorer.tsx b/src/components/explore/RepoExplorer.tsx index feb7134..31d7d44 100644 --- a/src/components/explore/RepoExplorer.tsx +++ b/src/components/explore/RepoExplorer.tsx @@ -15,6 +15,7 @@ import CollectionsTab from './tabs/CollectionsTab'; import IdentityTab from './tabs/IdentityTab'; import AuditTab from './tabs/AuditTab'; import BacklinksTab from './tabs/BacklinksTab'; +import NotFoundPanel from '@/components/NotFoundPanel'; const TABS = [ { id: 'collections', label: 'Lexicons' }, @@ -47,13 +48,12 @@ export default function RepoExplorer({ repo }: { repo: string }) { if (error) { return ( -
-

{error}

-

- Try a handle (aturi.to), a DID (did:plc:…), or an{' '} - at:// URI. -

-
+ ); } if (!identity) {