katheryn-frontend/src/app/gallery/[slug]/page.tsx

205 lines
6.5 KiB
TypeScript

import { Metadata } from 'next';
import Image from 'next/image';
import Link from 'next/link';
import { notFound } from 'next/navigation';
import { getArtwork, getArtworks, getAssetUrl, Artwork } from '@/lib/directus';
export const revalidate = 60;
type Props = {
params: Promise<{ slug: string }>;
};
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params;
try {
const artwork = await getArtwork(slug);
if (!artwork) return { title: 'Artwork Not Found' };
return {
title: artwork.title,
description: artwork.description?.replace(/<[^>]*>/g, '').slice(0, 160) || `${artwork.title} by Katheryn Trenshaw`,
openGraph: {
images: artwork.image
? [getAssetUrl(artwork.image, { width: 1200, quality: 85 })]
: undefined,
},
};
} catch {
return { title: 'Artwork Not Found' };
}
}
export async function generateStaticParams() {
try {
const artworks = await getArtworks({ status: 'published' });
return artworks.map((artwork) => ({
slug: String(artwork.slug || artwork.id),
}));
} catch {
return [];
}
}
export default async function ArtworkPage({ params }: Props) {
const { slug } = await params;
let artwork: Artwork;
try {
artwork = await getArtwork(slug);
if (!artwork) notFound();
} catch {
notFound();
}
const imageUrl = getAssetUrl(artwork.image, { width: 1200, quality: 90 });
return (
<div className="min-h-screen py-12">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
{/* Breadcrumb */}
<nav className="mb-8">
<ol className="flex items-center space-x-2 text-sm text-gray-500">
<li>
<Link href="/" className="hover:text-gray-700">
Home
</Link>
</li>
<li>/</li>
<li>
<Link href="/gallery" className="hover:text-gray-700">
Gallery
</Link>
</li>
<li>/</li>
<li className="text-gray-900">{artwork.title}</li>
</ol>
</nav>
<div className="grid gap-12 lg:grid-cols-2">
{/* Image */}
<div className="relative aspect-square overflow-hidden rounded-lg bg-gray-100">
{artwork.image ? (
<Image
src={imageUrl}
alt={artwork.title}
fill
sizes="(max-width: 1024px) 100vw, 50vw"
className="object-contain"
priority
/>
) : (
<div className="flex h-full items-center justify-center text-gray-400">
<span>No image available</span>
</div>
)}
</div>
{/* Details */}
<div>
<h1 className="font-serif text-4xl font-bold text-gray-900">
{artwork.title}
</h1>
{artwork.year && (
<p className="mt-2 text-lg text-gray-500">{artwork.year}</p>
)}
<dl className="mt-8 space-y-4">
{artwork.medium && (
<div>
<dt className="text-sm font-medium text-gray-500">Medium</dt>
<dd className="mt-1 text-gray-900">{artwork.medium}</dd>
</div>
)}
{artwork.dimensions && (
<div>
<dt className="text-sm font-medium text-gray-500">Dimensions</dt>
<dd className="mt-1 text-gray-900">{artwork.dimensions}</dd>
</div>
)}
{typeof artwork.series === 'object' && artwork.series?.name && (
<div>
<dt className="text-sm font-medium text-gray-500">Series</dt>
<dd className="mt-1">
<Link
href={`/gallery/series/${artwork.series.slug || artwork.series.id}`}
className="text-amber-700 hover:text-amber-800"
>
{artwork.series.name}
</Link>
</dd>
</div>
)}
</dl>
{/* Price & Status */}
<div className="mt-8 rounded-lg bg-gray-50 p-6">
{artwork.status === 'sold' ? (
<div>
<span className="inline-block rounded bg-red-100 px-3 py-1 text-sm font-medium text-red-800">
Sold
</span>
<p className="mt-2 text-sm text-gray-600">
This artwork has found a new home. Contact us for similar works.
</p>
</div>
) : artwork.price ? (
<div>
<p className="text-3xl font-bold text-gray-900">
${artwork.price.toLocaleString()}
</p>
<button className="mt-4 w-full rounded-md bg-amber-700 px-6 py-3 font-medium text-white transition hover:bg-amber-800">
Enquire About This Piece
</button>
</div>
) : (
<div>
<p className="text-gray-600">Price on request</p>
<button className="mt-4 w-full rounded-md bg-amber-700 px-6 py-3 font-medium text-white transition hover:bg-amber-800">
Contact for Details
</button>
</div>
)}
</div>
{/* Description */}
{artwork.description && (
<div className="mt-8">
<h2 className="text-lg font-medium text-gray-900">About This Piece</h2>
<div
className="prose prose-gray mt-4"
dangerouslySetInnerHTML={{ __html: artwork.description }}
/>
</div>
)}
</div>
</div>
{/* Back to Gallery */}
<div className="mt-12">
<Link
href="/gallery"
className="inline-flex items-center text-amber-700 hover:text-amber-800"
>
<svg
className="mr-2 h-4 w-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M15 19l-7-7 7-7"
/>
</svg>
Back to Gallery
</Link>
</div>
</div>
</div>
);
}