+1
-1
react/package.json
+1
-1
react/package.json
+3
-18
react/src/App.tsx
+3
-18
react/src/App.tsx
···
1
1
import { posts } from "./lib/post";
2
-
import { Entry } from "./components/Entry";
2
+
import { BlogPostList } from "./components/BlogPostList";
3
3
4
4
export function App() {
5
5
return (
6
6
<>
7
-
<div className="w-lvw h-lvh p-5 flex flex-col items-center gap-10">
7
+
<div className="w-screen p-5 flex flex-col items-center gap-10">
8
8
<h1 className="text-5xl font-bold">Posts</h1>
9
-
<div className="flex flex-col items-center justify-center gap-4">
10
-
{posts
11
-
.sort(
12
-
(a, b) =>
13
-
new Date(a.datePosted).getTime() -
14
-
new Date(b.datePosted).getTime(),
15
-
)
16
-
.map((post, idx) => (
17
-
<Entry
18
-
key={idx}
19
-
idx={idx}
20
-
title={post.title}
21
-
datePosted={post.datePosted}
22
-
/>
23
-
))}
24
-
</div>
9
+
<BlogPostList posts={posts} />
25
10
</div>
26
11
</>
27
12
);
+46
react/src/components/BlogPostDetail.tsx
+46
react/src/components/BlogPostDetail.tsx
···
1
+
import { useParams, Outlet } from "react-router";
2
+
import { posts } from "../lib/post";
3
+
import { Link } from "react-router";
4
+
5
+
export function BlogPostDetail() {
6
+
const { postId } = useParams();
7
+
const post = posts[parseInt(postId!)];
8
+
9
+
const formattedDate = new Date(post.datePosted).toLocaleDateString("en-US", {
10
+
month: "long",
11
+
day: "numeric",
12
+
year: "numeric",
13
+
});
14
+
return (
15
+
<>
16
+
<div className="md:grid md:grid-cols-3 flex flex-col w-full">
17
+
<Link
18
+
to="/"
19
+
className="text-gray-700 dark:text-gray-200 hover:text-gray-400 flex justify-center items-center w-16 mb-4 md:mb-0"
20
+
>
21
+
Home
22
+
</Link>
23
+
<h1 className="text-3xl md:text-4xl font-bold text-center">
24
+
{post.title}
25
+
</h1>
26
+
</div>
27
+
<div className="flex flex-col gap-1 md:gap-2.5 justify-center items-center mb-1.5 md:mb-2.5">
28
+
<p className="text-gray-700 dark:text-gray-400 text-sm md:text-lg">
29
+
By: {post.author}
30
+
</p>
31
+
<p className="text-gray-600 dark:text-gray-500 text-xs md:text-base">
32
+
Published on {formattedDate}
33
+
</p>
34
+
</div>
35
+
<div className="text-sm md:text-lg md:w-3xl w-full">{post.content}</div>
36
+
</>
37
+
);
38
+
}
39
+
40
+
export function PostLayout() {
41
+
return (
42
+
<div className="flex flex-col justify-center gap-3.5 md:gap-5 items-center p-5 w-screen">
43
+
<Outlet />
44
+
</div>
45
+
);
46
+
}
+33
react/src/components/BlogPostItem.tsx
+33
react/src/components/BlogPostItem.tsx
···
1
+
import { Link } from "react-router";
2
+
3
+
export function BlogPostItem({
4
+
title,
5
+
idx,
6
+
datePosted,
7
+
summary,
8
+
}: {
9
+
title: string;
10
+
idx: number;
11
+
datePosted: string;
12
+
summary: string;
13
+
}) {
14
+
return (
15
+
<>
16
+
<Link to={`/entries/${idx}`}>
17
+
<div className="border border-gray-300 p-3.5 md:p-5 rounded-md flex flex-col gap justify-center items-center max-w-lg">
18
+
<div className="flex flex-row gap-4 justify-center items-center">
19
+
<p className="text-gray-500">#{idx + 1}</p>
20
+
<h2 className="text-xl md:text-2xl dark:text-gray-300 text-gray-900 font-bold ">
21
+
{title}
22
+
</h2>
23
+
</div>
24
+
<p className="text-gray-500 text-sm">Posted on {datePosted}</p>
25
+
<div className="h-3" />
26
+
<p className="text-left w-full">
27
+
{summary.length > 100 ? summary.substring(0, 100) + "..." : summary}
28
+
</p>
29
+
</div>
30
+
</Link>
31
+
</>
32
+
);
33
+
}
+26
react/src/components/BlogPostList.tsx
+26
react/src/components/BlogPostList.tsx
···
1
+
import type { BlogPost } from "../lib/post";
2
+
import { BlogPostItem } from "./BlogPostItem";
3
+
4
+
export function BlogPostList({ posts }: { posts: BlogPost[] }) {
5
+
if (!posts.length) {
6
+
return <p className="text-lg text-center">No blog posts available</p>;
7
+
}
8
+
return (
9
+
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 items-center justify-center gap-5">
10
+
{posts
11
+
.sort(
12
+
(a, b) =>
13
+
new Date(b.datePosted).getTime() - new Date(a.datePosted).getTime(),
14
+
)
15
+
.map((post, idx) => (
16
+
<BlogPostItem
17
+
key={idx}
18
+
idx={posts.length - idx - 1}
19
+
title={post.title}
20
+
summary={post.summary}
21
+
datePosted={post.datePosted}
22
+
/>
23
+
))}
24
+
</div>
25
+
);
26
+
}
-23
react/src/components/Entry.tsx
-23
react/src/components/Entry.tsx
···
1
-
import { Link } from "react-router";
2
-
3
-
export function Entry({
4
-
title,
5
-
idx,
6
-
datePosted,
7
-
}: {
8
-
title: string;
9
-
idx: number;
10
-
datePosted: string;
11
-
}) {
12
-
return (
13
-
<>
14
-
<Link to={`/entries/${idx}`}>
15
-
<div className="border border-gray-300 p-4 rounded-md flex flex-row gap-4 justify-center items-center">
16
-
<p className="text-gray-500">#{idx + 1}</p>
17
-
<h2 className="text-lg text-gray-300 font-bold">{title}</h2>
18
-
<p className="text-gray-500 text-sm">Posted on {datePosted}</p>
19
-
</div>
20
-
</Link>
21
-
</>
22
-
);
23
-
}
-30
react/src/components/Post.tsx
-30
react/src/components/Post.tsx
···
1
-
import { useParams, Outlet } from "react-router";
2
-
import { posts } from "../lib/post";
3
-
import { Link } from "react-router";
4
-
5
-
export function Post() {
6
-
const { postId } = useParams();
7
-
const post = posts[parseInt(postId!)];
8
-
return (
9
-
<>
10
-
<div className="md:grid md:grid-cols-3 flex flex-col w-full">
11
-
<Link
12
-
to="/"
13
-
className="text-gray-200 hover:text-gray-400 flex justify-center items-center w-16"
14
-
>
15
-
Home
16
-
</Link>
17
-
<h2 className="text-3xl text-center">{post.title}</h2>
18
-
</div>
19
-
<p className="text-lg md:w-3xl w-full">{post.content}</p>
20
-
</>
21
-
);
22
-
}
23
-
24
-
export function PostLayout() {
25
-
return (
26
-
<div className="flex flex-col justify-center gap-3 items-center p-4 w-lvw">
27
-
<Outlet />
28
-
</div>
29
-
);
30
-
}
-6
react/src/index.css
-6
react/src/index.css
+9
react/src/lib/post.ts
+9
react/src/lib/post.ts
···
1
1
export interface BlogPost {
2
2
datePosted: string;
3
3
title: string;
4
+
author: string;
5
+
summary: string;
4
6
content: string;
5
7
}
6
8
···
8
10
{
9
11
datePosted: "2025-11-15",
10
12
title: "My First Blog Post",
13
+
author: "Samuel Shuert",
14
+
summary: "First blog post",
11
15
content:
12
16
"This is my first blog post. I am excited to share my thoughts with the world! lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
13
17
},
14
18
{
15
19
datePosted: "2025-11-17",
16
20
title: "My Second Blog Post",
21
+
author: "Samuel Shuert",
22
+
summary: "Another post",
17
23
content:
18
24
"This is my second blog post. I am excited to share my thoughts with the world!",
19
25
},
20
26
{
21
27
datePosted: "2025-11-18",
22
28
title: "My Third Blog Post",
29
+
author: "Samuel Shuert",
30
+
summary:
31
+
"The third blog post lorem ipsum dolor sit amet consectetur adipiscing elit The third blog post lorem ipsum dolor sit amet consectetur adipiscing elit",
23
32
content:
24
33
"This is my third blog post. I am excited to share my thoughts with the world!",
25
34
},
+2
-2
react/src/main.tsx
+2
-2
react/src/main.tsx
···
3
3
import { BrowserRouter, Routes, Route } from "react-router";
4
4
import "./index.css";
5
5
import { App } from "./App.tsx";
6
-
import { Post, PostLayout } from "./components/Post.tsx";
6
+
import { BlogPostDetail, PostLayout } from "./components/BlogPostDetail.tsx";
7
7
8
8
createRoot(document.getElementById("root")!).render(
9
9
<StrictMode>
···
11
11
<Routes>
12
12
<Route index element={<App />} />
13
13
<Route path="entries" element={<PostLayout />}>
14
-
<Route path=":postId" element={<Post />} />
14
+
<Route path=":postId" element={<BlogPostDetail />} />
15
15
</Route>
16
16
</Routes>
17
17
</BrowserRouter>