Live demo: blog routing (atoms)
2024
- Demens absconditus depopulo turba tepesco conor contego — January 3, 2024
- Adficio careo animus esse voluntarius adversus — February 8, 2024
- Culpa enim statim desino — July 25, 2024
- Abundans venia advoco color cognomen voluntarius — November 3, 2024
- Ulciscor tandem ancilla ventito — March 3, 2024
- Ipsa crastinus sperno sit quisquam — July 26, 2024
- Excepturi adhaero triumphus adulatio aut sperno — October 23, 2024
- Corona certe suscipio — January 2, 2024
- Arbustum virgo adimpleo — June 28, 2024
- Testimonium vero usitas cunae degero adflicto perspiciatis — September 10, 2024
- Decor suffragium tolero terra abscido arma — December 9, 2024
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}`;
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 });
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>
);
};
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;