Skip to content

Commit 0a2839e

Browse files
committed
add: new stories section
1 parent 29d2c1f commit 0a2839e

File tree

99 files changed

+852
-151
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

99 files changed

+852
-151
lines changed

.DS_Store

0 Bytes
Binary file not shown.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"use client";
2+
3+
import { canela_regu, canela_th } from "@/lib/fonts";
4+
import { formatDate } from "@/lib/utils";
5+
6+
interface StoriesPostProps {
7+
slug: string;
8+
title: string;
9+
publishedAt: string;
10+
summary: string;
11+
image?: string;
12+
}
13+
14+
const StoriesPost = ({ slug, title, publishedAt, summary, image }: StoriesPostProps) => {
15+
return (
16+
<div
17+
className={`${canela_th.className} border-titledCream group cursor-pointer relative bg-white rounded-xl shadow-shadowSm hover:shadow-shadowLg transition-all duration-300 overflow-hidden border border-gray-100`}
18+
onClick={() => (window.location.href = `/stories/${slug}`)}
19+
>
20+
<div className="aspect-w-16 aspect-h-9 bg-gray-100 overflow-hidden">
21+
{image ? (
22+
<img
23+
src={image}
24+
alt={title}
25+
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
26+
/>
27+
) : (
28+
<div className="w-full h-full bg-gradient-to-br from-gray-100 to-gray-200 flex items-center justify-center">
29+
<span className="text-gray-400 text-sm">No image</span>
30+
</div>
31+
)}
32+
</div>
33+
<div className="p-6">
34+
<h3
35+
className={`${canela_regu.className} text-left text-xl font-semibold text-gray-700 mb-2 group-hover:text-tealBright transition-colors duration-200`}
36+
>
37+
{title}
38+
</h3>
39+
<div className="flex items-center mb-2">
40+
<time className="text-sm text-gray-500">
41+
{formatDate(publishedAt)}
42+
</time>
43+
</div>
44+
<p className="text-left text-gray-600 line-clamp-2 m-0">{summary}</p>
45+
</div>
46+
</div>
47+
);
48+
};
49+
50+
export default StoriesPost;

app/components/header/index.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ const Header = () => {
5454
<MenuLink href="/" value="Home" />
5555
<MenuLink href="/explora" value="Explora" postText="+" />
5656
<MenuLink href="/blogs" value="Blogs" />
57+
<MenuLink href="/stories" value="Stories" />
5758
</div>
5859
</div>
5960
<div className="hidden max-md:flex justify-between relative w-[100%] px-4">
@@ -132,6 +133,15 @@ const Header = () => {
132133
Blogs
133134
</Link>
134135
</li>
136+
<li className="border-t border-b-0 border-[0.1rem] flex leading-1">
137+
<Link
138+
className="px-3 w-full text-left text-grey py-2"
139+
href="/stories"
140+
onClick={toggleMenuButton}
141+
>
142+
Stories
143+
</Link>
144+
</li>
135145
<li className="border-t border-b border-[0.1rem] flex leading-1">
136146
<Link
137147
className="px-3 w-full text-left text-grey py-2"

app/components/stories-posts.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { getStoriesPosts } from "../stories/utils";
2+
import StoriesPost from "./@ui/stories-post/index";
3+
4+
export function StoriesPosts() {
5+
let allStoriesPosts = getStoriesPosts();
6+
7+
return (
8+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
9+
{allStoriesPosts
10+
.sort((a, b) => {
11+
if (
12+
new Date(a.metadata.publishedAt || "") >
13+
new Date(b.metadata.publishedAt || "")
14+
) {
15+
return -1;
16+
}
17+
return 1;
18+
})
19+
.map((post) => (
20+
<StoriesPost
21+
key={post.slug}
22+
slug={post.slug}
23+
title={post.metadata.title}
24+
publishedAt={post.metadata.publishedAt}
25+
summary={post.metadata.summary}
26+
image={post.metadata.image}
27+
/>
28+
))}
29+
</div>
30+
);
31+
}

app/sitemap.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { getBlogPosts } from "@/blogs/utils";
22
import { getExplorations } from "@/explora/utils";
3+
import { getStoriesPosts } from "@/stories/utils";
34

45
export const baseUrl = "https://www.sumitso.in";
56

@@ -18,11 +19,17 @@ export default async function sitemap() {
1819
}))
1920
);
2021

