From eeb22e47d3bd2628c82ecb6100b9207e459d9cdc Mon Sep 17 00:00:00 2001 From: RXWatcher <14085001+RXWatcher@users.noreply.github.com> Date: Mon, 25 May 2026 01:42:53 +0200 Subject: [PATCH] feat(audiobooks): render Also-by-author / In-series rails when API exposes them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Frontend gains a feature-detected display path for two relationship rails. Detail responses may now carry also_by_author and in_series. When present, the detail page mounts Also-by-author followed by In-series, with the current book highlighted inside the series strip. When the backend omits them — which is the case today — the rails are not rendered, so this lands as zero visible change until the backend ships the data. Co-Authored-By: Claude Opus 4.7 (1M context) --- web/src/lib/audiobooks/types.ts | 19 +++++++ web/src/pages/audiobooks/AudiobookDetail.tsx | 26 ++++++++++ .../audiobooks/components/RelatedRail.tsx | 49 +++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 web/src/pages/audiobooks/components/RelatedRail.tsx diff --git a/web/src/lib/audiobooks/types.ts b/web/src/lib/audiobooks/types.ts index a3973791..1b2a37c1 100644 --- a/web/src/lib/audiobooks/types.ts +++ b/web/src/lib/audiobooks/types.ts @@ -43,10 +43,29 @@ export interface AudiobookDetailItem { genres?: string[]; } +export interface AudiobookRelatedItem { + content_id: string; + title: string; + poster_url?: string; + year?: number; +} + +export interface AudiobookSeriesEntry { + content_id: string; + title: string; + poster_url?: string; + series_index?: number; +} + export interface AudiobookDetailResponse { audiobook: AudiobookDetailItem; author?: string; narrator?: string; files: AudiobookFile[]; progress?: AudiobookProgress; + also_by_author?: AudiobookRelatedItem[]; + in_series?: { + name?: string; + entries: AudiobookSeriesEntry[]; + }; } diff --git a/web/src/pages/audiobooks/AudiobookDetail.tsx b/web/src/pages/audiobooks/AudiobookDetail.tsx index fad17287..19cbbf96 100644 --- a/web/src/pages/audiobooks/AudiobookDetail.tsx +++ b/web/src/pages/audiobooks/AudiobookDetail.tsx @@ -7,6 +7,7 @@ import { Play } from "lucide-react"; import AudiobookPlayer from "./player/AudiobookPlayer"; import { ChaptersSection } from "./components/ChaptersSection"; import { NarratorCard } from "./components/NarratorCard"; +import { RelatedRail } from "./components/RelatedRail"; import DetailHero from "@/pages/ItemDetail/DetailHero"; import MetadataBadges from "@/pages/ItemDetail/components/MetadataBadges"; import type { AudiobookChapter, AudiobookFile } from "@/lib/audiobooks/types"; @@ -231,6 +232,31 @@ export default function AudiobookDetail() { onSelect={(s) => openPlayer(s)} /> {narrator && } + + {data.also_by_author && data.also_by_author.length > 0 && ( + ({ + content_id: it.content_id, + title: it.title, + poster_url: it.poster_url, + subtitle: it.year ? String(it.year) : undefined, + }))} + /> + )} + + {data.in_series && data.in_series.entries.length > 0 && ( + ({ + content_id: it.content_id, + title: it.title, + poster_url: it.poster_url, + subtitle: typeof it.series_index === "number" ? `Book ${it.series_index}` : undefined, + highlight: it.content_id === contentId, + }))} + /> + )} ); diff --git a/web/src/pages/audiobooks/components/RelatedRail.tsx b/web/src/pages/audiobooks/components/RelatedRail.tsx new file mode 100644 index 00000000..28408132 --- /dev/null +++ b/web/src/pages/audiobooks/components/RelatedRail.tsx @@ -0,0 +1,49 @@ +import ViewTransitionLink from "@/components/ViewTransitionLink"; + +interface RelatedRailItem { + content_id: string; + title: string; + poster_url?: string; + subtitle?: string; + highlight?: boolean; +} + +interface RelatedRailProps { + heading: string; + items: RelatedRailItem[]; +} + +export function RelatedRail({ heading, items }: RelatedRailProps) { + if (items.length === 0) return null; + return ( +
+

{heading}

+
+ {items.map((item) => ( + +
+ {item.poster_url ? ( + {item.title} + ) : null} +
+
{item.title}
+ {item.subtitle && ( +
{item.subtitle}
+ )} +
+ ))} +
+
+ ); +}