Add NotFound page for virtual 404s inside the SPA
All checks were successful
/ image_build (push) Successful in 1m19s
All checks were successful
/ image_build (push) Successful in 1m19s
This commit is contained in:
52
src/404/404.sass
Normal file
52
src/404/404.sass
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
@use "../about/about"
|
||||||
|
|
||||||
|
|
||||||
|
.not-found
|
||||||
|
background: about.$reader-bg
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
flex-grow: 1
|
||||||
|
justify-content: center
|
||||||
|
align-items: center
|
||||||
|
h1,h2,h3,h4
|
||||||
|
font-family: Neuton, serif
|
||||||
|
|
||||||
|
.nf-ctr
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
|
||||||
|
h1,h2,h3,p
|
||||||
|
text-align: center
|
||||||
|
margin: 0
|
||||||
|
|
||||||
|
.nf-title
|
||||||
|
background: white
|
||||||
|
color: black
|
||||||
|
border-radius: 15px 15px 0 0
|
||||||
|
padding: 15px
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
h1
|
||||||
|
margin: 10px 0
|
||||||
|
font-size: 100px
|
||||||
|
h3
|
||||||
|
font-size: 30px
|
||||||
|
margin-bottom: 10px
|
||||||
|
|
||||||
|
.nf-desc
|
||||||
|
background: black
|
||||||
|
color: white
|
||||||
|
padding: 15px
|
||||||
|
border-radius: 0 0 15px 15px
|
||||||
|
|
||||||
|
.routes-list
|
||||||
|
padding: 15px
|
||||||
|
display: flex
|
||||||
|
flex-direction: row
|
||||||
|
flex-wrap: wrap
|
||||||
|
gap: 15px
|
||||||
|
justify-content: center
|
||||||
|
align-items: center
|
||||||
|
a
|
||||||
|
font-size: 20px
|
||||||
|
text-decoration: underline
|
||||||
35
src/404/NotFound.tsx
Normal file
35
src/404/NotFound.tsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import "./404.sass";
|
||||||
|
import { ROUTES } from "../router/Route";
|
||||||
|
import { Link } from "../router/Link";
|
||||||
|
import { motion } from "motion/react";
|
||||||
|
import { AnimateSpin, AnimateFade } from "../animations";
|
||||||
|
import { type Path } from "../router/router";
|
||||||
|
|
||||||
|
export default function NotFoundPage() {
|
||||||
|
return <motion.main className="not-found" {...AnimateFade()}>
|
||||||
|
<motion.section {...AnimateSpin()} className="nf-ctr">
|
||||||
|
<div className="nf-title">
|
||||||
|
<h1>404</h1>
|
||||||
|
<h3>This page was not found</h3>
|
||||||
|
</div>
|
||||||
|
<div className="nf-desc">
|
||||||
|
<p>Did you mistype the URL? Here are a list of common links</p>
|
||||||
|
<section className="routes-list">
|
||||||
|
{
|
||||||
|
ROUTES.map(
|
||||||
|
([path,name]) => <LinkBox key={name} path={path} name={name}/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</motion.section>
|
||||||
|
</motion.main>
|
||||||
|
}
|
||||||
|
|
||||||
|
function LinkBox({path, name}: {path: Path, name: string}) {
|
||||||
|
return (
|
||||||
|
<Link key={path} href={path}>
|
||||||
|
<div>{name}</div>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -47,7 +47,6 @@
|
|||||||
border-radius: 0 15px 0 0
|
border-radius: 0 15px 0 0
|
||||||
padding: 10px
|
padding: 10px
|
||||||
font-weight: bold
|
font-weight: bold
|
||||||
font-family: Neuton, serif
|
|
||||||
border-bottom: white 2px solid
|
border-bottom: white 2px solid
|
||||||
h2
|
h2
|
||||||
margin: 0
|
margin: 0
|
||||||
@@ -55,9 +54,11 @@
|
|||||||
.link-desc
|
.link-desc
|
||||||
background: black
|
background: black
|
||||||
color: white
|
color: white
|
||||||
font-family: Inter, sans-serif
|
padding: 15px
|
||||||
padding: 10px
|
|
||||||
flex-grow: 1
|
flex-grow: 1
|
||||||
justify-content: center
|
justify-content: center
|
||||||
align-items: center
|
align-items: center
|
||||||
border-radius: 0 0 15px 0
|
border-radius: 0 0 15px 0
|
||||||
|
|
||||||
|
font-family: Neuton, serif
|
||||||
|
font-size: 20px
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, {PropsWithChildren} from "react";
|
import React, {type PropsWithChildren, ReactElement} from "react";
|
||||||
import {useRouter} from "./RouterContext";
|
import {useRouter} from "./RouterContext";
|
||||||
import {Path} from "./router";
|
import {Path} from "./router";
|
||||||
|
|
||||||
type Route = [Path, string];
|
export type Route = [Path, string];
|
||||||
type Routes = [Route, Route, Route, Route];
|
type Routes = [Route, Route, Route, Route];
|
||||||
export const ROUTES: Routes = [
|
export const ROUTES: Routes = [
|
||||||
["/", "Home"],
|
["/", "Home"],
|
||||||
@@ -11,7 +11,8 @@ export const ROUTES: Routes = [
|
|||||||
["/links", "Links"]
|
["/links", "Links"]
|
||||||
];
|
];
|
||||||
|
|
||||||
function Route(props: PropsWithChildren & {route: Route}) {
|
type RouteProps = PropsWithChildren & {route: Route};
|
||||||
|
function Route(props: RouteProps) {
|
||||||
const {route: [path], children} = props;
|
const {route: [path], children} = props;
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
// remove search params
|
// remove search params
|
||||||
@@ -25,11 +26,26 @@ const Canvas = React.lazy(() => import("../index/Canvas"));
|
|||||||
const About = React.lazy(() => import("../about/AboutPage"));
|
const About = React.lazy(() => import("../about/AboutPage"));
|
||||||
const ArticlesList = React.lazy(() => import("../articles/ArticlesList"));
|
const ArticlesList = React.lazy(() => import("../articles/ArticlesList"));
|
||||||
const LinkTree = React.lazy(() => import("../links/LinkTree"));
|
const LinkTree = React.lazy(() => import("../links/LinkTree"));
|
||||||
|
const NotFound = React.lazy(() => import("../404/NotFound"));
|
||||||
|
|
||||||
|
// ensures only one route is rendered at a time, with a virtual 404 page as fallback
|
||||||
|
function RouteSwitch({children}: {children: ReactElement<RouteProps, typeof Route>[]}) {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const child = children.find(child => {
|
||||||
|
const [expected] = child.props.route;
|
||||||
|
const [current] = /\/?\w*[^?]/.exec(router.current) ?? [location.pathname];
|
||||||
|
return current === expected
|
||||||
|
});
|
||||||
|
|
||||||
|
return <>{child || <NotFound/>}</>
|
||||||
|
}
|
||||||
|
|
||||||
export function Routes() {
|
export function Routes() {
|
||||||
const [index, about, articles, links] = ROUTES;
|
const [index, about, articles, links] = ROUTES;
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
|
<RouteSwitch>
|
||||||
<Route route={index}>
|
<Route route={index}>
|
||||||
<Canvas/>
|
<Canvas/>
|
||||||
</Route>
|
</Route>
|
||||||
@@ -42,5 +58,6 @@ export function Routes() {
|
|||||||
<Route route={links}>
|
<Route route={links}>
|
||||||
<LinkTree/>
|
<LinkTree/>
|
||||||
</Route>
|
</Route>
|
||||||
|
</RouteSwitch>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user