Skip to content

Commit adf5cb1

Browse files
committed
feat: helpful 404 pages
1 parent 638d171 commit adf5cb1

2 files changed

Lines changed: 144 additions & 42 deletions

File tree

src/components/docs/DocNavigation.tsx

Lines changed: 53 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
/**
2-
* Document Navigation Component
3-
*
4-
* Displays previous/next navigation links at the bottom of docs
5-
*/
6-
71
import { Link } from 'react-router-dom';
82
import { ChevronLeft, ChevronRight } from 'lucide-react';
93
import { cn } from '@/lib/utils';
@@ -17,44 +11,70 @@ interface DocNavigationProps {
1711
className?: string;
1812
}
1913

14+
function NavCard({
15+
doc,
16+
direction,
17+
projectId,
18+
version,
19+
}: {
20+
doc: DocFile;
21+
direction: 'prev' | 'next';
22+
projectId: string;
23+
version: string;
24+
}) {
25+
const isPrev = direction === 'prev';
26+
27+
return (
28+
<Link
29+
to={`/docs/${projectId}/${version}/${doc.slug}`}
30+
className={cn(
31+
'group flex flex-col w-full h-full p-4 rounded-lg border border-border hover:border-primary/50 hover:bg-muted/50 transition-all',
32+
isPrev ? 'items-start text-left' : 'items-end text-right',
33+
)}
34+
>
35+
<span className="flex items-center gap-1 text-sm text-muted-foreground mb-1">
36+
{isPrev && <ChevronLeft className="h-4 w-4" />}
37+
{isPrev ? 'Previous' : 'Next'}
38+
{!isPrev && <ChevronRight className="h-4 w-4" />}
39+
</span>
40+
<span className="font-medium text-foreground group-hover:text-primary transition-colors">
41+
{doc.frontmatter.title}
42+
</span>
43+
{doc.frontmatter.description && (
44+
<span className="text-sm text-muted-foreground line-clamp-2 mt-1.5">
45+
{doc.frontmatter.description}
46+
</span>
47+
)}
48+
<span className="text-xs text-muted-foreground/50 mt-2">
49+
in {doc.category}
50+
</span>
51+
</Link>
52+
);
53+
}
54+
2055
export function DocNavigation({ prev, next, projectId, version, className }: DocNavigationProps) {
2156
if (!prev && !next) return null;
2257

2358
return (
2459
<div className={cn('grid grid-cols-1 sm:grid-cols-2 gap-4 mt-12 pt-8 border-t border-border', className)}>
25-
{/* Previous Link */}
2660
<div className="w-full">
2761
{prev && (
28-
<Link
29-
to={`/docs/${projectId}/${version}/${prev.slug}`}
30-
className="group flex flex-col items-start w-full h-full p-4 rounded-lg border border-border hover:border-primary/50 hover:bg-muted/50 transition-all"
31-
>
32-
<span className="flex items-center gap-1 text-sm text-muted-foreground mb-1">
33-
<ChevronLeft className="h-4 w-4" />
34-
Previous
35-
</span>
36-
<span className="font-medium text-foreground group-hover:text-primary transition-colors">
37-
{prev.frontmatter.title}
38-
</span>
39-
</Link>
62+
<NavCard
63+
doc={prev}
64+
direction="prev"
65+
projectId={projectId}
66+
version={version}
67+
/>
4068
)}
4169
</div>
42-
43-
{/* Next Link */}
4470
<div className="w-full">
4571
{next && (
46-
<Link
47-
to={`/docs/${projectId}/${version}/${next.slug}`}
48-
className="group flex flex-col items-end w-full h-full p-4 rounded-lg border border-border hover:border-primary/50 hover:bg-muted/50 transition-all text-right"
49-
>
50-
<span className="flex items-center gap-1 text-sm text-muted-foreground mb-1">
51-
Next
52-
<ChevronRight className="h-4 w-4" />
53-
</span>
54-
<span className="font-medium text-foreground group-hover:text-primary transition-colors">
55-
{next.frontmatter.title}
56-
</span>
57-
</Link>
72+
<NavCard
73+
doc={next}
74+
direction="next"
75+
projectId={projectId}
76+
version={version}
77+
/>
5878
)}
5979
</div>
6080
</div>

src/pages/Docs.tsx

Lines changed: 91 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
* - Previous/next navigation
99
*/
1010

11-
import { useParams, Navigate } from 'react-router-dom';
12-
import { Edit, Calendar, AlertCircle } from 'lucide-react';
11+
import { Link, useParams, Navigate } from 'react-router-dom';
12+
import { Edit, Calendar, AlertCircle, ChevronRight } from 'lucide-react';
1313
import { Header } from '@/components/layout/Header';
1414
import { Footer } from '@/components/layout/Footer';
1515
import { Sidebar, MobileSidebar } from '@/components/layout/Sidebar';
@@ -92,7 +92,21 @@ export function Docs() {
9292
return <Navigate to={`/docs/${firstProject.id}/${latest.id}/${firstDoc.slug}`} replace />;
9393
}
9494
}
95-
return <div className="min-h-screen flex flex-col bg-docs"><Header /><div className="flex-1 flex items-center justify-center">No documentation found</div><Footer /></div>;
95+
return (
96+
<div className="min-h-screen flex flex-col bg-docs">
97+
<Header />
98+
<div className="flex-1 container mx-auto px-4 py-20 flex items-center justify-center">
99+
<div className="text-center max-w-md">
100+
<p className="text-5xl font-bold text-muted-foreground/20 mb-4">404</p>
101+
<h1 className="text-2xl font-bold mb-2">Page not found</h1>
102+
<p className="text-muted-foreground">
103+
The page you&apos;re looking for doesn&apos;t exist or has been moved.
104+
</p>
105+
</div>
106+
</div>
107+
<Footer />
108+
</div>
109+
);
96110
}
97111

