Skip to content

Commit 12393a1

Browse files
committed
Basics of Series
1 parent c661730 commit 12393a1

File tree

10 files changed

+390
-42
lines changed

10 files changed

+390
-42
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Gemini's Guide to React - Understanding Components
2+
3+
In this second part of Gemini's Guide to React, we'll focus on one of React's most fundamental concepts: Components.
4+
5+
Components are independent, reusable bits of code. They serve the same purpose as JavaScript functions, but work in isolation and return HTML via a render function. Components come in two types: Class Components and Functional Components. Modern React development primarily uses Functional Components with Hooks.
6+
7+
Example of a Functional Component:
8+
9+
```jsx
10+
import React from 'react';
11+
12+
function Welcome(props) {
13+
return <h1>Hello, {props.name}</h1>;
14+
}
15+
16+
export default Welcome;
17+
```
18+
19+
Next, we'll explore JSX and how it makes writing React components more intuitive.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Gemini's Guide to React - Introduction
2+
3+
Welcome to the first part of Gemini's Guide to React! In this series, we'll explore the fundamentals of React, starting with what React is and why it's so popular.
4+
5+
React is a JavaScript library for building user interfaces. It allows developers to create large web applications that can change data without reloading the page. Its main purpose is to build fast and scalable UIs.
6+
7+
Key concepts we'll cover:
8+
- Components
9+
- JSX
10+
- State and Props
11+
12+
Stay tuned for the next part where we'll dive into Components!

public/posts/shownPosts.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,5 +74,19 @@
7474
"updated": "2025-10-13",
7575
"tags": ["writing", "updates"],
7676
"category": "rant"
77+
},
78+
{
79+
"slug": "gemini-react-intro",
80+
"title": "Introduction",
81+
"date": "2024-01-01",
82+
"series": "Gemini's Guide to React",
83+
"seriesIndex": 1
84+
},
85+
{
86+
"slug": "gemini-react-components",
87+
"title": "Understanding Components",
88+
"date": "2024-01-05",
89+
"series": "Gemini's Guide to React",
90+
"seriesIndex": 2
7791
}
7892
]

src/components/AnimatedRoutes.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import AboutPage from '../pages/AboutPage';
1010
import LogsPage from '../pages/LogsPage';
1111
import LogDetailPage from '../pages/LogDetailPage';
1212
import NotFoundPage from '../pages/NotFoundPage';
13+
import SeriesPage from '../pages/SeriesPage';
1314

