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
+1 -1
View File
@@ -1,4 +1,4 @@
import { NextRequest, NextResponse } from "next/server";
import { type NextRequest, NextResponse } from "next/server";
import { readFile } from "fs/promises";
import { join } from "path";
+3 -3
View File
@@ -1,7 +1,7 @@
"use client";
import { ArrowUpRight, Newspaper } from "lucide-react";
import { Button } from "~/components/ui/button";
import Link from "next/link";
import {
Card,
@@ -22,12 +22,12 @@ export default function ArticlesPage() {
await new Promise((resolve) => setTimeout(resolve, 0));
setLoading(false);
};
fetchArticles();
void fetchArticles();
}, []);
return (
<div className="space-y-6">
<section className="animate-fade-in-up prose prose-zinc dark:prose-invert max-w-none">
<section className="animate-fade-in-up prose prose-zinc max-w-none dark:prose-invert">
<div className="flex items-start gap-3">
<Newspaper className="h-8 w-8 text-primary" />
<div>
+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>
);
}
+4 -8
View File
@@ -1,6 +1,6 @@
"use client";
import { useState, useRef, useEffect, useCallback } from "react";
import { useState, useRef, useEffect } from "react";
import { Tabs, TabsList, TabsTrigger, TabsContent } from "~/components/ui/tabs";
import {
Card,
@@ -22,8 +22,6 @@ import {
ChevronRight,
AlertCircle,
Loader2,
Maximize2,
Minimize2,
Eye,
} from "lucide-react";
import Link from "next/link";
@@ -116,7 +114,7 @@ function PDFViewer({ url, title, type }: PDFViewerProps) {
}
};
loadPDF();
void loadPDF();
}, [url, isClient]);
useEffect(() => {
@@ -177,7 +175,7 @@ function PDFViewer({ url, title, type }: PDFViewerProps) {
}
};
renderPage();
void renderPage();
}, [pdfDoc, pageNum, scale, rotation]);
const nextPage = () => {
@@ -481,7 +479,7 @@ export default function CVPage() {
return (
<div className="space-y-6">
<section className="animate-fade-in-up prose prose-zinc dark:prose-invert max-w-none">
<section className="animate-fade-in-up prose prose-zinc max-w-none dark:prose-invert">
<div className="flex items-start gap-3">
<FileText className="h-8 w-8 text-primary" />
<div>
@@ -520,8 +518,6 @@ export default function CVPage() {
</TabsContent>
</Tabs>
</div>
</div>
);
}
+2 -2
View File
@@ -55,7 +55,7 @@ export default function ExperiencePage() {
const renderExperienceSection = (
title: string,
experiences: typeof researchExperience,
delay: number = 1,
delay = 1,
) => (
<section className="animate-fade-in-up space-y-6">
<h2 className="text-2xl font-bold">{title}</h2>
@@ -118,7 +118,7 @@ export default function ExperiencePage() {
return (
<div className="space-y-12">
{/* Header */}
<section className="animate-fade-in-up prose prose-zinc dark:prose-invert max-w-none">
<section className="animate-fade-in-up prose prose-zinc max-w-none dark:prose-invert">
<div className="flex items-start gap-3">
<Building className="h-8 w-8 text-primary" />
<div>
+6 -7
View File
@@ -1,4 +1,3 @@
import Script from "next/script";
import type { Metadata } from "next";
import { env } from "~/env";
@@ -26,20 +25,20 @@ export default function RootLayout({ children }: React.PropsWithChildren) {
suppressHydrationWarning
>
<body
className="flex min-h-screen flex-col bg-background font-sans text-foreground relative"
className="relative flex min-h-screen flex-col bg-background font-sans text-foreground"
suppressHydrationWarning
>
{/* Background Elements */}
<div className="fixed inset-0 -z-10 overflow-hidden pointer-events-none flex items-center justify-center">
<div className="pointer-events-none fixed inset-0 -z-10 flex items-center justify-center overflow-hidden">
<div className="absolute inset-0 bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:24px_24px] [mask-image:radial-gradient(ellipse_60%_50%_at_50%_50%,#000_70%,transparent_100%)]"></div>
<div className="w-[800px] h-[800px] bg-neutral-400/40 dark:bg-neutral-500/30 rounded-full blur-3xl animate-blob"></div>
<div className="animate-blob h-[800px] w-[800px] rounded-full bg-neutral-400/40 blur-3xl dark:bg-neutral-500/30"></div>
</div>
{env.NEXT_PUBLIC_UMAMI_WEBSITE_ID && (
<Script
defer
src={
env.NEXT_PUBLIC_UMAMI_SCRIPT_URL ||
env.NEXT_PUBLIC_UMAMI_SCRIPT_URL ??
"https://analytics.umami.is/script.js"
}
data-website-id={env.NEXT_PUBLIC_UMAMI_WEBSITE_ID}
@@ -48,9 +47,9 @@ export default function RootLayout({ children }: React.PropsWithChildren) {
<BreadcrumbProvider>
<Navigation />
<div className="flex flex-1 pt-24 flex-col lg:flex-row">
<div className="flex flex-1 flex-col pt-24 lg:flex-row">
<Sidebar />
<div className="flex-1 min-w-0 lg:pl-96">
<div className="min-w-0 flex-1 lg:pl-96">
<div className="mx-auto max-w-screen-xl px-6 sm:px-8 lg:pl-0 lg:pr-8">
<main className="pb-8 pt-4">
<BreadcrumbWrapper />
+13 -14
View File
@@ -1,19 +1,20 @@
import {
ArrowUpRight,
Code,
FlaskConical,
Users,
GraduationCap,
Building,
MapPin,
Mail,
ExternalLink,
BookOpen,
School,
Award,
Calendar,
BookOpen,
Building,
Code,
ExternalLink,
FlaskConical,
GraduationCap,
Mail,
MapPin,
School,
Users
} from "lucide-react";
import Link from "next/link";
import { Badge } from "~/components/ui/badge";
import { Button } from "~/components/ui/button";
import {
Card,
CardContent,
@@ -21,9 +22,7 @@ import {
CardHeader,
CardTitle,
} from "~/components/ui/card";
import { Button } from "~/components/ui/button";
import { Badge } from "~/components/ui/badge";
import { researchInterests, education, experiences, awards } from "~/lib/data";
import { awards, education, experiences, researchInterests } from "~/lib/data";
export default function HomePage() {
const researchExperience = experiences.filter(
+180 -203
View File
@@ -1,6 +1,5 @@
"use client";
import { useEffect, useState } from "react";
import {
Card,
CardHeader,
@@ -14,21 +13,14 @@ import Link from "next/link";
import { ArrowUpRight, Play, BookOpen, FolderGit2, Github } from "lucide-react";
import { projects } from "~/lib/data";
import { ImageWithSkeleton } from "~/components/ui/image-with-skeleton";
import { CardSkeleton } from "~/components/ui/skeletons";
export default function ProjectsPage() {
const [loading, setLoading] = useState(true);
useEffect(() => {
setLoading(false);
}, []);
const featuredProjects = projects.filter((p) => p.featured);
const otherProjects = projects.filter((p) => !p.featured);
return (
<div className="space-y-8">
<section className="prose prose-zinc dark:prose-invert max-w-none">
<section className="prose prose-zinc max-w-none dark:prose-invert">
<div className="flex items-start gap-3">
<FolderGit2 className="h-8 w-8 text-primary" />
<div>
@@ -45,87 +37,207 @@ export default function ProjectsPage() {
<section className="animate-fade-in-up space-y-6">
<h2 className="text-2xl font-bold">Featured Work</h2>
<div className="space-y-8">
{loading ? (
<>
<CardSkeleton />
<CardSkeleton />
<CardSkeleton />
</>
) : (
featuredProjects.map((project, index) => (
{featuredProjects.map((project, index) => (
<div
key={index}
className={`animate-fade-in-up-delay-${Math.min(index + 1, 4)} card-hover`}
>
<Card className="overflow-hidden">
<div className="flex flex-col lg:flex-row">
{/* Project Image */}
{project.image && (
<div className="lg:w-1/3">
<div className="flex items-center justify-center p-4 lg:h-full">
<ImageWithSkeleton
src={project.image}
alt={project.imageAlt ?? project.title}
width={400}
height={300}
className="h-auto w-full object-contain"
containerClassName="w-full rounded-xl shadow-md overflow-hidden"
priority={index === 0}
/>
</div>
</div>
)}
{/* Project Content */}
<div className="card-content-stretch flex flex-1 flex-col p-6">
<div className="flex-1 space-y-4">
<div>
<CardTitle className="break-words text-xl leading-tight">
{project.title}
</CardTitle>
<CardDescription className="mt-2 break-words text-base leading-relaxed">
{project.description}
</CardDescription>
</div>
<p className="break-words leading-relaxed text-muted-foreground">
{project.longDescription}
</p>
</div>
<div className="mt-6 flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
<div className="flex flex-wrap gap-2">
{project.tags.map((tag) => (
<Badge
key={tag}
variant="secondary"
className="break-words"
>
{tag}
</Badge>
))}
</div>
<div className="flex gap-2 sm:flex-shrink-0">
{project.link && project.link.startsWith("/") && (
<Button
variant="outline"
asChild
className="button-hover"
>
<Link href={project.link}>
{project.title ===
"LaTeX Introduction Tutorial" ? (
<>
<Play className="mr-2 h-4 w-4" />
Watch Tutorial
</>
) : (
<>
<BookOpen className="mr-2 h-4 w-4" />
Learn More
</>
)}
</Link>
</Button>
)}
{project.websiteLink && (
<Button
variant="outline"
asChild
className="button-hover"
>
<Link
href={project.websiteLink}
target="_blank"
rel="noopener noreferrer"
>
<ArrowUpRight className="mr-2 h-4 w-4" />
Visit Site
</Link>
</Button>
)}
{project.gitLink && (
<Button
variant="outline"
asChild
className="button-hover"
>
<Link
href={project.gitLink}
target="_blank"
rel="noopener noreferrer"
>
<Github className="mr-2 h-4 w-4" />
View Code
</Link>
</Button>
)}
{project.link &&
!project.link.startsWith("/") &&
!project.websiteLink &&
!project.gitLink && (
<Button
variant="outline"
asChild
className="button-hover"
>
<Link
href={project.link}
target="_blank"
rel="noopener noreferrer"
>
<ArrowUpRight className="mr-2 h-4 w-4" />
View Project
</Link>
</Button>
)}
</div>
</div>
</div>
</div>
</Card>
</div>
))}
</div>
</section>
{/* Other Projects */}
{otherProjects.length > 0 && (
<section className="animate-fade-in-up space-y-6">
<h2 className="text-2xl font-bold">Additional Projects</h2>
<div className="grid-equal-height grid gap-6 md:grid-cols-2">
{otherProjects.map((project, index) => (
<div
key={index}
className={`animate-fade-in-up-delay-${Math.min(index + 1, 4)} card-hover`}
>
<Card className="overflow-hidden">
<div className="flex flex-col lg:flex-row">
{/* Project Image */}
{project.image && (
<div className="lg:w-1/3">
<div className="flex items-center justify-center p-4 lg:h-full">
<ImageWithSkeleton
src={project.image}
alt={project.imageAlt || project.title}
width={400}
height={300}
className="h-auto w-full object-contain"
containerClassName="w-full rounded-xl shadow-md overflow-hidden"
priority={index === 0}
/>
</div>
</div>
)}
<Card className="card-full-height flex flex-col">
{project.image && (
<div className="flex h-48 items-center justify-center p-4">
<ImageWithSkeleton
src={project.image}
alt={project.imageAlt ?? project.title}
width={400}
height={250}
className="h-auto max-h-full w-full object-contain"
containerClassName="w-full h-full flex items-center justify-center rounded-xl shadow-md overflow-hidden"
/>
</div>
)}
{/* Project Content */}
<div className="card-content-stretch flex flex-1 flex-col p-6">
<div className="flex-1 space-y-4">
<div className="flex flex-1 flex-col p-6">
<div className="flex flex-1 flex-col">
<CardHeader className="p-0">
<div>
<CardTitle className="break-words text-xl leading-tight">
<CardTitle className="break-words text-lg leading-tight">
{project.title}
</CardTitle>
<CardDescription className="mt-2 break-words text-base leading-relaxed">
{project.description}
</CardDescription>
</div>
<CardDescription className="mt-2 break-words leading-relaxed">
{project.description}
</CardDescription>
</CardHeader>
<p className="break-words leading-relaxed text-muted-foreground">
{project.longDescription}
</p>
</div>
<div className="mt-6 flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
<CardContent className="flex flex-1 flex-col p-0 pt-4">
<div className="flex flex-wrap gap-2">
{project.tags.map((tag) => (
<Badge
key={tag}
variant="secondary"
className="break-words"
className="break-words text-xs"
>
{tag}
</Badge>
))}
</div>
<div className="flex gap-2 sm:flex-shrink-0">
<div className="mt-auto flex gap-2 pt-4">
{project.link && project.link.startsWith("/") && (
<Button
variant="outline"
asChild
className="button-hover"
className="button-hover sm:flex-shrink-0"
>
<Link href={project.link}>
{project.title ===
"LaTeX Introduction Tutorial" ? (
<>
<Play className="mr-2 h-4 w-4" />
Watch Tutorial
</>
) : (
<>
<BookOpen className="mr-2 h-4 w-4" />
Learn More
</>
)}
<BookOpen className="mr-2 h-4 w-4" />
Learn More
</Link>
</Button>
)}
@@ -134,7 +246,7 @@ export default function ProjectsPage() {
<Button
variant="outline"
asChild
className="button-hover"
className="button-hover sm:flex-shrink-0"
>
<Link
href={project.websiteLink}
@@ -151,7 +263,7 @@ export default function ProjectsPage() {
<Button
variant="outline"
asChild
className="button-hover"
className="button-hover sm:flex-shrink-0"
>
<Link
href={project.gitLink}
@@ -171,7 +283,7 @@ export default function ProjectsPage() {
<Button
variant="outline"
asChild
className="button-hover"
className="button-hover sm:flex-shrink-0"
>
<Link
href={project.link}
@@ -184,147 +296,12 @@ export default function ProjectsPage() {
</Button>
)}
</div>
</div>
</CardContent>
</div>
</div>
</Card>
</div>
))
)}
</div>
</section>
{/* Other Projects */}
{otherProjects.length > 0 && (
<section className="animate-fade-in-up space-y-6">
<h2 className="text-2xl font-bold">Additional Projects</h2>
<div className="grid-equal-height grid gap-6 md:grid-cols-2">
{loading ? (
<>
<CardSkeleton />
<CardSkeleton />
</>
) : (
otherProjects.map((project, index) => (
<div
key={index}
className={`animate-fade-in-up-delay-${Math.min(index + 1, 4)} card-hover`}
>
<Card className="card-full-height flex flex-col">
{project.image && (
<div className="flex h-48 items-center justify-center p-4">
<ImageWithSkeleton
src={project.image}
alt={project.imageAlt || project.title}
width={400}
height={250}
className="h-auto max-h-full w-full object-contain"
containerClassName="w-full h-full flex items-center justify-center rounded-xl shadow-md overflow-hidden"
/>
</div>
)}
<div className="flex flex-1 flex-col p-6">
<div className="flex flex-1 flex-col">
<CardHeader className="p-0">
<div>
<CardTitle className="break-words text-lg leading-tight">
{project.title}
</CardTitle>
</div>
<CardDescription className="mt-2 break-words leading-relaxed">
{project.description}
</CardDescription>
</CardHeader>
<CardContent className="flex flex-1 flex-col p-0 pt-4">
<div className="flex flex-wrap gap-2">
{project.tags.map((tag) => (
<Badge
key={tag}
variant="secondary"
className="break-words text-xs"
>
{tag}
</Badge>
))}
</div>
<div className="mt-auto flex gap-2 pt-4">
{project.link && project.link.startsWith("/") && (
<Button
variant="outline"
asChild
className="button-hover sm:flex-shrink-0"
>
<Link href={project.link}>
<BookOpen className="mr-2 h-4 w-4" />
Learn More
</Link>
</Button>
)}
{project.websiteLink && (
<Button
variant="outline"
asChild
className="button-hover sm:flex-shrink-0"
>
<Link
href={project.websiteLink}
target="_blank"
rel="noopener noreferrer"
>
<ArrowUpRight className="mr-2 h-4 w-4" />
Visit Site
</Link>
</Button>
)}
{project.gitLink && (
<Button
variant="outline"
asChild
className="button-hover sm:flex-shrink-0"
>
<Link
href={project.gitLink}
target="_blank"
rel="noopener noreferrer"
>
<Github className="mr-2 h-4 w-4" />
View Code
</Link>
</Button>
)}
{project.link &&
!project.link.startsWith("/") &&
!project.websiteLink &&
!project.gitLink && (
<Button
variant="outline"
asChild
className="button-hover sm:flex-shrink-0"
>
<Link
href={project.link}
target="_blank"
rel="noopener noreferrer"
>
<ArrowUpRight className="mr-2 h-4 w-4" />
View Project
</Link>
</Button>
)}
</div>
</CardContent>
</div>
</div>
</Card>
</div>
))
)}
))}
</div>
</section>
)}
+3 -5
View File
@@ -5,7 +5,6 @@ import {
BookOpenText,
FileText,
Presentation,
BookOpen,
Monitor,
} from "lucide-react";
import Link from "next/link";
@@ -18,7 +17,6 @@ import {
CardHeader,
CardTitle,
} from "~/components/ui/card";
import { Skeleton } from "~/components/ui/skeleton";
import { CardSkeleton } from "~/components/ui/skeletons";
import type { Publication } from "~/lib/bibtex";
import { parseBibtex } from "~/lib/bibtex";
@@ -27,10 +25,10 @@ import { parseBibtex } from "~/lib/bibtex";
export default function PublicationsPage() {
const [publications, setPublications] = useState<Publication[]>([]);
const [loading, setLoading] = useState(true);
const tagsToStrip = ["paperUrl", "posterUrl"];
useEffect(() => {
fetch("/publications.bib")
void fetch("/publications.bib")
.then((res) => res.text())
.then((text) => {
const pubs = parseBibtex(text);
@@ -85,7 +83,7 @@ export default function PublicationsPage() {
return (
<div className="space-y-6">
<section className="animate-fade-in-up prose prose-zinc dark:prose-invert max-w-none">
<section className="animate-fade-in-up prose prose-zinc max-w-none dark:prose-invert">
<div className="flex items-start gap-3">
<BookOpenText className="h-8 w-8 text-primary" />
<div>
+2 -4
View File
@@ -26,7 +26,7 @@ export default function TripsPage() {
return (
<div className="space-y-6">
<section className="animate-fade-in-up prose prose-zinc dark:prose-invert max-w-none">
<section className="animate-fade-in-up prose prose-zinc max-w-none dark:prose-invert">
<div className="flex items-start gap-3">
<Plane className="h-8 w-8 text-primary" />
<div>
@@ -63,9 +63,7 @@ export default function TripsPage() {
<ImageWithSkeleton
src={image}
alt={
trip.alts && trip.alts[imgIndex]
? trip.alts[imgIndex]
: `${trip.title} - image ${imgIndex + 1}`
trip.alts?.[imgIndex] ?? `${trip.title} - image ${imgIndex + 1}`
}
width={250}
height={200}