205 lines
6.5 KiB
TypeScript
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>
|
|
);
|
|
}
|