1415
const pageVariants = {
1516
initial: {
@@ -63,6 +64,34 @@ function AnimatedRoutes() {
6364
</motion.div>
6465
}
6566
/>
67+
<Route
68+
path="/blog/series/:seriesSlug/:episodeSlug"
69+
element={
70+
<motion.div
71+
initial="initial"
72+
animate="in"
73+
exit="out"
74+
variants={pageVariants}
75+
transition={pageTransition}
76+
>
77+
<BlogPostPage />
78+
</motion.div>
79+
}
80+
/>
81+
<Route
82+
path="/blog/series/:seriesSlug"
83+
element={
84+
<motion.div
85+
initial="initial"
86+
animate="in"
87+
exit="out"
88+
variants={pageVariants}
89+
transition={pageTransition}
90+
>
91+
<SeriesPage />
92+
</motion.div>
93+
}
94+
/>
6695
<Route
6796
path="/blog/:slug"
6897
element={

src/components/PostItem.js

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22
import { Link } from 'react-router-dom';
33

4-
const PostItem = ({ slug, title, date, updatedDate, category }) => {
4+
const PostItem = ({ slug, title, date, updatedDate, category, series, seriesIndex, isSeries }) => {
55
// Format the date to a shorter format: Month Day, Year
66
const formattedDate = new Date(date).toLocaleDateString('en-US', {
77
month: 'short',
@@ -22,18 +22,26 @@ const PostItem = ({ slug, title, date, updatedDate, category }) => {
2222
backgroundColor:
2323
category === 'dev'
2424
? 'var(--color-dev-badge)'
25+
: category === 'series'
26+
? 'var(--color-series-badge)'
2527
: 'var(--color-takes-badge)',
2628
};
2729
const postBackgroundColorClass =
28-
category === 'dev' ? 'bg-dev-card-bg' : 'bg-takes-card-bg';
30+
category === 'dev'
31+
? 'bg-dev-card-bg'
32+
: category === 'series'
33+
? 'bg-series-card-bg'
34+
: 'bg-takes-card-bg';
2935
const postHoverBackgroundColorClass =
3036
category === 'dev'
3137
? 'hover:bg-dev-card-bg-hover'
38+
: category === 'series'
39+
? 'hover:bg-series-card-bg-hover'
3240
: 'hover:bg-takes-card-bg-hover';
3341

3442
return (
3543
<Link
36-
to={`/blog/${slug}`}
44+
to={isSeries ? `/blog/${slug}` : `/blog/${slug}`}
3745
className={`block p-8 my-4 border border-gray-700/50 rounded-lg shadow-lg cursor-pointer transition-colors group ${postBackgroundColorClass} ${postHoverBackgroundColorClass}`}
3846
>
3947
<article>
@@ -48,6 +56,11 @@ const PostItem = ({ slug, title, date, updatedDate, category }) => {
4856
{category}
4957
</span>
5058
)}
59+
{series && !isSeries && (
60+
<span className="mr-2 px-2 py-1 text-xs font-medium text-blue-400 bg-blue-400/10 rounded-full">
61+
{series} - Part {seriesIndex}
62+
</span>
63+
)}
5164
<h2 className="text-xl font-semibold text-white group-hover:text-title-hover group-hover:underline transition-colors">
5265
{title}
5366
</h2>
@@ -58,12 +71,12 @@ const PostItem = ({ slug, title, date, updatedDate, category }) => {
5871
</span>
5972
)}
6073
<span className="ml-4 flex-shrink-0 text-sm font-medium text-primary-400 group-hover:text-title-hover group-hover:underline transition-colors">
61-
<span className="hidden sm:inline">Read post</span> &rarr;
74+
<span className="hidden sm:inline">{isSeries ? 'View Series' : 'Read post'}</span> &rarr;
6275
</span>
6376
</div>
6477
</article>
6578
</Link>
6679
);
6780
};
6881

69-
export default PostItem;
82+
export default PostItem;

src/components/PostMetadata.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React from 'react';
2+
import { Link } from 'react-router-dom';
23
import Label from './Label';
34

45
const PostMetadata = ({
@@ -7,6 +8,7 @@ const PostMetadata = ({
78
isAtTop,
89
overrideDate,
910
updatedDate,
11+
seriesPosts,
1012
}) => {
1113
if (!metadata) {
1214
return null;
@@ -92,6 +94,34 @@ const PostMetadata = ({
9294
</div>
9395
</div>
9496
)}
97+
98+
{metadata.series && seriesPosts && seriesPosts.length > 0 && (
99+
<>
100+
<div>
101+
<Label>Series</Label>
102+
<p className="text-gray-300 ml-1 mt-1">{metadata.series}</p>
103+
</div>
104+
<div>
105+
<Label>Episodes</Label>
106+
<ul className="list-disc list-inside ml-4 mt-2 text-gray-300">
107+
{seriesPosts.map((postInSeries) => {
108+
const currentSeriesSlug = metadata.series.toLowerCase().replace(/[^a-z0-9]+/g, '-') + '-series';
109+
const episodeLink = `/blog/series/${currentSeriesSlug}/${postInSeries.slug}`;
110+
return (
111+
<li key={postInSeries.slug}>
112+
<Link
113+
to={episodeLink}
114+
className={`hover:text-primary-400 ${postInSeries.slug === metadata.slug ? 'font-semibold text-primary-400' : ''}`}
115+
>
116+
{postInSeries.title}
117+
</Link>
118+
</li>
119+
);
120+
})}
121+
</ul>
122+
</div>
123+
</>
124+
)}
95125
</div>
96126
<div className="mt-6">
97127
<Label className="mb-1">Reading Progress</Label>

src/components/ProjectCard.js

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,24 @@ const ProjectCard = ({ project, size = 1 }) => {
77
size === 2 ? 'md:col-span-2' : size === 3 ? 'md:col-span-3' : 'col-span-1';
88

99
return (
10-
<Link
11-
to={`/projects/${project.slug}`}
10+
<div
1211
className={`block bg-gray-500/10 p-6 rounded-lg shadow-lg hover:bg-gray-500/20 transition-colors border border-gray-700/50 cursor-pointer flex flex-col ${colSpanClass}`}
1312
>
14-
<h3 className="text-xl font-semibold text-white">{project.title}</h3>
15-
<p className="mt-2 text-gray-400 flex-grow">{project.description}</p>
16-
<a
17-
href={project.link || `/projects/${project.slug}`}
18-
target="_blank"
19-
rel="noopener noreferrer"
20-
className="mt-4 inline-block text-red-500 hover:text-red-300 transition-colors mt-auto flex items-center"
21-
>
22-
View Project <FaExternalLinkAlt className="ml-1" size={12} />
23-
</a>
24-
</Link>
13+
<Link to={`/projects/${project.slug}`} className="flex flex-col flex-grow">
14+
<h3 className="text-xl font-semibold text-white">{project.title}</h3>
15+
<p className="mt-2 text-gray-400 flex-grow">{project.description}</p>
16+
</Link>
17+
{project.link && (
18+
<a
19+
href={project.link}
20+
target="_blank"
21+
rel="noopener noreferrer"
22+
className="mt-4 inline-block text-red-500 hover:text-red-300 transition-colors mt-auto flex items-center"
23+
>
24+
View Project <FaExternalLinkAlt className="ml-1" size={12} />
25+
</a>
26+
)}
27+
</div>
2528
);
2629
};
2730

src/pages/BlogPage.js

Lines changed: 66 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,61 @@ import { ArrowLeftIcon } from '@phosphor-icons/react';
66

77
const BlogPage = () => {
88
usePageTitle('Blog');
9-
const [posts, setPosts] = useState([]);
9+
const [displayItems, setDisplayItems] = useState([]);
1010
const [loading, setLoading] = useState(true);
1111

1212
useEffect(() => {
1313
const fetchPostSlugs = async () => {
1414
try {
1515
const response = await fetch('/posts/shownPosts.json');
1616
if (response.ok) {
17-
const slugs = await response.json();
18-
setPosts(slugs);
17+
const allPosts = await response.json();
18+
19+
const seriesMap = new Map();
20+
const individualPosts = [];
21+
22+
allPosts.forEach(post => {
23+
if (post.series) {
24+
if (!seriesMap.has(post.series)) {
25+
seriesMap.set(post.series, {
26+
title: post.series,
27+
slug: post.series.toLowerCase().replace(/[^a-z0-9]+/g, '-') + '-series', // Generate a slug for the series
28+
isSeries: true,
29+
posts: []
30+
});
31+
}
32+
seriesMap.get(post.series).posts.push(post);
33+
} else {
34+
individualPosts.push(post);
35+
}
36+
});
37+
38+
// Sort series posts by seriesIndex
39+
seriesMap.forEach(series => {
40+
series.posts.sort((a, b) => a.seriesIndex - b.seriesIndex);
41+
});
42+
43+
// Combine individual posts and series entries
44+
const combinedItems = [
45+
...Array.from(seriesMap.values()),
46+
...individualPosts,
47+
];
48+
49+
// Sort combined items by date (newest first), series entries should appear based on the date of their latest post
50+
combinedItems.sort((a, b) => {
51+
const dateA = a.isSeries ? new Date(Math.max(...a.posts.map(p => new Date(p.date)))) : new Date(a.date);
52+
const dateB = b.isSeries ? new Date(Math.max(...b.posts.map(p => new Date(p.date)))) : new Date(b.date);
53+
return dateB - dateA;
54+
});
55+
56+
setDisplayItems(combinedItems);
1957
} else {
2058
console.error('Failed to fetch post slugs');
21-
setPosts([]);
59+
setDisplayItems([]);
2260
}
2361
} catch (error) {
2462
console.error('Error fetching post slugs:', error);
25-
setPosts([]);
63+
setDisplayItems([]);
2664
} finally {
2765
setLoading(false);
2866
}
@@ -72,21 +110,34 @@ const BlogPage = () => {
72110
</p>
73111
<div className="mt-4 text-center">
74112
<span className="ml-2 px-3 py-1 text-base font-medium text-gray-200 bg-gray-800 rounded-full">
75-
Total: {posts.length}
113+
Total: {displayItems.length}
76114
</span>
77115
</div>
78116
</div>
79117
<div className="mt-16">
80118
<div className="">
81-
{posts.map((post) => (
82-
<PostItem
83-
key={post.slug}
84-
slug={post.slug}
85-
title={post.title}
86-
date={post.date}
87-
updatedDate={post.updated}
88-
category={post.category}
89-
/>
119+
{displayItems.map((item) => (
120+
item.isSeries ? (
121+
<PostItem
122+
key={item.slug}
123+
slug={`series/${item.slug}`}
124+
title={item.title}
125+
date={item.posts[item.posts.length - 1].date} // Date of the latest post in the series
126+
category="series"
127+
isSeries={true}
128+
/>
129+
) : (
130+
<PostItem
131+
key={item.slug}
132+
slug={item.slug}
133+
title={item.title}
134+
date={item.date}
135+
updatedDate={item.updated}
136+
category={item.category}
137+
series={item.series}
138+
seriesIndex={item.seriesIndex}
139+
/>
140+
)
90141
))}
91142
</div>
92143
</div>

0 commit comments

Comments
 (0)