portfolio/app/projects/[category]/page.tsx
2026-01-29 20:54:37 +01:00

103 lines
3.6 KiB
TypeScript

"use client";
import { motion } from "framer-motion";
import Link from "next/link";
import { Globe, Smartphone, ArrowLeft, Server } from "lucide-react";
import { use } from "react";
import { PROJECT_REGISTRY } from "@/data/projects";
const CATEGORY_META = {
web: {
title: "Web Systems",
icon: <Globe className="w-8 h-8 text-blue-400" />,
description: "Architecting scalable web applications and distributed systems.",
},
mobile: {
title: "Mobile Apps",
icon: <Smartphone className="w-8 h-8 text-purple-400" />,
description: "Building cross-platform experiences with Flutter and native integrations.",
},
infrastructure: {
title: "DevOps & Infrastructure",
icon: <Server className="w-8 h-8 text-green-400" />,
description: "Self-hosted systems architecture and automated deployment pipelines.",
}
};
export default function CategoryPage({ params }: { params: Promise<{ category: string }> }) {
const resolvedParams = use(params);
const category = resolvedParams.category;
// 1. Get metadata for the header
const meta = CATEGORY_META[category as keyof typeof CATEGORY_META];
// 2. Filter the registry to find projects belonging to this category
const filteredProjects = PROJECT_REGISTRY.filter(p => p.category === category);
if (!meta) {
return (
<div className="p-24 text-white font-mono">
<h1 className="text-2xl mb-4">404: Category Not Found</h1>
<Link href="/" className="text-blue-400 underline mt-4 block">Return Home</Link>
</div>
);
}
return (
<main className="min-h-screen bg-[#0a0a0a] text-white p-8 md:p-24">
<Link href="/" className="flex items-center gap-2 text-neutral-500 hover:text-white transition-colors mb-12 font-mono text-xs">
<ArrowLeft size={14} /> BACK TO DASHBOARD
</Link>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="max-w-5xl mx-auto"
>
<div className="flex items-center gap-4 mb-6">
{meta.icon}
<h1 className="text-5xl font-bold">{meta.title}</h1>
</div>
<p className="text-xl text-neutral-400 max-w-2xl mb-16">{meta.description}</p>
<div className="grid grid-cols-1 gap-8">
{filteredProjects.map((project, index) => (
<Link
key={project.slug}
href={`/projects/${category}/${project.slug}`}>
<motion.div
layout
initial={{ opacity: 0, x: -20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{
delay: index * 0.1,
duration: 0.4,
ease: "easeOut"
}}
className="group p-8 rounded-3xl bg-neutral-900/50 border border-neutral-800 hover:border-neutral-700 transition-colors"
>
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
<div>
<h3 className="text-2xl font-semibold mb-2">{project.title}</h3>
<p className="text-neutral-500">{project.description}</p>
</div>
<div className="flex flex-wrap gap-2">
{project.stack.map(s => (
<span key={s} className="text-[10px] font-mono bg-neutral-800 text-neutral-400 px-3 py-1 rounded-full uppercase">
{s}
</span>
))}
</div>
</div>
</motion.div>
</Link>
))}
</div>
</motion.div>
</main>
);
}