Skip to content

Commit 2dfbec9

Browse files
authored
Merge pull request #36 from objectstack-ai/copilot/fix-homepage-font-color
2 parents 9939947 + 0115e54 commit 2dfbec9

File tree

7 files changed

+1672
-9
lines changed

7 files changed

+1672
-9
lines changed
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
import { notFound } from 'next/navigation';
2+
import { blog } from '@/app/source';
3+
import defaultMdxComponents from 'fumadocs-ui/mdx';
4+
import { HomeLayout } from 'fumadocs-ui/layouts/home';
5+
import { baseOptions } from '@/app/layout.config';
6+
import Link from 'next/link';
7+
import { ArrowLeft } from 'lucide-react';
8+
9+
// Extended type for blog post data
10+
interface BlogPostData {
11+
title: string;
12+
description?: string;
13+
author?: string;
14+
date?: string;
15+
tags?: string[];
16+
body: React.ComponentType;
17+
}
18+
19+
export default async function BlogPage({
20+
params,
21+
}: {
22+
params: Promise<{ lang: string; slug?: string[] }>;
23+
}) {
24+
const { slug } = await params;
25+
26+
// If no slug, show blog index
27+
if (!slug || slug.length === 0) {
28+
const posts = blog.getPages();
29+
30+
return (
31+
<HomeLayout {...baseOptions}>
32+
<main className="container max-w-5xl mx-auto px-4 py-16">
33+
<div className="mb-12">
34+
<h1 className="text-4xl font-bold mb-4">Blog</h1>
35+
<p className="text-lg text-fd-foreground/80">
36+
Insights, updates, and best practices from the ObjectStack team.
37+
</p>
38+
</div>
39+
40+
<div className="grid gap-8 md:grid-cols-2">
41+
{posts.map((post) => {
42+
const postData = post.data as unknown as BlogPostData;
43+
return (
44+
<Link
45+
key={post.url}
46+
href={post.url}
47+
className="group block rounded-lg border border-fd-border bg-fd-card p-6 transition-all hover:border-fd-primary/30 hover:shadow-md"
48+
>
49+
<div className="mb-3">
50+
<h2 className="text-2xl font-semibold mb-2 group-hover:text-fd-primary transition-colors">
51+
{postData.title}
52+
</h2>
53+
{postData.description && (
54+
<p className="text-fd-foreground/70">
55+
{postData.description}
56+
</p>
57+
)}
58+
</div>
59+
60+
<div className="flex items-center gap-4 text-sm text-fd-foreground/70">
61+
{postData.date && (
62+
<time dateTime={postData.date}>
63+
{new Date(postData.date).toLocaleDateString('en-US', {
64+
year: 'numeric',
65+
month: 'long',
66+
day: 'numeric',
67+
})}
68+
</time>
69+
)}
70+
{postData.author && (
71+
<span>By {postData.author}</span>
72+
)}
73+
</div>
74+
75+
{postData.tags && postData.tags.length > 0 && (
76+
<div className="mt-4 flex flex-wrap gap-2">
77+
{postData.tags.map((tag: string) => (
78+
<span
79+
key={tag}
80+
className="inline-flex items-center rounded-full bg-fd-primary/10 px-2.5 py-0.5 text-xs font-medium text-fd-primary"
81+
>
82+
{tag}
83+
</span>
84+
))}
85+
</div>
86+
)}
87+
</Link>
88+
);
89+
})}
90+
</div>
91+
92+
{posts.length === 0 && (
93+
<div className="text-center py-12">
94+
<p className="text-fd-foreground/70">No blog posts yet. Check back soon!</p>
95+
</div>
96+
)}
97+
</main>
98+
</HomeLayout>
99+
);
100+
}
101+
102+
// Show individual blog post
103+
const page = blog.getPage(slug);
104+
105+
if (!page) {
106+
notFound();
107+
}
108+
109+
const pageData = page.data as unknown as BlogPostData;
110+
const MDX = page.data.body;
111+
112+
return (
113+
<HomeLayout {...baseOptions}>
114+
<main className="container max-w-4xl mx-auto px-4 py-16">
115+
<Link
116+
href="/blog"
117+
className="inline-flex items-center gap-2 text-sm text-fd-foreground/70 hover:text-fd-foreground mb-8 transition-colors"
118+
>
119+
<ArrowLeft className="w-4 h-4" />
120+
Back to Blog
121+
</Link>
122+
123+
<article className="prose prose-neutral dark:prose-invert max-w-none">
124+
<header className="mb-8 pb-8 border-b border-fd-border">
125+
<h1 className="text-4xl font-bold mb-4">{pageData.title}</h1>
126+
127+
{pageData.description && (
128+
<p className="text-xl text-fd-foreground/80 mb-6">
129+
{pageData.description}
130+
</p>
131+
)}
132+
133+
<div className="flex items-center gap-4 text-sm text-fd-foreground/70">
134+
{pageData.date && (
135+
<time dateTime={pageData.date}>
136+
{new Date(pageData.date).toLocaleDateString('en-US', {
137+
year: 'numeric',
138+
month: 'long',
139+
day: 'numeric',
140+
})}
141+
</time>
142+
)}
143+
{pageData.author && (
144+
<span>By {pageData.author}</span>
145+
)}
146+
</div>
147+
148+
{pageData.tags && pageData.tags.length > 0 && (
149+
<div className="mt-4 flex flex-wrap gap-2">
150+
{pageData.tags.map((tag: string) => (
151+
<span
152+
key={tag}
153+
className="inline-flex items-center rounded-full bg-fd-primary/10 px-3 py-1 text-sm font-medium text-fd-primary"
154+
>
155+
{tag}
156+
</span>
157+
))}
158+
</div>
159+
)}
160+
</header>
161+
162+
<MDX components={defaultMdxComponents} />
163+
</article>
164+
</main>
165+
</HomeLayout>
166+
);
167+
}
168+
169+
export async function generateStaticParams() {
170+
return blog.getPages().map((page) => ({
171+
slug: page.slugs,
172+
}));
173+
}
174+
175+
export async function generateMetadata({
176+
params,
177+
}: {
178+
params: Promise<{ slug?: string[] }>;
179+
}) {
180+
const { slug } = await params;
181+
182+
// If no slug, return default metadata for blog index
183+
if (!slug || slug.length === 0) {
184+
return {
185+
title: 'Blog',
186+
description: 'Insights, updates, and best practices from the ObjectStack team.',
187+
};
188+
}
189+
190+
const page = blog.getPage(slug);
191+
192+
if (!page) {
193+
notFound();
194+
}
195+
196+
return {
197+
title: page.data.title,
198+
description: page.data.description,
199+
};
200+
}

