feat: add library 53f7de11
Steve Simkins · 2026-04-25 18:54 3 file(s) · +105 −0
packages/client/src/components/page/BookCard.astro (added) +36 −0
1 +
---
2 +
type Book = {
3 +
	id: number;
4 +
	title: string;
5 +
	authors: string;
6 +
	cover_url: string | null;
7 +
	notes: string | null;
8 +
	status: "read" | "reading" | "want";
9 +
};
10 +
11 +
const { book } = Astro.props as { book: Book };
12 +
---
13 +
14 +
<article class="flex items-start gap-4 py-3 border-b border-[#333]">
15 +
	<div class="flex-shrink-0 w-14 h-20">
16 +
		{book.cover_url ? (
17 +
			<img
18 +
				src={book.cover_url}
19 +
				alt=""
20 +
				width="56"
21 +
				height="80"
22 +
				style="width:56px;height:80px;object-fit:cover;"
23 +
				loading="lazy"
24 +
			/>
25 +
		) : (
26 +
			<div style="width:56px;height:80px;" class="bg-[#2a2a2a] rounded" />
27 +
		)}
28 +
	</div>
29 +
	<div class="flex flex-col gap-1">
30 +
		<span class="text-base">{book.title}</span>
31 +
		<span class="text-xs opacity-50">{book.authors}</span>
32 +
		{book.notes && (
33 +
			<span class="text-xs opacity-70 mt-1">{book.notes}</span>
34 +
		)}
35 +
	</div>
36 +
</article>
packages/client/src/data/constants.ts +4 −0
36 36
    path: "/cellar",
37 37
  },
38 38
  {
39 +
    title: "Library",
40 +
    path: "/library",
41 +
  },
42 +
  {
39 43
    title: "Birds",
40 44
    path: "/birds",
41 45
  },
packages/client/src/pages/library.astro (added) +65 −0
1 +
---
2 +
export const prerender = false;
3 +
4 +
import PageLayout from "@/layouts/Base.astro";
5 +
import BookCard from "@/components/page/BookCard.astro";
6 +
7 +
type Book = {
8 +
	id: number;
9 +
	title: string;
10 +
	authors: string;
11 +
	cover_url: string | null;
12 +
	notes: string | null;
13 +
	status: "read" | "reading" | "want";
14 +
};
15 +
16 +
const meta = {
17 +
	title: "Library",
18 +
	description: "Books I'm reading, have read, and want to read",
19 +
};
20 +
21 +
let books: Book[] = [];
22 +
let error: string | null = null;
23 +
try {
24 +
	const res = await fetch("https://library.stevedylan.dev/api/books?status=all");
25 +
	if (res.ok) {
26 +
		books = await res.json();
27 +
	} else {
28 +
		error = `API returned ${res.status}`;
29 +
	}
30 +
} catch (e) {
31 +
	error = e instanceof Error ? e.message : "Failed to reach library API";
32 +
}
33 +
34 +
const reading = books.filter((b) => b.status === "reading");
35 +
const read = books.filter((b) => b.status === "read");
36 +
const want = books.filter((b) => b.status === "want");
37 +
38 +
const sections: { label: string; items: Book[] }[] = [
39 +
	{ label: "Reading", items: reading },
40 +
	{ label: "Read", items: read },
41 +
	{ label: "Want to Read", items: want },
42 +
];
43 +
---
44 +
45 +
<PageLayout meta={meta}>
46 +
	<div class="flex min-h-screen flex-col items-start justify-start gap-6">
47 +
		<h1 class="title">Library</h1>
48 +
		{error ? (
49 +
			<p class="text-red-400 text-sm">Could not load books: {error}</p>
50 +
		) : books.length === 0 ? (
51 +
			<p class="text-gray-400 text-sm">no books yet</p>
52 +
		) : (
53 +
			<div class="flex flex-col w-full gap-8">
54 +
				{sections.filter((s) => s.items.length > 0).map((section) => (
55 +
					<div class="flex flex-col w-full">
56 +
						<h2 class="text-sm font-semibold uppercase tracking-widest opacity-50 mb-2">{section.label}</h2>
57 +
						{section.items.map((book) => (
58 +
							<BookCard book={book} />
59 +
						))}
60 +
					</div>
61 +
				))}
62 +
			</div>
63 +
		)}
64 +
	</div>
65 +
</PageLayout>