91 lines
2.8 KiB
TypeScript
91 lines
2.8 KiB
TypeScript
/* ═══════════════════════════════════════════════════════════════
|
||
PAGE STATE
|
||
|
||
The three ways a detail page can fail to be a detail page. All
|
||
four of them need the same thing, and it should be the same thing
|
||
— a visitor who hits a dead retreat link and a dead chapter link
|
||
shouldn't get two different pages.
|
||
|
||
Rendered inside PageShell so the chrome doesn't flicker in and
|
||
out between loading and loaded.
|
||
|
||
A 404 gets no retry button: the slug doesn't exist and trying
|
||
again won't change that. Anything else does, because a dropped
|
||
connection is the usual cause and one tap fixes it.
|
||
═══════════════════════════════════════════════════════════════ */
|
||
|
||
import { Link } from 'react-router-dom'
|
||
import PageShell from './PageShell.tsx'
|
||
|
||
const TEAL = '#138ba0'
|
||
const BODY = '#4a6b72'
|
||
|
||
export default function PageState({
|
||
loading,
|
||
error,
|
||
notFound,
|
||
onRetry,
|
||
noun,
|
||
backTo,
|
||
backLabel,
|
||
}: {
|
||
loading: boolean
|
||
error: string | null
|
||
notFound: boolean
|
||
onRetry: () => void
|
||
/** Lowercase, as it appears mid-sentence: "retreat", "chapter". */
|
||
noun: string
|
||
backTo: string
|
||
backLabel: string
|
||
}) {
|
||
let title: string
|
||
let content: React.ReactNode
|
||
|
||
if (notFound) {
|
||
title = 'Not found'
|
||
content = (
|
||
<div className="max-w-6xl mx-auto px-6">
|
||
<p style={{ color: BODY }}>
|
||
There’s no {noun} at this address. It may have been renamed, or taken
|
||
down.
|
||
</p>
|
||
<Link
|
||
to={backTo}
|
||
className="mt-4 inline-block rounded-full border px-4 py-1.5 text-sm font-medium transition-colors hover:bg-[#eef9fb]"
|
||
style={{ borderColor: TEAL, color: TEAL }}
|
||
>
|
||
{backLabel}
|
||
</Link>
|
||
</div>
|
||
)
|
||
} else if (error) {
|
||
title = 'Something went wrong'
|
||
content = (
|
||
<div className="max-w-6xl mx-auto px-6">
|
||
<p style={{ color: '#b3261e' }}>Couldn’t load this {noun}. {error}</p>
|
||
<button
|
||
type="button"
|
||
onClick={onRetry}
|
||
className="mt-3 rounded-full border px-4 py-1.5 text-sm font-medium transition-colors hover:bg-[#eef9fb]"
|
||
style={{ borderColor: TEAL, color: TEAL }}
|
||
>
|
||
Try again
|
||
</button>
|
||
</div>
|
||
)
|
||
} else {
|
||
title = 'Loading…'
|
||
content = (
|
||
<p className="max-w-6xl mx-auto px-6" style={{ color: BODY }} role="status">
|
||
Loading this {noun}…
|
||
</p>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<PageShell
|
||
title={title}
|
||
sections={[{ id: 'status', title: '', accent: TEAL, background: '#ffffff', content }]}
|
||
/>
|
||
)
|
||
}
|