Files
portfolio/app/components/Projects.tsx

131 lines
5.1 KiB
TypeScript

"use client";
import { useState, useEffect } from "react";
import { motion } from "framer-motion";
import { ArrowUpRight } from "lucide-react";
import Link from "next/link";
import Image from "next/image";
import { useLocale, useTranslations } from "next-intl";
import { Skeleton } from "boneyard-js/react";
import ProjectThumbnail from "./ProjectThumbnail";
interface Project {
id: number;
slug: string;
title: string;
description: string;
content: string;
tags: string[];
featured: boolean;
category: string;
date: string;
github?: string;
live?: string;
imageUrl?: string;
}
const Projects = () => {
const [projects, setProjects] = useState<Project[]>([]);
const [loading, setLoading] = useState(true);
const locale = useLocale();
useTranslations("home.projects");
useEffect(() => {
const loadProjects = async () => {
try {
const response = await fetch("/api/projects?featured=true&published=true&limit=6");
if (response.ok) {
const data = await response.json();
setProjects(data.projects || []);
}
} catch (error) {
console.error("Featured projects fetch failed:", error);
} finally {
setLoading(false);
}
};
loadProjects();
}, []);
return (
<section id="projects" className="py-16 sm:py-24 md:py-32 px-4 sm:px-6 bg-stone-50 dark:bg-stone-950">
<div className="max-w-7xl mx-auto">
<div className="flex flex-col md:flex-row justify-between items-start md:items-end mb-8 sm:mb-12 md:mb-16 gap-4 sm:gap-6">
<div>
<h2 className="text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-black text-stone-900 dark:text-stone-50 tracking-tighter mb-2 sm:mb-4 uppercase">
Selected Work<span className="text-emerald-600 dark:text-emerald-400">.</span>
</h2>
<p className="text-base sm:text-lg md:text-xl text-stone-500 max-w-xl font-light">
Projects that pushed my boundaries.
</p>
</div>
<Link href={`/${locale}/projects`} className="group flex items-center gap-2 text-stone-900 dark:text-stone-100 font-black border-b-2 border-stone-900 dark:border-stone-100 pb-1 hover:opacity-70 transition-all text-xs uppercase tracking-widest">
View Archive <ArrowUpRight className="group-hover:-translate-y-1 group-hover:translate-x-1 transition-transform" size={14} />
</Link>
</div>
<Skeleton name="projects-grid" loading={loading} animate="shimmer" transition>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 md:gap-12">
{projects.length === 0 && !loading ? (
<div className="col-span-2 py-12 text-center text-stone-400 dark:text-stone-600 text-sm">
No projects yet.
</div>
) : (
projects.map((project) => (
<motion.div
key={project.id}
className="group relative"
>
<Link href={`/${locale}/projects/${project.slug}`} className="block">
{/* Image Card */}
<div className="relative aspect-[16/10] sm:aspect-[4/3] rounded-2xl sm:rounded-3xl overflow-hidden bg-stone-200 dark:bg-stone-900 mb-4 sm:mb-6">
{project.imageUrl ? (
<Image
src={project.imageUrl}
alt={project.title}
fill
className="object-cover transition-transform duration-700 group-hover:scale-105"
/>
) : (
<ProjectThumbnail
title={project.title}
category={project.category}
tags={project.tags}
slug={project.slug}
size="card"
/>
)}
{/* Overlay on Hover */}
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/10 transition-colors duration-500" />
</div>
{/* Text Content */}
<div className="flex justify-between items-start">
<div>
<h3 className="text-lg sm:text-xl md:text-2xl font-bold text-stone-900 dark:text-stone-100 mb-1 sm:mb-2 group-hover:underline decoration-2 underline-offset-4">
{project.title}
</h3>
<p className="text-sm sm:text-base text-stone-500 dark:text-stone-400 line-clamp-2 max-w-md">
{project.description}
</p>
</div>
<div className="hidden sm:flex gap-2">
{project.tags.slice(0, 2).map(tag => (
<span key={tag} className="px-3 py-1 rounded-full border border-stone-200 dark:border-stone-800 text-xs font-medium text-stone-600 dark:text-stone-400">
{tag}
</span>
))}
</div>
</div>
</Link>
</motion.div>
)))}
</div>
</Skeleton>
</div>
</section>
);
};
export default Projects;