122 lines
3.6 KiB
TypeScript
122 lines
3.6 KiB
TypeScript
import type { Metadata } from "next";
|
||
import Link from "next/link";
|
||
import { notFound } from "next/navigation";
|
||
import { IconArrowNarrowLeft, IconExternalLink } from "@tabler/icons-react";
|
||
import Hero from "@/components/content/Hero";
|
||
import SectionLabel from "@/components/layout/SectionLabel";
|
||
import { colors, projects } from "@/constants";
|
||
import ProjectCarousel from "@/components/content/ProjectCarousel";
|
||
|
||
export function generateStaticParams() {
|
||
return projects.map((project) => ({ slug: project.slug }));
|
||
}
|
||
|
||
export async function generateMetadata({
|
||
params,
|
||
}: {
|
||
params: Promise<{ slug: string }>;
|
||
}): Promise<Metadata> {
|
||
const { slug } = await params;
|
||
const project = projects.find((p) => p.slug === slug);
|
||
|
||
if (!project) return { title: "Project — Angel Mankel" };
|
||
|
||
return {
|
||
title: `${project.title} — Angel Mankel`,
|
||
description: project.description,
|
||
};
|
||
}
|
||
|
||
export default async function ProjectPage({
|
||
params,
|
||
}: {
|
||
params: Promise<{ slug: string }>;
|
||
}) {
|
||
const { slug } = await params;
|
||
const project = projects.find((p) => p.slug === slug);
|
||
|
||
if (!project) notFound();
|
||
|
||
return (
|
||
<div className="space-y-14">
|
||
<Link
|
||
href="/projects"
|
||
className="inline-flex items-center gap-1 text-sm text-neutral-400 transition-colors hover:text-neutral-200"
|
||
>
|
||
<IconArrowNarrowLeft size={18} />
|
||
All projects
|
||
</Link>
|
||
|
||
<Hero label={project.title} subtitle={project.description}>
|
||
<div className="space-y-3">
|
||
<p className="text-md text-neutral-400">
|
||
{project.year} — {project.stack.join(" · ")}
|
||
</p>
|
||
|
||
<a
|
||
href={project.liveUrl}
|
||
target="_blank"
|
||
rel="noreferrer"
|
||
className="inline-flex items-center gap-2 rounded-lg border border-neutral-700 px-4 py-2 text-[15px] font-medium transition-colors hover:border-neutral-500"
|
||
style={{ color: colors.accent }}
|
||
>
|
||
Open live app
|
||
<IconExternalLink size={18} />
|
||
</a>
|
||
</div>
|
||
</Hero>
|
||
|
||
{/* Screenshots */}
|
||
{project.screenshots.length > 0 && (
|
||
<ProjectCarousel screenshots={project.screenshots} />
|
||
)}
|
||
|
||
{/* Overview */}
|
||
<section className="space-y-4">
|
||
<SectionLabel label="Overview" />
|
||
<div className="text-[17px] leading-[1.65] text-neutral-200">
|
||
{project.overview[0]}
|
||
</div>
|
||
</section>
|
||
|
||
{/* Problem Solved */}
|
||
<section className="space-y-4">
|
||
<SectionLabel label="Problem Solved" />
|
||
<div className="text-[17px] leading-[1.65] text-neutral-200">
|
||
{project.overview[1]}
|
||
</div>
|
||
</section>
|
||
|
||
{/* What I learned */}
|
||
<section className="space-y-4">
|
||
<SectionLabel label="What I learned" />
|
||
<ul className="space-y-2 text-[15px] leading-[1.65] text-neutral-200">
|
||
{project.learned.map((item) => (
|
||
<li
|
||
key={item}
|
||
className="before:mr-2 before:text-neutral-600 before:content-['–']"
|
||
>
|
||
{item}
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</section>
|
||
|
||
{/* What I'd do differently */}
|
||
<section className="space-y-4">
|
||
<SectionLabel label="What I'd do differently" />
|
||
<ul className="space-y-2 text-[15px] leading-[1.65] text-neutral-200">
|
||
{project.improvements.map((item) => (
|
||
<li
|
||
key={item}
|
||
className="before:mr-2 before:text-neutral-600 before:content-['–']"
|
||
>
|
||
{item}
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</section>
|
||
</div>
|
||
);
|
||
}
|