apps/docs/app/[lang]/page.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ export default async function HomePage({
2727
{t.hero.title.line1} <br/> {t.hero.title.line2}
2828
</h1>
2929

30-
<p className="mx-auto max-w-2xl text-lg text-fd-muted-foreground sm:text-xl leading-relaxed">
30+
<p className="mx-auto max-w-2xl text-lg text-fd-foreground/80 sm:text-xl leading-relaxed">
3131
{t.hero.subtitle.line1}
3232
<br className="hidden sm:inline" />
33-
<span className="text-fd-foreground font-medium">{t.hero.subtitle.line2}</span>
33+
<span className="text-fd-foreground font-semibold">{t.hero.subtitle.line2}</span>
3434
</p>
3535

3636
<div className="flex flex-col sm:flex-row items-center justify-center gap-4 pt-4">
@@ -124,7 +124,7 @@ export default async function HomePage({
124124

125125
{/* Personas Section */}
126126
<div className="mt-32 mb-16 w-full max-w-5xl px-4">
127-
<h2 className="text-3xl font-bold tracking-tight mb-12 bg-gradient-to-r from-fd-foreground to-fd-muted-foreground bg-clip-text text-transparent">
127+
<h2 className="text-3xl font-bold tracking-tight mb-12 bg-gradient-to-r from-fd-foreground to-fd-foreground/70 bg-clip-text text-transparent">
128128
{t.personas.heading}
129129
</h2>
130130
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
@@ -166,7 +166,7 @@ function FeatureCard({ icon, title, description, href }: { icon: React.ReactNode
166166
<h3 className="mb-2 text-lg font-semibold text-fd-card-foreground group-hover:text-fd-primary transition-colors">
167167
{title}
168168
</h3>
169-
<p className="text-sm text-fd-muted-foreground leading-relaxed">
169+
<p className="text-sm text-fd-foreground/70 leading-relaxed">
170170
{description}
171171
</p>
172172
</div>
@@ -188,7 +188,7 @@ function PersonaCard({ icon, title, description, href, action }: { icon: React.R
188188
<Link href={href} className="flex flex-col items-start p-8 rounded-2xl bg-fd-secondary/30 border border-fd-border/50 hover:bg-fd-secondary/60 hover:border-fd-primary/30 transition-all group text-left">
189189
{icon}
190190
<h3 className="text-xl font-bold mb-3">{title}</h3>
191-
<p className="text-fd-muted-foreground mb-6 text-sm leading-relaxed flex-grow text-left">
191+
<p className="text-fd-foreground/70 mb-6 text-sm leading-relaxed flex-grow text-left">
192192
{description}
193193
</p>
194194
<div className="flex items-center text-sm font-semibold text-fd-primary mt-auto group-hover:translate-x-1 transition-transform">

apps/docs/app/layout.config.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ export const baseOptions: BaseLayoutProps = {
2222
url: '/docs/',
2323
active: 'nested-url',
2424
},
25+
{
26+
text: 'Blog',
27+
url: '/blog',
28+
active: 'nested-url',
29+
},
2530
// {
2631
// text: 'Concepts',
2732
// url: '/docs/concepts/manifesto',

apps/docs/app/source.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1-
import { docs } from 'fumadocs-mdx:collections/server';
1+
import { docs, blog as blogCollection } from 'fumadocs-mdx:collections/server';
22
import { loader } from 'fumadocs-core/source';
33
import { i18n } from '@/lib/i18n';
44

55
export const source = loader({
66
baseUrl: '/docs',
77
i18n,
8-
source: (docs as any).toFumadocsSource(),
8+
source: docs.toFumadocsSource(),
9+
});
10+
11+
export const blog = loader({
12+
baseUrl: '/blog',
13+
source: blogCollection.toFumadocsSource(),
914
});

apps/docs/source.config.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,21 @@
1-
import { defineDocs, defineConfig } from 'fumadocs-mdx/config';
1+
import { defineDocs, defineConfig, frontmatterSchema } from 'fumadocs-mdx/config';
2+
import { z } from 'zod';
23

34
export const docs = defineDocs({
45
dir: '../../content/docs',
5-
}) as any;
6+
});
7+
8+
const blogSchema = frontmatterSchema.extend({
9+
author: z.string().optional(),
10+
date: z.coerce.string().optional(),
11+
tags: z.array(z.string()).optional(),
12+
});
13+
14+
export const blog = defineDocs({
15+
dir: '../../content/blog',
16+
docs: {
17+
schema: blogSchema,
18+
},
19+
});
620

721
export default defineConfig();

0 commit comments

Comments
 (0)