135 lines
4 KiB
TypeScript
135 lines
4 KiB
TypeScript
import { Metadata } from "next";
|
|
import { notFound, redirect } from "next/navigation";
|
|
import { cache } from "react";
|
|
import { defineQuery } from "next-sanity";
|
|
import { sanity } from "@/lib/sanity";
|
|
|
|
import { AnimatedText } from "@/components/AnimatedText";
|
|
import { ReleaseCover } from "@/components/release/ReleaseCover";
|
|
import { StreamingLinks } from "@/components/release/StreamingLinks";
|
|
import { urlFor } from "@/lib/sanityImage";
|
|
import { ARTIST_PLACEHOLDER_SRC } from "@/lib/constants";
|
|
import { buildLinks, type Release } from "@/lib/release";
|
|
|
|
export const dynamicParams = true;
|
|
export const revalidate = 86400;
|
|
|
|
const RELEASE_BY_CATALOG_NO_QUERY = defineQuery(`
|
|
*[_type == "release" && lower(catalogNo) == $slug][0]{
|
|
name,
|
|
albumArtist,
|
|
label,
|
|
catalogNo,
|
|
albumCover,
|
|
officialUrl,
|
|
spotifyUrl,
|
|
appleMusicUrl,
|
|
deezerUrl,
|
|
amazonMusicUrl,
|
|
tidalUrl,
|
|
qobuzUrl,
|
|
}
|
|
`);
|
|
|
|
const getRelease = cache(async (slug: string) => {
|
|
try {
|
|
const release = await sanity.fetch<Release>(RELEASE_BY_CATALOG_NO_QUERY, { slug });
|
|
return release;
|
|
} catch (error) {
|
|
console.error("Failed to fetch release:", error);
|
|
return null;
|
|
}
|
|
});
|
|
|
|
const RELEASE_CATALOG_SLUGS_QUERY = defineQuery(
|
|
`*[_type == "release" && defined(catalogNo)][]{ "slug": lower(catalogNo) }`,
|
|
);
|
|
|
|
export async function generateStaticParams() {
|
|
const releases = await sanity.fetch<{ slug: string }[]>(RELEASE_CATALOG_SLUGS_QUERY);
|
|
|
|
return releases.map(({ slug }) => ({ slug }));
|
|
}
|
|
|
|
export async function generateMetadata({
|
|
params,
|
|
}: {
|
|
params: Promise<{ slug: string }>;
|
|
}): Promise<Metadata> {
|
|
const { slug } = await params;
|
|
|
|
if (!slug) return { title: "TRPTK" };
|
|
|
|
const release = await getRelease(slug.toLowerCase());
|
|
|
|
if (!release) return { title: "TRPTK" };
|
|
|
|
const description = `Listen to ${release.name}${release.albumArtist ? ` by ${release.albumArtist}` : ""} on all major streaming platforms.`;
|
|
|
|
const ogImage = release.albumCover
|
|
? urlFor(release.albumCover).width(1200).height(1200).url()
|
|
: undefined;
|
|
|
|
return {
|
|
title: `${release.albumArtist ? `${release.albumArtist} • ` : ""}${release.name}`,
|
|
description,
|
|
openGraph: {
|
|
title: release.name,
|
|
description: `${release.albumArtist || "TRPTK"}${release.label ? ` - ${release.label}` : ""}`,
|
|
type: "music.album",
|
|
...(ogImage && { images: [{ url: ogImage, width: 1200, height: 1200, alt: `${release.name} by ${release.albumArtist}` }] }),
|
|
},
|
|
twitter: {
|
|
card: "summary_large_image",
|
|
title: release.name,
|
|
description,
|
|
...(ogImage && { images: [ogImage] }),
|
|
},
|
|
};
|
|
}
|
|
|
|
export default async function ReleasePage({ params }: { params: Promise<{ slug: string }> }) {
|
|
const { slug } = await params;
|
|
if (!slug) notFound();
|
|
|
|
const normalizedSlug = slug.toLowerCase();
|
|
if (slug !== normalizedSlug) {
|
|
redirect(`/${normalizedSlug}`);
|
|
}
|
|
|
|
const release = await getRelease(slug);
|
|
if (!release) notFound();
|
|
|
|
const links = buildLinks(release);
|
|
const title = release.name ?? slug;
|
|
const albumArtist = release.albumArtist ?? "";
|
|
|
|
return (
|
|
<div className="flex h-dvh w-full flex-col">
|
|
<main className="mx-auto my-auto w-full max-w-90 px-6 py-12 text-center font-silka text-sm text-lighttext transition-all duration-1000 ease-in-out md:px-8 md:py-16 dark:text-darktext">
|
|
<header>
|
|
<ReleaseCover
|
|
src={release.albumCover ? urlFor(release.albumCover).url() : ARTIST_PLACEHOLDER_SRC}
|
|
alt={`Album cover image for ${title} by ${albumArtist}`}
|
|
/>
|
|
|
|
<div className="my-10">
|
|
<AnimatedText
|
|
text={title}
|
|
as="h1"
|
|
className="mb-2 font-argesta text-2xl break-words text-lighttext dark:text-white"
|
|
/>
|
|
<AnimatedText
|
|
text={albumArtist}
|
|
as="h2"
|
|
className="text-sm break-words text-lightsec dark:text-darksec"
|
|
delay={0.25}
|
|
/>
|
|
</div>
|
|
</header>
|
|
|
|
<StreamingLinks releaseName={release.name} links={links} />
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|