Corona certe suscipio
January 2, 2024
Vetus verto summa. Carcer vorax facere laborum adflicto debitis contigo.
January 2, 2024
Vetus verto summa. Carcer vorax facere laborum adflicto debitis contigo.
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;