98112
// Resolve the active version: use the URL segment when valid, otherwise
@@ -103,7 +117,20 @@ export function Docs() {
103117
(version && project.versions.find(v => v.id === version)) || latestVersion;
104118

105119
if (!activeVersion) {
106-
return <div className="min-h-screen flex flex-col bg-docs"><Header /><div className="flex-1 flex items-center justify-center">No documentation found for this project</div><Footer /></div>;
120+
return (
121+
<div className="min-h-screen flex flex-col bg-docs">
122+
<Header />
123+
<div className="flex-1 container mx-auto px-4 py-20 flex items-center justify-center">
124+
<div className="text-center max-w-md">
125+
<h1 className="text-2xl font-bold mb-2">Nothing here yet</h1>
126+
<p className="text-muted-foreground">
127+
The documentation you&apos;re looking for hasn&apos;t been written yet.
128+
</p>
129+
</div>
130+
</div>
131+
<Footer />
132+
</div>
133+
);
107134
}
108135

109136
if (!version || version !== activeVersion.id) {
@@ -112,7 +139,20 @@ export function Docs() {
112139
if (targetSlug) {
113140
return <Navigate to={`/docs/${project.id}/${activeVersion.id}/${targetSlug}`} replace />;
114141
}
115-
return <div className="min-h-screen flex flex-col bg-docs"><Header /><div className="flex-1 flex items-center justify-center">No documentation found for this project</div><Footer /></div>;
142+
return (
143+
<div className="min-h-screen flex flex-col bg-docs">
144+
<Header />
145+
<div className="flex-1 container mx-auto px-4 py-20 flex items-center justify-center">
146+
<div className="text-center max-w-md">
147+
<h1 className="text-2xl font-bold mb-2">Nothing here yet</h1>
148+
<p className="text-muted-foreground">
149+
The documentation you&apos;re looking for hasn&apos;t been written yet.
150+
</p>
151+
</div>
152+
</div>
153+
<Footer />
154+
</div>
155+
);
116156
}
117157

118158
// Redirect to first doc in version if no slug provided.
@@ -121,7 +161,20 @@ export function Docs() {
121161
if (firstDoc) {
122162
return <Navigate to={`/docs/${project.id}/${activeVersion.id}/${firstDoc.slug}`} replace />;
123163
}
124-
return <div className="min-h-screen flex flex-col bg-docs"><Header /><div className="flex-1 flex items-center justify-center">No documentation found for this project</div><Footer /></div>;
164+
return (
165+
<div className="min-h-screen flex flex-col bg-docs">
166+
<Header />
167+
<div className="flex-1 container mx-auto px-4 py-20 flex items-center justify-center">
168+
<div className="text-center max-w-md">
169+
<h1 className="text-2xl font-bold mb-2">Nothing here yet</h1>
170+
<p className="text-muted-foreground">
171+
The documentation you&apos;re looking for hasn&apos;t been written yet.
172+
</p>
173+
</div>
174+
</div>
175+
<Footer />
176+
</div>
177+
);
125178
}
126179

127180
// Get prev/next navigation within the active version.
@@ -201,9 +254,38 @@ export function Docs() {
201254
<div className="h-4 bg-muted rounded w-5/6 animate-pulse" />
202255
</div>
203256
) : !doc ? (
204-
<div className="py-20 text-center">
205-
<p className="text-4xl font-bold mb-4">404</p>
206-
<p className="text-muted-foreground">This page doesn&apos;t exist.</p>
257+
<div className="py-16">
258+
<div className="text-center mb-10">
259+
<p className="text-6xl font-bold text-muted-foreground/20 mb-4">404</p>
260+
<h2 className="text-2xl font-bold mb-2">Page not found</h2>
261+
<p className="text-muted-foreground max-w-md mx-auto">
262+
This documentation page doesn&apos;t exist. Try searching with <kbd className="px-1.5 py-0.5 rounded border bg-muted font-mono text-xs">&larr;&#x2325;K</kbd> or browse the sections below.
263+
</p>
264+
</div>
265+
<div className="max-w-lg mx-auto">
266+
<p className="text-sm font-medium text-muted-foreground mb-4 text-center">
267+
Browse {project.name} documentation
268+
</p>
269+
<div className="space-y-2">
270+
{activeVersion.categories.map(cat => {
271+
const firstDoc = cat.docs[0];
272+
if (!firstDoc) return null;
273+
return (
274+
<Link
275+
key={cat.name}
276+
to={`/docs/${project.id}/${activeVersion.id}/${firstDoc.slug}`}
277+
className="flex items-center gap-3 p-3 rounded-lg border border-border hover:border-primary/50 hover:bg-muted/50 transition-all group"
278+
>
279+
<div className="flex-1 min-w-0">
280+
<p className="font-medium text-sm group-hover:text-primary transition-colors">{cat.name}</p>
281+
<p className="text-xs text-muted-foreground">{cat.docs.length} {cat.docs.length === 1 ? 'doc' : 'docs'}</p>
282+
</div>
283+
<ChevronRight className="h-4 w-4 text-muted-foreground/50 group-hover:text-primary transition-colors shrink-0" />
284+
</Link>
285+
);
286+
})}
287+
</div>
288+
</div>
207289
</div>
208290
) : (
209291
<div key={slug} className="animate-fadein mx-auto max-w-4xl">

0 commit comments

Comments
 (0)