refactor: fix linting and typechecking errors

This commit is contained in:
2025-12-11 19:41:36 -05:00
parent 962f2ad7ee
commit 4f9602a242
35 changed files with 768 additions and 720 deletions
+63 -53
View File
@@ -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
View File
@@ -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>
);
}