Die Posterkachel zog über lib/images.ts die serverseitige Cache-Logik in den Browser-Bundle, sobald sie aus einer Client-Komponente heraus gerendert wurde — der Build brach ab, sobald eine solche Seite dazukam. Die reine URL-Bildung liegt jetzt in lib/image-url.ts und darf überall verwendet werden; Abruf und Dateicache bleiben hinter server-only.
97 lines
2.8 KiB
TypeScript
97 lines
2.8 KiB
TypeScript
import { posterUrl } from "@/lib/image-url";
|
|
|
|
/**
|
|
* Streamingdienste als Badges.
|
|
*
|
|
* Farbe und Logo kommen aus dem Katalog; Dienste ohne hinterlegte Akzentfarbe
|
|
* bekommen einen neutralen Ton, damit die Reihe trotzdem ruhig aussieht.
|
|
*/
|
|
|
|
export type ProviderBadgeData = {
|
|
id: string;
|
|
name: string;
|
|
logoPath: string | null;
|
|
color: string | null;
|
|
};
|
|
|
|
const KIND_LABEL = {
|
|
FLATRATE: "Im Abo",
|
|
FREE: "Kostenlos",
|
|
ADS: "Mit Werbung",
|
|
RENT: "Leihen",
|
|
BUY: "Kaufen",
|
|
} as const;
|
|
|
|
export type ProviderKindKey = keyof typeof KIND_LABEL;
|
|
|
|
export function ProviderBadge({ provider }: { provider: ProviderBadgeData }) {
|
|
const logo = posterUrl(provider.logoPath, "w92");
|
|
const accent = provider.color ?? "var(--fl-border-strong)";
|
|
|
|
return (
|
|
<span
|
|
className="inline-flex items-center gap-1.5 rounded-full border py-1 pl-1 pr-3 text-xs font-medium"
|
|
style={{
|
|
borderColor: `color-mix(in oklab, ${accent} 55%, transparent)`,
|
|
backgroundColor: `color-mix(in oklab, ${accent} 14%, transparent)`,
|
|
}}
|
|
>
|
|
{/* Farbpunkt liegt darunter; ein Logo, das nicht lädt, fällt in sich
|
|
zusammen und gibt ihn wieder frei. */}
|
|
<span className="relative h-5 w-5 shrink-0">
|
|
<span
|
|
aria-hidden
|
|
className="absolute inset-0 rounded-full"
|
|
style={{ backgroundColor: accent }}
|
|
/>
|
|
{logo && (
|
|
// eslint-disable-next-line @next/next/no-img-element
|
|
<img
|
|
src={logo}
|
|
alt=""
|
|
loading="lazy"
|
|
decoding="async"
|
|
className="absolute inset-0 h-5 w-5 rounded-full object-cover"
|
|
/>
|
|
)}
|
|
</span>
|
|
<span className="text-[var(--fl-text)]">{provider.name}</span>
|
|
</span>
|
|
);
|
|
}
|
|
|
|
/** Alle Verfügbarkeiten eines Titels, nach Art gruppiert. */
|
|
export function ProviderList({
|
|
groups,
|
|
region,
|
|
}: {
|
|
groups: Partial<Record<ProviderKindKey, ProviderBadgeData[]>>;
|
|
region: string;
|
|
}) {
|
|
const order: ProviderKindKey[] = ["FLATRATE", "FREE", "ADS", "RENT", "BUY"];
|
|
const present = order.filter((kind) => (groups[kind]?.length ?? 0) > 0);
|
|
|
|
if (present.length === 0) {
|
|
return (
|
|
<p className="text-sm text-[var(--fl-faint)]">
|
|
Für die Region {region} meldet TMDB derzeit keinen Streamingdienst.
|
|
</p>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-3">
|
|
{present.map((kind) => (
|
|
<div key={kind} className="flex flex-wrap items-center gap-2">
|
|
<span className="w-24 shrink-0 text-xs font-semibold uppercase tracking-wide text-[var(--fl-faint)]">
|
|
{KIND_LABEL[kind]}
|
|
</span>
|
|
{groups[kind]?.map((provider) => (
|
|
<ProviderBadge key={`${kind}-${provider.id}`} provider={provider} />
|
|
))}
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|