import type { Metadata } from "next"; import { notFound, redirect } from "next/navigation"; import { cache } from "react"; import { defineQuery } from "next-sanity"; import { sanity } from "@/lib/sanity"; import { ArtistTabs } from "@/components/artist/ArtistTabs"; import { ArtistReleasesTab, type ArtistRelease } from "@/components/artist/ArtistReleasesTab"; import { ReleaseCover } from "@/components/release/ReleaseCover"; import { AnimatedText } from "@/components/AnimatedText"; import { urlFor } from "@/lib/sanityImage"; import { ARTIST_PLACEHOLDER_SRC } from "@/lib/constants"; import { Breadcrumb } from "@/components/Breadcrumb"; export const dynamicParams = true; export const revalidate = 86400; type ComposedWork = { title?: string; slug?: string; originalComposerName?: string; arrangerName?: string; }; type Composer = { name?: string; slug?: string; birthYear?: number; deathYear?: number; bio?: any; image?: any; releases?: ArtistRelease[]; composedWorks?: ComposedWork[]; arrangedWorks?: ComposedWork[]; }; const COMPOSER_DETAIL_QUERY = defineQuery(` *[_type == "composer" && slug.current == $slug][0]{ name, birthYear, deathYear, "slug": slug.current, bio, image, "releases": *[ _type == "release" && count(tracks[work->composer->slug.current == $slug]) > 0 ] | order(releaseDate desc, catalogNo desc) { _id, name, albumArtist, catalogNo, "slug": slug.current, releaseDate, albumCover, genre, instrumentation }, "composedWorks": *[ _type == "work" && composer->slug.current == $slug ] | order(title asc, defined(arranger) asc, arranger->name asc) { title, "slug": slug.current, "arrangerName": arranger->name }, "arrangedWorks": *[ _type == "work" && arranger->slug.current == $slug ] | order(coalesce(composer->sortKey, composer->name) asc, title asc) { title, "slug": slug.current, "originalComposerName": composer->name, "originalComposerSortKey": composer->sortKey } } `); const getComposer = cache(async (slug: string) => { try { const composer = await sanity.fetch(COMPOSER_DETAIL_QUERY, { slug }); return composer; } catch (error) { console.error("Failed to fetch composer:", error); return null; } }); const COMPOSER_SLUGS_QUERY = defineQuery( `*[_type == "composer" && defined(slug.current)]{ "slug": slug.current }`, ); export async function generateStaticParams() { const slugs = await sanity.fetch>(COMPOSER_SLUGS_QUERY); return slugs.map((c) => ({ slug: c.slug })); } export async function generateMetadata({ params, }: { params: Promise<{ slug: string }>; }): Promise { const { slug: rawSlug } = await params; const slug = rawSlug.toLowerCase(); const composer = await getComposer(slug); if (!composer) notFound(); const years = formatYears(composer.birthYear, composer.deathYear); const description = `Explore ${composer.name}'s works${years ? ` (${years})` : ""} and releases on TRPTK.`; const ogImage = composer.image ? urlFor(composer.image).width(1200).height(630).url() : undefined; return { title: `${composer.name}${years ? ` (${years})` : ""}`, description, alternates: { canonical: `/composer/${slug}` }, openGraph: { title: composer.name, description: `Composer${years ? ` (${years})` : ""}${composer.composedWorks?.length ? ` - ${composer.composedWorks.length} works` : ""}`, type: "profile", ...(ogImage && { images: [{ url: ogImage, width: 1200, height: 630, alt: composer.name }] }), }, twitter: { card: "summary_large_image", title: composer.name, description, ...(ogImage && { images: [ogImage] }), }, }; } const formatYears = (birthYear?: number, deathYear?: number): string => { if (birthYear && deathYear) return `${birthYear}–${deathYear}`; if (birthYear) return `${birthYear}`; return ""; }; export default async function ComposerPage({ params }: { params: Promise<{ slug: string }> }) { const { slug } = await params; if (!slug) notFound(); const normalizedSlug = slug.toLowerCase(); if (slug !== normalizedSlug) { redirect(`/composer/${normalizedSlug}`); } const composer = await getComposer(slug); if (!composer) notFound(); const displayName = composer.name ?? ""; const displayYears = formatYears(composer.birthYear, composer.deathYear); const jsonLd = { "@context": "https://schema.org", "@type": "Person", name: composer.name, url: `https://trptk.com/composer/${slug}`, ...(composer.birthYear && { birthDate: String(composer.birthYear) }), ...(composer.deathYear && { deathDate: String(composer.deathYear) }), ...(composer.image && { image: urlFor(composer.image).width(800).url() }), }; return ( <>