feat: add library
53f7de11
3 file(s) · +105 −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> |
| 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 | }, |
| 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> |