22+
// Fetch stories posts
23+
let storiesPosts = getStoriesPosts().map((post) => ({
24+
url: `${baseUrl}/stories/${post.slug}`,
25+
lastModified: post.metadata.publishedAt,
26+
}));
27+
2128
// Base routes
22-
let routes = ["", "/blogs", "/explora"].map((route) => ({
29+
let routes = ["", "/blogs", "/explora", "/stories"].map((route) => ({
2330
url: `${baseUrl}${route}`,
2431
lastModified: new Date().toISOString().split("T")[0],
2532
}));
2633

27-
return [...routes, ...blogs, ...explorations];
34+
return [...routes, ...blogs, ...explorations, ...storiesPosts];
2835
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { ImageResponse } from 'next/og'
2+
3+
export const runtime = 'edge'
4+
5+
export const alt = 'Stories Post'
6+
export const size = {
7+
width: 1200,
8+
height: 630,
9+
}
10+
11+
export const contentType = 'image/png'
12+
13+
export default async function Image({ params }: { params: { slug: string } }) {
14+
return new ImageResponse(
15+
(
16+
<div
17+
style={{
18+
height: '100%',
19+
width: '100%',
20+
display: 'flex',
21+
flexDirection: 'column',
22+
alignItems: 'center',
23+
justifyContent: 'center',
24+
backgroundColor: '#f6f5f1',
25+
fontSize: 32,
26+
fontWeight: 600,
27+
}}
28+
>
29+
<div
30+
style={{
31+
left: 42,
32+
top: 42,
33+
position: 'absolute',
34+
display: 'flex',
35+
alignItems: 'center',
36+
}}
37+
>
38+
<span
39+
style={{
40+
width: 24,
41+
height: 24,
42+
background: 'linear-gradient(90deg, #042f2e, #0d9488)',
43+
marginRight: 8,
44+
}}
45+
/>
46+
<span style={{ color: '#042f2e' }}>sumitso.in</span>
47+
</div>
48+
<div
49+
style={{
50+
display: 'flex',
51+
flexDirection: 'column',
52+
alignItems: 'center',
53+
justifyContent: 'center',
54+
textAlign: 'center',
55+
maxWidth: 900,
56+
padding: '0 40px',
57+
}}
58+
>
59+
<div
60+
style={{
61+
fontSize: 48,
62+
fontWeight: 800,
63+
background: 'linear-gradient(90deg, #042f2e, #0d9488)',
64+
backgroundClip: 'text',
65+
color: 'transparent',
66+
marginBottom: 16,
67+
textAlign: 'center',
68+
lineHeight: 1.2,
69+
}}
70+
>
71+
Stories
72+
</div>
73+
<div
74+
style={{
75+
fontSize: 20,
76+
color: '#374151',
77+
textAlign: 'center',
78+
lineHeight: 1.4,
79+
}}
80+
>
81+
Stories, experiences, and reflections from my journey
82+
</div>
83+
</div>
84+
</div>
85+
),
86+
{
87+
...size,
88+
}
89+
)
90+
}

app/stories/[slug]/page.tsx

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { notFound } from "next/navigation";
2+
import { getStoriesPosts } from "../utils";
3+
import { CustomMDX } from "@/components/mdx";
4+
import Header from "@/components/header";
5+
import Footer from "@/components/footer";
6+
import { canela_regu } from "@/lib/fonts";
7+
import { formatDate } from "../utils";
8+
9+
export async function generateStaticParams() {
10+
let posts = getStoriesPosts();
11+
12+
return posts.map((post) => ({
13+
slug: post.slug,
14+
}));
15+
}
16+
17+
export function generateMetadata({ params }) {
18+
let post = getStoriesPosts().find((post) => post.slug === params.slug);
19+
if (!post) {
20+
return;
21+
}
22+
23+
let {
24+
title,
25+
publishedAt: publishedTime,
26+
summary: description,
27+
image,
28+
} = post.metadata;
29+
30+
let ogImage = image
31+
? `https://sumitso.in${image}`
32+
: `https://sumitso.in/og?title=${encodeURIComponent(title)}`;
33+
34+
return {
35+
title,
36+
description,
37+
openGraph: {
38+
title,
39+
description,
40+
type: "article",
41+
publishedTime,
42+
url: `https://sumitso.in/stories/${post.slug}`,
43+
images: [
44+
{
45+
url: ogImage,
46+
},
47+
],
48+
},
49+
twitter: {
50+
card: "summary_large_image",
51+
title,
52+
description,
53+
images: [ogImage],
54+
},
55+
};
56+
}
57+
58+
export default function StoriesPost({ params }) {
59+
let posts = getStoriesPosts();
60+
let post = posts.find((post) => post.slug === params.slug);
61+
62+
if (!post) {
63+
notFound();
64+
}
65+
66+
return (
67+
<div className="min-h-screen bg-gradient-to-b">
68+
<Header />
69+
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
70+
<article className="prose prose-lg max-w-none">
71+
<header className="mb-8">
72+
<h1
73+
className={`${canela_regu.className} text-4xl font-bold text-gray-900 mb-4`}
74+
>
75+
{post.metadata.title}
76+
</h1>
77+
{post.metadata.publishedAt && (
78+
<time className="text-gray-500 text-sm">
79+
{formatDate(post.metadata.publishedAt)}
80+
</time>
81+
)}
82+
</header>
83+
84+
{post.metadata.image && (
85+
<div className="mb-8">
86+
<img
87+
src={post.metadata.image}
88+
alt={post.metadata.title}
89+
className="w-full h-auto rounded-lg shadow-md"
90+
/>
91+
</div>
92+
)}
93+
94+
<div className="prose prose-lg max-w-none">
95+
<CustomMDX source={post.content} />
96+
</div>
97+
</article>
98+
</div>
99+
<Footer />
100+
</div>
101+
);
102+
}

app/stories/layout.tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React from "react";
2+
import "../global.css";
3+
import { Metadata } from "next";
4+
5+
export const metadata: Metadata = {
6+
title: "Sumit So | Stories",
7+
description:
8+
"Stories, experiences, and reflections from Sumit So. Tales from conferences, life experiences, and the human side of being a developer.",
9+
openGraph: {
10+
title: "Sumit So | Stories",
11+
description:
12+
"Stories, experiences, and reflections from Sumit So. Tales from conferences, life experiences, and the human side of being a developer.",
13+
type: "website",
14+
locale: "en_US",
15+
siteName: "Sumit So",
16+
url: "/stories",
17+
},
18+
twitter: {
19+
card: "summary_large_image",
20+
title: "Sumit So | Stories",
21+
description:
22+
"Stories, experiences, and reflections from Sumit So. Tales from conferences, life experiences, and the human side of being a developer.",
23+
},
24+
keywords: [
25+
"Sumit So",
26+
"Stories",
27+
"Experiences",
28+
"Conferences",
29+
"Life Stories",
30+
"Developer Life",
31+
"Reflections",
32+
"Thoughts",
33+
],
34+
authors: [{ name: "Sumit So", url: "https://sumitso.in" }],
35+
creator: "Sumit So",
36+
publisher: "Sumit So",
37+
alternates: {
38+
canonical: "/stories",
39+
},
40+
};
41+
42+
export default function RootLayout({
43+
children,
44+
}: {
45+
children: React.ReactNode;
46+
}) {
47+
return <div>{children}</div>;
48+
}

0 commit comments

Comments
 (0)