diff --git a/web/src/pages/audiobooks/AudiobookLibrary.tsx b/web/src/pages/audiobooks/AudiobookLibrary.tsx new file mode 100644 index 00000000..5825604b --- /dev/null +++ b/web/src/pages/audiobooks/AudiobookLibrary.tsx @@ -0,0 +1,123 @@ +import { useState } from "react"; + +import ScrollToTopButton from "@/components/ScrollToTopButton"; +import { Skeleton } from "@/components/ui/skeleton"; +import ViewTransitionLink from "@/components/ViewTransitionLink"; +import { useAudiobookLibrary } from "@/hooks/audiobooks/useAudiobookLibrary"; + +const PAGE_SIZE = 60; + +const GRID_CLASSES = + "grid grid-cols-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-7 xl:grid-cols-8 gap-3"; + +export default function AudiobookLibrary() { + const [offset, setOffset] = useState(0); + const { data, isLoading, error } = useAudiobookLibrary({ limit: PAGE_SIZE, offset }); + + if (isLoading && !data) { + return ( +
+

Audiobooks

+
+ {Array.from({ length: 24 }).map((_, i) => ( +
+ + +
+ ))} +
+
+ ); + } + + if (error) { + return ( +
+

Audiobooks

+
+ Failed to load audiobooks: {error instanceof Error ? error.message : "Unknown error"} +
+
+ ); + } + + const items = data?.items ?? []; + const total = data?.total ?? 0; + + return ( +
+

Audiobooks

+ {items.length === 0 ? ( +
+ No audiobooks indexed yet. Set a library's type to{" "} + audiobooks{" "} + and rescan. +
+ ) : ( +
+ {items.map((item) => ( +
+ +
+ {item.poster_url ? ( + {item.title} + ) : ( +
+ {item.title || "No Cover"} +
+ )} +
+
+ + +
+ {item.title} +
+ {item.year > 0 && ( +
+ {item.year} +
+ )} +
+
+ ))} +
+ )} + {total > PAGE_SIZE && ( +
+ + + {offset + 1}–{Math.min(offset + PAGE_SIZE, total)} of {total} + + +
+ )} + +
+ ); +}