trptk/app/artists/page.tsx
2026-02-24 17:14:07 +01:00

153 lines
4.1 KiB
TypeScript

import type { Metadata } from "next";
import { defineQuery } from "next-sanity";
import { sanity } from "@/lib/sanity";
import { ArtistCard } from "@/components/artist/ArtistCard";
import type { ArtistCardData } from "@/components/artist/types";
import { AnimatedText } from "@/components/AnimatedText";
import { SearchBar } from "@/components/SearchBar";
import { PaginationNav } from "@/components/PaginationNav";
import { CARD_GRID_CLASSES_4 } from "@/lib/constants";
import { PAGE_SIZE, clampInt, normalizeQuery, groqLikeParam } from "@/lib/listHelpers";
import type { SortOption } from "@/components/SortDropdown";
export const revalidate = 86400;
type SortMode = "name" | "sortKey";
const SORT_OPTIONS: SortOption<SortMode>[] = [
{ value: "name", label: "Sort by first name" },
{ value: "sortKey", label: "Sort by last name" },
];
function normalizeSort(s: string | undefined): SortMode {
return s === "sortKey" ? "sortKey" : "name";
}
const ARTISTS_BY_NAME_QUERY = defineQuery(`
*[
_type == "artist" &&
(
$q == "" ||
name match $qPattern ||
role match $qPattern
)
]
| order(lower(name) asc)
[$start...$end]{
_id,
name,
role,
"slug": slug.current,
image
}
`);
const ARTISTS_BY_SORT_KEY_QUERY = defineQuery(`
*[
_type == "artist" &&
(
$q == "" ||
name match $qPattern ||
role match $qPattern
)
]
| order(lower(coalesce(sortKey, name)) asc, lower(name) asc)
[$start...$end]{
_id,
name,
role,
"slug": slug.current,
image
}
`);
const ARTISTS_COUNT_QUERY = defineQuery(`
count(*[
_type == "artist" &&
(
$q == "" ||
name match $qPattern ||
role match $qPattern
)
])
`);
export const metadata: Metadata = {
title: "Artists",
description:
"Browse all TRPTK artists. Discover performers, soloists, and ensembles featured on our recordings.",
};
export default async function ArtistsPage({
searchParams,
}: {
searchParams: Promise<{ page?: string; q?: string; sort?: string }>;
}) {
const sp = await searchParams;
const q = normalizeQuery(sp.q);
const sort = normalizeSort(sp.sort);
const page = clampInt(sp.page, 1, 1, 9999);
const start = (page - 1) * PAGE_SIZE;
const end = start + PAGE_SIZE;
const qPattern = q ? `${groqLikeParam(q)}*` : "";
const listQuery = sort === "sortKey" ? ARTISTS_BY_SORT_KEY_QUERY : ARTISTS_BY_NAME_QUERY;
const [artists, total] = await Promise.all([
sanity.fetch<ArtistCardData[]>(listQuery, { start, end, q, qPattern }),
sanity.fetch<number>(ARTISTS_COUNT_QUERY, { q, qPattern }),
]);
const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));
const safePage = Math.min(page, totalPages);
const buildHref = (nextPage: number) => {
const params = new URLSearchParams();
if (q) params.set("q", q);
if (sort !== "name") params.set("sort", sort);
if (nextPage > 1) params.set("page", String(nextPage));
const qs = params.toString();
return qs ? `/artists?${qs}` : "/artists";
};
return (
<main className="mx-auto my-auto max-w-300 px-6 py-12 font-silka md:px-8 md:py-16 2xl:max-w-400">
<div className="mb-10">
<AnimatedText text="Artists" as="h1" className="font-argesta text-3xl" />
<p className="mt-2 text-base text-lightsec dark:text-darksec">
{total ? `${total} artist(s)` : "No artists found."}
</p>
</div>
<div className="mb-12">
<SearchBar
initialQuery={q}
initialSort={sort}
initialPage={safePage}
defaultSort="name"
placeholder="Search artists…"
sortOptions={SORT_OPTIONS}
sortAriaLabel="Sort artists"
/>
</div>
<section className={CARD_GRID_CLASSES_4}>
{artists.map((artist) => (
<ArtistCard
key={artist._id}
name={artist.name}
subtitle={artist.role}
image={artist.image}
href={artist.slug ? `/artist/${artist.slug}` : "/artists/"}
/>
))}
</section>
{totalPages > 1 ? (
<PaginationNav page={safePage} totalPages={totalPages} buildHref={buildHref} />
) : null}
</main>
);
}