Skip to content

Commit bcff7dd

Browse files
committed
Handle 404
1 parent 81bd35a commit bcff7dd

13 files changed

Lines changed: 152 additions & 28 deletions

File tree

src/App.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { lazy, Suspense, } from 'react'
22
import { Route, Routes, } from 'react-router'
3+
import { AppErrorBoundary, } from '~/components/NotFound'
34
import { buildRoutes, } from '~/route-gen'
45

56
const modules = import.meta.glob<{ default: React.ComponentType }>('./routes/**/[!_]*.tsx',)
@@ -16,13 +17,15 @@ export { routes, }
1617

1718
export default function App() {
1819
return (
19-
<Suspense>
20-
<Routes>
21-
{routes.map((r,) => {
22-
const Page = componentMap.get(r.path,)
23-
return Page ? <Route key={r.path} path={r.path} element={<Page />} /> : null
24-
},)}
25-
</Routes>
26-
</Suspense>
20+
<AppErrorBoundary>
21+
<Suspense>
22+
<Routes>
23+
{routes.map((r,) => {
24+
const Page = componentMap.get(r.path,)
25+
return Page ? <Route key={r.path} path={r.path} element={<Page />} /> : null
26+
},)}
27+
</Routes>
28+
</Suspense>
29+
</AppErrorBoundary>
2730
)
2831
}

src/components/NotFound.tsx

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { Component, type ReactNode, } from 'react'
2+
import BaseLayout from '~/components/BaseLayout'
3+
4+
export class NotFoundError extends Error {
5+
constructor(message?: string,) {
6+
super(message,)
7+
this.name = 'NotFoundError'
8+
}
9+
}
10+
11+
export default function NotFound({ message, }: { message?: string },) {
12+
return (
13+
<div className='flex flex-col items-center justify-center py-24 px-4'>
14+
<p className='text-7xl font-extralight tracking-tight text-ink-muted select-none mb-6'>
15+
404
16+
</p>
17+
<h1 className='text-lg font-medium text-ink mb-2'>
18+
Page not found
19+
</h1>
20+
{message && (
21+
<p className='text-sm text-ink-muted mb-6'>
22+
{message}
23+
</p>
24+
)}
25+
{!message && <div className='mb-6' />}
26+
<a
27+
href='/'
28+
className='text-sm text-ink-muted hover:text-ink transition-colors'
29+
>
30+
&larr; Back to home
31+
</a>
32+
</div>
33+
)
34+
}
35+
36+
interface Props {
37+
children: ReactNode
38+
}
39+
40+
interface State {
41+
error: Error | null
42+
}
43+
44+
export class AppErrorBoundary extends Component<Props, State> {
45+
constructor(props: Props,) {
46+
super(props,)
47+
this.state = { error: null, }
48+
}
49+
50+
static getDerivedStateFromError(error: Error,) {
51+
return { error, }
52+
}
53+
54+
componentDidCatch() {
55+
// Could log to an error service here
56+
}
57+
58+
render() {
59+
if (this.state.error instanceof NotFoundError) {
60+
return (
61+
<BaseLayout>
62+
<NotFound message={this.state.error.message} />
63+
</BaseLayout>
64+
)
65+
}
66+
if (this.state.error) {
67+
return (
68+
<BaseLayout>
69+
<div className='flex flex-col items-center justify-center py-24 px-4'>
70+
<p className='text-5xl font-extralight tracking-tight text-ink-muted select-none mb-6'>
71+
Error
72+
</p>
73+
<h1 className='text-lg font-medium text-ink mb-2'>
74+
Something went wrong
75+
</h1>
76+
<p className='text-sm text-ink-muted mb-6'>
77+
{this.state.error.message}
78+
</p>
79+
<a
80+
href='/'
81+
className='text-sm text-ink-muted hover:text-ink transition-colors'
82+
>
83+
&larr; Back to home
84+
</a>
85+
</div>
86+
</BaseLayout>
87+
)
88+
}
89+
return this.props.children
90+
}
91+
}

src/routes/404.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import BaseLayout from '~/components/BaseLayout'
2+
import NotFound from '~/components/NotFound'
3+
4+
export default function NotFoundPage() {
5+
return (
6+
<BaseLayout>
7+
<NotFound />
8+
</BaseLayout>
9+
)
10+
}

