refactor: fix linting and typechecking errors
This commit is contained in:
@@ -1,82 +1,92 @@
|
||||
import { notFound } from "next/navigation";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { Badge } from "~/components/ui/badge";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { BreadcrumbUpdater } from "~/components/BreadcrumbUpdater";
|
||||
|
||||
interface BlogPostMetadata {
|
||||
title: string;
|
||||
publishedAt: string;
|
||||
tags?: string[];
|
||||
summary?: string;
|
||||
image?: string;
|
||||
}
|
||||
|
||||
interface PageProps {
|
||||
params: Promise<{ slug: string }>;
|
||||
params: Promise<{ slug: string }>;
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const contentDir = path.join(process.cwd(), "src/content/blog");
|
||||
const files = fs.readdirSync(contentDir);
|
||||
const contentDir = path.join(process.cwd(), "src/content/blog");
|
||||
const files = fs.readdirSync(contentDir);
|
||||
|
||||
return files
|
||||
.filter((file) => file.endsWith(".mdx"))
|
||||
.map((file) => ({
|
||||
slug: file.replace(".mdx", ""),
|
||||
}));
|
||||
return files
|
||||
.filter((file) => file.endsWith(".mdx"))
|
||||
.map((file) => ({
|
||||
slug: file.replace(".mdx", ""),
|
||||
}));
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: PageProps) {
|
||||
const { slug } = await params;
|
||||
try {
|
||||
const { metadata } = await import(`~/content/blog/${slug}.mdx`);
|
||||
return metadata;
|
||||
} catch (e) {
|
||||
return {
|
||||
title: "Post Not Found",
|
||||
};
|
||||
}
|
||||
const { slug } = await params;
|
||||
try {
|
||||
const { metadata } = (await import(`~/content/blog/${slug}.mdx`)) as {
|
||||
metadata: BlogPostMetadata;
|
||||
};
|
||||
return metadata;
|
||||
} catch {
|
||||
return {
|
||||
title: "Post Not Found",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default async function BlogPost({ params }: PageProps) {
|
||||
const { slug } = await params;
|
||||
let Post;
|
||||
let metadata;
|
||||
const { slug } = await params;
|
||||
let Post;
|
||||
let metadata;
|
||||
|
||||
try {
|
||||
const content = await import(`~/content/blog/${slug}.mdx`);
|
||||
Post = content.default;
|
||||
metadata = content.metadata;
|
||||
} catch (e) {
|
||||
notFound();
|
||||
}
|
||||
try {
|
||||
const content = (await import(`~/content/blog/${slug}.mdx`)) as {
|
||||
default: React.ComponentType;
|
||||
metadata: BlogPostMetadata;
|
||||
};
|
||||
Post = content.default;
|
||||
metadata = content.metadata;
|
||||
} catch {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return (
|
||||
<article className="animate-fade-in-up space-y-8">
|
||||
<BreadcrumbUpdater title={metadata.title} />
|
||||
<div className="mb-8">
|
||||
{/* <Button variant="ghost" asChild className="-ml-4 text-muted-foreground mb-4">
|
||||
return (
|
||||
<article className="animate-fade-in-up space-y-8">
|
||||
<BreadcrumbUpdater title={metadata.title} />
|
||||
<div className="mb-8">
|
||||
{/* <Button variant="ghost" asChild className="-ml-4 text-muted-foreground mb-4">
|
||||
<Link href="/blog">
|
||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||
Back to Blog
|
||||
</Link>
|
||||
</Button> */}
|
||||
|
||||
<h1 className="text-3xl font-bold mb-4">{metadata.title}</h1>
|
||||
<h1 className="mb-4 text-3xl font-bold">{metadata.title}</h1>
|
||||
|
||||
<div className="flex flex-wrap gap-4 items-center text-muted-foreground mb-6">
|
||||
<time dateTime={metadata.publishedAt}>{metadata.publishedAt}</time>
|
||||
{metadata.tags && (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{metadata.tags.map((tag: string) => (
|
||||
<Badge key={tag} variant="secondary">
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mb-6 flex flex-wrap items-center gap-4 text-muted-foreground">
|
||||
<time dateTime={metadata.publishedAt}>{metadata.publishedAt}</time>
|
||||
{metadata.tags && (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{metadata.tags.map((tag: string) => (
|
||||
<Badge key={tag} variant="secondary">
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="prose prose-zinc dark:prose-invert max-w-none">
|
||||
<Post />
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
<div className="prose prose-zinc max-w-none dark:prose-invert">
|
||||
<Post />
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
+89
-69
@@ -1,87 +1,107 @@
|
||||
import Link from "next/link";
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "~/components/ui/card";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardDescription,
|
||||
} from "~/components/ui/card";
|
||||
import { Badge } from "~/components/ui/badge";
|
||||
import { BookOpen } from "lucide-react";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
interface BlogPost {
|
||||
title: string;
|
||||
publishedAt: string;
|
||||
summary: string;
|
||||
tags?: string[];
|
||||
slug: string;
|
||||
}
|
||||
|
||||
// Helper to get blog posts
|
||||
async function getBlogPosts() {
|
||||
const contentDir = path.join(process.cwd(), "src/content/blog");
|
||||
const files = fs.readdirSync(contentDir);
|
||||
async function getBlogPosts(): Promise<BlogPost[]> {
|
||||
const contentDir = path.join(process.cwd(), "src/content/blog");
|
||||
const files = fs.readdirSync(contentDir);
|
||||
|
||||
const posts = await Promise.all(
|
||||
files
|
||||
.filter((file) => file.endsWith(".mdx"))
|
||||
.map(async (file) => {
|
||||
const slug = file.replace(".mdx", "");
|
||||
// Dynamic import to get metadata
|
||||
const { metadata } = await import(`~/content/blog/${file}`);
|
||||
return {
|
||||
slug,
|
||||
...metadata,
|
||||
};
|
||||
})
|
||||
);
|
||||
const posts = await Promise.all(
|
||||
files
|
||||
.filter((file) => file.endsWith(".mdx"))
|
||||
.map(async (file) => {
|
||||
const slug = file.replace(".mdx", "");
|
||||
// Dynamic import to get metadata
|
||||
const { metadata } = (await import(`~/content/blog/${file}`)) as {
|
||||
metadata: Omit<BlogPost, "slug">;
|
||||
};
|
||||
return {
|
||||
slug,
|
||||
...metadata,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
return posts.sort((a, b) => {
|
||||
if (new Date(a.publishedAt) > new Date(b.publishedAt)) {
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
return posts.sort((a, b) => {
|
||||
if (new Date(a.publishedAt) > new Date(b.publishedAt)) {
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
}
|
||||
|
||||
export const metadata = {
|
||||
title: "Blog",
|
||||
description: "Thoughts, tutorials, and project deep-dives.",
|
||||
title: "Blog",
|
||||
description: "Thoughts, tutorials, and project deep-dives.",
|
||||
};
|
||||
|
||||
export default async function BlogPage() {
|
||||
const posts = await getBlogPosts();
|
||||
const posts = await getBlogPosts();
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<section className="animate-fade-in-up prose prose-zinc dark:prose-invert max-w-none">
|
||||
<div className="flex items-start gap-3">
|
||||
<BookOpen className="h-8 w-8 text-primary" />
|
||||
<div>
|
||||
<h1 className="mb-2 text-2xl font-bold">Blog</h1>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-2 text-lg text-muted-foreground">
|
||||
Deep dives into my projects, tutorials, and thoughts on technology.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<div className="grid gap-6 animate-fade-in-up-delay-2">
|
||||
{posts.map((post) => (
|
||||
<Link key={post.slug} href={`/blog/${post.slug}`} className="block card-hover">
|
||||
<Card className="h-full transition-colors hover:bg-muted/50">
|
||||
<CardHeader>
|
||||
<div className="flex justify-between items-start">
|
||||
<CardTitle className="text-xl mb-2">{post.title}</CardTitle>
|
||||
<span className="text-sm text-muted-foreground whitespace-nowrap ml-4">
|
||||
{post.publishedAt}
|
||||
</span>
|
||||
</div>
|
||||
<CardDescription className="text-base">
|
||||
{post.summary}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{post.tags?.map((tag: string) => (
|
||||
<Badge key={tag} variant="secondary">
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<section className="animate-fade-in-up prose prose-zinc max-w-none dark:prose-invert">
|
||||
<div className="flex items-start gap-3">
|
||||
<BookOpen className="h-8 w-8 text-primary" />
|
||||
<div>
|
||||
<h1 className="mb-2 text-2xl font-bold">Blog</h1>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
<p className="mt-2 text-lg text-muted-foreground">
|
||||
Deep dives into my projects, tutorials, and thoughts on technology.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<div className="animate-fade-in-up-delay-2 grid gap-6">
|
||||
{posts.map((post) => (
|
||||
<Link
|
||||
key={post.slug}
|
||||
href={`/blog/${post.slug}`}
|
||||
className="card-hover block"
|
||||
>
|
||||
<Card className="h-full transition-colors hover:bg-muted/50">
|
||||
<CardHeader>
|
||||
<div className="flex items-start justify-between">
|
||||
<CardTitle className="mb-2 text-xl">{post.title}</CardTitle>
|
||||
<span className="ml-4 whitespace-nowrap text-sm text-muted-foreground">
|
||||
{post.publishedAt}
|
||||
</span>
|
||||
</div>
|
||||
<CardDescription className="text-base">
|
||||
{post.summary}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{post.tags?.map((tag: string) => (
|
||||
<Badge key={tag} variant="secondary">
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user