Cogito rem viriliter
August 5, 2023
Creptio vomica vinum deludo agnosco thermae concedo trepide creo. Tenuis considero voveo.
August 5, 2023
Creptio vomica vinum deludo agnosco thermae concedo trepide creo. Tenuis considero voveo.
import { rootRouteAtom, numericRouteAtom, paramRouteAtom, validateRouteAtom } from "jarl-atoms";
import { Link, Route, Switch } from "jarl-react";
import {
BlogPost,
isValidCalendarDate,
postBySlug,
postsForDay,
postsForMonth,
postsForYear,
daysInMonth,
monthsInYear,
yearsWithPosts,
} from "./blogPosts";
const MONTH_NAMES = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
];
const formatDate = (post: BlogPost) => `${MONTH_NAMES[post.month - 1]} ${post.day}, ${post.year}`;
// The page this demo is mounted on, so its whole tree below is plain module-level atoms.
const blogRoot = rootRouteAtom({ basePath: "/demos/blog-routing" });
const yearRoute = numericRouteAtom("year", { parent: blogRoot });
const monthRoute = numericRouteAtom("month", { parent: yearRoute, min: 1, max: 12 });
const daySegment = numericRouteAtom("day", { parent: monthRoute });
// A segment's own min/max only bounds it in isolation; a real calendar date needs all three
// together, so the whole date is validated as part of matching rather than in a page component.
const dayRoute = validateRouteAtom(daySegment, ({ year, month, day }) => isValidCalendarDate(year, month, day));
const postRoute = paramRouteAtom("slug", { parent: dayRoute });
const BlogNav = () => (
<nav>
<Link route={blogRoot} to={{}} exact>
All posts
</Link>
</nav>
);
const BlogNotFound = ({ reason }: { reason: string }) => (
<div>
<h3>Not found</h3>
<p>{reason}</p>
<p>
<Link route={blogRoot} to={{}}>
Back to all posts
</Link>
</p>
</div>
);
const PostList = ({ posts }: { posts: BlogPost[] }) => (
<ul>
{posts.map((post) => (
<li key={post.slug}>
<Link route={postRoute} to={post}>
{post.title}
</Link>{" "}
<span>— {formatDate(post)}</span>
</li>
))}
</ul>
);
const BlogIndex = () => (
<div>
<h3>Blog</h3>
<ul>
{yearsWithPosts().map((year) => (
<li key={year}>
<Link route={yearRoute} to={{ year }}>
{year}
</Link>{" "}
({postsForYear(year).length} posts)
</li>
))}
</ul>
</div>
);
const YearPage = ({ year }: { year: number }) => {
const posts = postsForYear(year);
if (posts.length === 0) {
return <BlogNotFound reason={`No posts in ${year}.`} />;
}
return (
<div>
<h3>{year}</h3>
<ul>
{monthsInYear(year).map((month) => (
<li key={month}>
<Link route={monthRoute} to={{ year, month }}>
{MONTH_NAMES[month - 1]}
</Link>{" "}
({postsForMonth(year, month).length})
</li>
))}
</ul>
<PostList posts={posts} />
</div>
);
};
const MonthPage = ({ year, month }: { year: number; month: number }) => {
const posts = postsForMonth(year, month);
if (posts.length === 0) {
return <BlogNotFound reason={`No posts in ${MONTH_NAMES[month - 1]} ${year}.`} />;
}
return (
<div>
<h3>
{MONTH_NAMES[month - 1]} {year}
</h3>
<ul>
{daysInMonth(year, month).map((day) => (
<li key={day}>
<Link route={dayRoute} to={{ year, month, day }}>
{day}
</Link>{" "}
({postsForDay(year, month, day).length})
</li>
))}
</ul>
<PostList posts={posts} />
</div>
);
};
const DayPage = ({ year, month, day }: { year: number; month: number; day: number }) => {
const posts = postsForDay(year, month, day);
if (posts.length === 0) {
return <BlogNotFound reason={`No posts on ${MONTH_NAMES[month - 1]} ${day}, ${year}.`} />;
}
return (
<div>
<h3>
{MONTH_NAMES[month - 1]} {day}, {year}
</h3>
<PostList posts={posts} />
</div>
);
};
const PostPage = ({ year, month, day, slug }: { year: number; month: number; day: number; slug: string }) => {
const post = postBySlug(year, month, day, slug);
if (!post) {
return <BlogNotFound reason="No post at this address." />;
}
return (
<article>
<h3>{post.title}</h3>
<p>
<em>{formatDate(post)}</em>
</p>
<p>{post.excerpt}</p>
</article>
);
};
/**
* Self-contained demo of a classic /blog/:year/:month/:day/:slug tree: URL-shape 404s (an
* impossible date included) via the Switch fallback, content-level 404s via empty-list checks.
*/
export const BlogRoutingApp = () => (
<>
<BlogNav />
<Switch fallback={<BlogNotFound reason="No blog route matches this address." />}>
<Route on={blogRoot} exact>
<BlogIndex />
</Route>
<Route on={yearRoute} exact>
{({ year }) => <YearPage year={year} />}
</Route>
<Route on={monthRoute} exact>
{({ year, month }) => <MonthPage year={year} month={month} />}
</Route>
<Route on={dayRoute} exact>
{({ year, month, day }) => <DayPage year={year} month={month} day={day} />}
</Route>
<Route on={postRoute} exact>
{({ year, month, day, slug }) => <PostPage year={year} month={month} day={day} slug={slug} />}
</Route>
</Switch>
</>
);
export default BlogRoutingApp;