src/routes/[owner]/[collection]/diff.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useEffect, useState, } from 'react'
22
import { useParams, useSearchParams, } from 'react-router'
33
import BaseLayout from '~/components/BaseLayout'
4+
import { NotFoundError, } from '~/components/NotFound'
45
import { useSSRData, } from '~/lib/ssr-data'
56
import { CollectionNav, } from '.'
67

@@ -42,7 +43,7 @@ export default function CollectionDiffPage() {
4243
},).then((r,) => (r.ok ? r.json() : [])),
4344
],).then(([col, vers,],) => {
4445
if (!col) {
45-
window.location.href = '/404'
46+
setLoading(false,)
4647
return
4748
}
4849
setData(col,)
@@ -102,13 +103,14 @@ export default function CollectionDiffPage() {
102103
setSearchParams({ from: String(f,), to: String(t,), },)
103104
}
104105

105-
if (loading || !data) {
106+
if (loading) {
106107
return (
107108
<BaseLayout>
108109
<div className='max-w-5xl mx-auto px-4 py-8 text-sm text-ink-muted'>Loading…</div>
109110
</BaseLayout>
110111
)
111112
}
113+
if (!data) throw new NotFoundError()
112114

113115
const targetVersion = versions.find((v: any,) => v.number === toNum)
114116
const baseVersion = versions.find((v: any,) => v.number === fromNum)

src/routes/[owner]/[collection]/index.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useEffect, useState, } from 'react'
22
import { Link, useParams, } from 'react-router'
33
import BaseLayout from '~/components/BaseLayout'
4+
import { NotFoundError, } from '~/components/NotFound'
45
import { useSSRData, } from '~/lib/ssr-data'
56

67
function CollectionNav({
@@ -99,7 +100,7 @@ export default function CollectionPage() {
99100
.then((r,) => (r.ok ? r.json() : null))
100101
.then((col,) => {
101102
if (!col) {
102-
window.location.href = '/404'
103+
setLoading(false,)
103104
return
104105
}
105106
setData(col,)
@@ -125,13 +126,14 @@ export default function CollectionPage() {
125126
},)
126127
}, [owner, collection, currentUser,],)
127128

128-
if (loading || !data) {
129+
if (loading) {
129130
return (
130131
<BaseLayout>
131132
<div className='max-w-5xl mx-auto px-4 py-8 text-sm text-ink-muted'>Loading…</div>
132133
</BaseLayout>
133134
)
134135
}
136+
if (!data) throw new NotFoundError()
135137

136138
const typeCounts: { type: string; count: number }[] = data.latestVersion?.typeCounts ?? []
137139
const allTypes = typeCounts.sort((a: any, b: any,) => a.type.localeCompare(b.type,))

src/routes/[owner]/[collection]/schemas.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { type FormEvent, useEffect, useState, } from 'react'
22
import { Link, useParams, } from 'react-router'
33
import BaseLayout from '~/components/BaseLayout'
4+
import { NotFoundError, } from '~/components/NotFound'
45
import { useSSRData, } from '~/lib/ssr-data'
56
import { CollectionNav, } from '.'
67

@@ -39,7 +40,7 @@ export default function CollectionSchemasPage() {
3940
: Promise.resolve([],),
4041
],).then(([col, sd, arkTypes,],) => {
4142
if (!col) {
42-
window.location.href = '/404'
43+
setLoading(false,)
4344
return
4445
}
4546
setData(col,)
@@ -87,13 +88,14 @@ export default function CollectionSchemasPage() {
8788
}
8889
}
8990

90-
if (loading || !data) {
91+
if (loading) {
9192
return (
9293
<BaseLayout>
9394
<div className='max-w-5xl mx-auto px-4 py-8 text-sm text-ink-muted'>Loading…</div>
9495
</BaseLayout>
9596
)
9697
}
98+
if (!data) throw new NotFoundError()
9799

98100
return (
99101
<BaseLayout>

src/routes/[owner]/[collection]/settings.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { type FormEvent, useEffect, useState, } from 'react'
22
import { Link, useParams, } from 'react-router'
33
import BaseLayout from '~/components/BaseLayout'
4+
import { NotFoundError, } from '~/components/NotFound'
45
import { useSSRData, } from '~/lib/ssr-data'
56
import { CollectionNav, } from '.'
67

@@ -49,7 +50,7 @@ export default function CollectionSettingsPage() {
4950
),
5051
],).then(([col, ark,],) => {
5152
if (!col) {
52-
window.location.href = '/404'
53+
setLoading(false,)
5354
return
5455
}
5556
setData(col,)
@@ -154,13 +155,14 @@ export default function CollectionSettingsPage() {
154155
}
155156
}
156157

157-
if (loading || !data) {
158+
if (loading) {
158159
return (
159160
<BaseLayout>
160161
<div className='max-w-5xl mx-auto px-4 py-8 text-sm text-ink-muted'>Loading…</div>
161162
</BaseLayout>
162163
)
163164
}
165+
if (!data) throw new NotFoundError()
164166

165167
const arkPath: string | null = arkSettings.arkUrl
166168
? new URL(arkSettings.arkUrl,).pathname

src/routes/[owner]/[collection]/v/[n].tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useEffect, useState, } from 'react'
22
import { Link, useParams, useSearchParams, } from 'react-router'
33
import BaseLayout from '~/components/BaseLayout'
4+
import { NotFoundError, } from '~/components/NotFound'
45
import { useSSRData, } from '~/lib/ssr-data'
56
import { CollectionNav, formatBytes, } from '..'
67

@@ -38,7 +39,7 @@ export default function CollectionVersionPage() {
3839
),
3940
],).then(([ver, col,],) => {
4041
if (!ver) {
41-
window.location.href = '/404'
42+
setLoading(false,)
4243
return
4344
}
4445
setVersion(ver,)
@@ -97,13 +98,14 @@ export default function CollectionVersionPage() {
9798
.then(setFiles,)
9899
}, [version, tab, owner, collection, n,],)
99100

100-
if (loading || !version) {
101+
if (loading) {
101102
return (
102103
<BaseLayout>
103104
<div className='max-w-5xl mx-auto px-4 py-8 text-sm text-ink-muted'>Loading…</div>
104105
</BaseLayout>
105106
)
106107
}
108+
if (!version) throw new NotFoundError()
107109

108110
const schemasMap = (version.schemas ?? {}) as Record<string, any>
109111
const allTypes = Object.keys(schemasMap,).sort()

src/routes/[owner]/[collection]/versions.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useEffect, useState, } from 'react'
22
import { Link, useParams, } from 'react-router'
33
import BaseLayout from '~/components/BaseLayout'
4+
import { NotFoundError, } from '~/components/NotFound'
45
import { useSSRData, } from '~/lib/ssr-data'
56
import { CollectionNav, formatBytes, } from '.'
67

@@ -25,7 +26,7 @@ export default function CollectionVersionsPage() {
2526
},).then((r,) => (r.ok ? r.json() : [])),
2627
],).then(([col, vers,],) => {
2728
if (!col) {
28-
window.location.href = '/404'
29+
setLoading(false,)
2930
return
3031
}
3132
setData(col,)
@@ -42,13 +43,14 @@ export default function CollectionVersionsPage() {
4243
},)
4344
}, [owner, collection, currentUser,],)
4445

45-
if (loading || !data) {
46+
if (loading) {
4647
return (
4748
<BaseLayout>
4849
<div className='max-w-5xl mx-auto px-4 py-8 text-sm text-ink-muted'>Loading…</div>
4950
</BaseLayout>
5051
)
5152
}
53+
if (!data) throw new NotFoundError()
5254

5355
return (
5456
<BaseLayout>

src/routes/[owner]/index.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useEffect, useState, } from 'react'
22
import { Link, useParams, } from 'react-router'
33
import BaseLayout from '~/components/BaseLayout'
4+
import { NotFoundError, } from '~/components/NotFound'
45
import { useSSRData, } from '~/lib/ssr-data'
56

67
export default function OwnerPage() {
@@ -22,7 +23,7 @@ export default function OwnerPage() {
2223
fetch(`/api/accounts/${owner}/collections`, { credentials: 'include', },).then((r,) => r.ok ? r.json() : []),
2324
],).then(([acct, cols,],) => {
2425
if (!acct) {
25-
window.location.href = '/404'
26+
setLoading(false,)
2627
return
2728
}
2829
setAccount(acct,)
@@ -42,13 +43,14 @@ export default function OwnerPage() {
4243
},)
4344
}, [owner, currentUser,],)
4445

45-
if (loading || !account) {
46+
if (loading) {
4647
return (
4748
<BaseLayout>
4849
<div className='max-w-5xl mx-auto px-4 py-8 text-sm text-ink-muted'>Loading…</div>
4950
</BaseLayout>
5051
)
5152
}
53+
if (!account) throw new NotFoundError()
5254

5355
const totalVersions = collections.reduce(
5456
(sum: number, c: any,) => sum + (c.versionCount ?? 0),

0 commit comments

Comments
 (0)