From a538c6734fbd2831404b450381ade7515d8b5e1f Mon Sep 17 00:00:00 2001 From: Martin Alexander Kaminski Date: Wed, 16 Sep 2026 16:41:47 +0200 Subject: [PATCH 1/6] feat: add more search params to open library API --- src/api/openLibrary.ts | 11 ++++++++++- src/api/types.ts | 6 +++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/api/openLibrary.ts b/src/api/openLibrary.ts index 50a4395..3fdf099 100644 --- a/src/api/openLibrary.ts +++ b/src/api/openLibrary.ts @@ -23,8 +23,17 @@ async function searchBooks( ): Promise { const url = new URL(SEARCH_URL); url.searchParams.set("q", params.query); - url.searchParams.set("limit", String(SEARCH_LIMIT)); + url.searchParams.set( + "limit", + params.limit ? String(params.limit) : String(SEARCH_LIMIT), + ); url.searchParams.set("fields", SEARCH_FIELDS.join(",")); + url.searchParams.set("lang", params.lang ?? "en"); + url.searchParams.set("page", params.page ? String(params.page) : "1"); + + if (params.sort) { + url.searchParams.set("sort", params.sort); + } const response = await fetch(url); diff --git a/src/api/types.ts b/src/api/types.ts index c3df2c5..d25723a 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -14,7 +14,11 @@ export interface OpenLibraryDoc { export interface BookSearchParams { query: string; - // Add more search parameters here as needed. + fields?: string; + sort?: string; + lang?: string; + limit?: number; + page?: number; } export interface Book { From 2c9d89a170231217d0cc369e7d2f3a574663e57f Mon Sep 17 00:00:00 2001 From: Martin Alexander Kaminski Date: Wed, 16 Sep 2026 16:47:50 +0200 Subject: [PATCH 2/6] feat: add basic pagination to BookGrid --- src/components/BookGrid/BookGrid.tsx | 38 ++++++++++-------- .../Pagination/Pagination.module.css | 39 ++++++++++++++++++ src/components/Pagination/Pagination.tsx | 40 +++++++++++++++++++ src/index.css | 2 + 4 files changed, 103 insertions(+), 16 deletions(-) create mode 100644 src/components/Pagination/Pagination.module.css create mode 100644 src/components/Pagination/Pagination.tsx diff --git a/src/components/BookGrid/BookGrid.tsx b/src/components/BookGrid/BookGrid.tsx index d41832e..335ab24 100644 --- a/src/components/BookGrid/BookGrid.tsx +++ b/src/components/BookGrid/BookGrid.tsx @@ -2,24 +2,21 @@ import { BookCard } from "../BookCard/BookCard"; import styles from "./BookGrid.module.css"; import { useBooks } from "../../api/useBooks"; import type { Book } from "../../api/types"; +import { Pagination } from "../Pagination/Pagination"; +import { useState } from "react"; interface BookGridProps { onBookSelect: (book: Book) => void; } function BookGrid({ onBookSelect }: BookGridProps) { + const [page, setPage] = useState(1); + const { data, isPending, isError, error } = useBooks({ query: "subject:fantasy", + page, }); - if (isPending) { - return ( -

- Loading books... -

- ); - } - if (isError) { return (

@@ -28,7 +25,7 @@ function BookGrid({ onBookSelect }: BookGridProps) { ); } - if (data.length === 0) { + if (data && data.length === 0) { return (

No books found for this search @@ -37,13 +34,22 @@ function BookGrid({ onBookSelect }: BookGridProps) { } return ( -

    - {data.map((book) => ( -
  • - -
  • - ))} -
+ <> + {!isPending ? ( +
    + {data.map((book) => ( +
  • + +
  • + ))} +
+ ) : ( +

+ Loading books... +

+ )} + + ); } diff --git a/src/components/Pagination/Pagination.module.css b/src/components/Pagination/Pagination.module.css new file mode 100644 index 0000000..de5df68 --- /dev/null +++ b/src/components/Pagination/Pagination.module.css @@ -0,0 +1,39 @@ +.pagination { + display: flex; + align-items: center; + justify-content: center; + gap: 1rem; + padding: 0.75rem 0; +} + +.paginationButton { + display: flex; + align-items: center; + justify-content: center; + width: 6rem; + padding: 0.5rem 1.25rem; + color: var(--theme-color); + background-color: white; + border: 1px solid var(--border-color); + border-radius: 0.5rem; + cursor: pointer; + transition: 0.15s ease-in-out; +} + +.paginationButton:hover:not(:disabled) { + background-color: #f3f4f6; + border-color: #9ca3af; +} + +.paginationButton:disabled { + color: var(--muted); + background-color: var(--muted-background); + border-color: var(--border-color); + cursor: not-allowed; +} + +.paginationInfo { + font-weight: 500; + width: 5rem; + text-align: center; +} diff --git a/src/components/Pagination/Pagination.tsx b/src/components/Pagination/Pagination.tsx new file mode 100644 index 0000000..e1433c4 --- /dev/null +++ b/src/components/Pagination/Pagination.tsx @@ -0,0 +1,40 @@ +import styles from "./Pagination.module.css"; + +function Pagination({ + totalPages, + currentPage, + onPageChange, +}: { + totalPages: number; + currentPage: number; + onPageChange: (page: number) => void; +}) { + const handlePageChange = (newPage: number) => { + window.scrollTo({ top: 0, behavior: "smooth" }); + onPageChange(newPage); + }; + + return ( +
+ + + {currentPage} of {totalPages} + + +
+ ); +} + +export { Pagination }; diff --git a/src/index.css b/src/index.css index 94cb449..1bbb4cc 100644 --- a/src/index.css +++ b/src/index.css @@ -4,6 +4,8 @@ --footer-background-color: #2b2a2a; --border-color: #ebe9e6; --border-radius: 8px; + --muted: #6b7280; + --muted-background: rgba(255, 255, 255, 0.25); } * { From 0b59b8c6461e530f3b9e6988415d26c407022508 Mon Sep 17 00:00:00 2001 From: Martin Alexander Kaminski Date: Wed, 16 Sep 2026 16:55:47 +0200 Subject: [PATCH 3/6] feat: show correct number of pages --- src/api/openLibrary.ts | 2 +- src/api/useBooks.ts | 15 +++++++-------- src/components/BookGrid/BookGrid.tsx | 11 ++++++++--- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/api/openLibrary.ts b/src/api/openLibrary.ts index 3fdf099..72cd850 100644 --- a/src/api/openLibrary.ts +++ b/src/api/openLibrary.ts @@ -61,4 +61,4 @@ function toBook(doc: OpenLibraryDoc): Book { }; } -export { searchBooks, toBook }; +export { SEARCH_URL, SEARCH_LIMIT, searchBooks, toBook }; diff --git a/src/api/useBooks.ts b/src/api/useBooks.ts index 8e87c61..ec3fcf6 100644 --- a/src/api/useBooks.ts +++ b/src/api/useBooks.ts @@ -1,20 +1,19 @@ import { useQuery } from "@tanstack/react-query"; import { searchBooks, toBook } from "./openLibrary"; -import type { - Book, - BookSearchParams, - OpenLibrarySearchResponse, -} from "./types"; +import type { BookSearchParams, OpenLibrarySearchResponse } from "./types"; -function toBooks(data: OpenLibrarySearchResponse): Book[] { - return data.docs.map(toBook); +function parseBooks(data: OpenLibrarySearchResponse) { + return { + numFound: data.numFound, + books: data.docs.map(toBook), + }; } function useBooks(params: BookSearchParams) { return useQuery({ queryKey: ["books", params], queryFn: () => searchBooks(params), - select: toBooks, + select: parseBooks, }); } diff --git a/src/components/BookGrid/BookGrid.tsx b/src/components/BookGrid/BookGrid.tsx index 335ab24..7b90f38 100644 --- a/src/components/BookGrid/BookGrid.tsx +++ b/src/components/BookGrid/BookGrid.tsx @@ -4,6 +4,7 @@ import { useBooks } from "../../api/useBooks"; import type { Book } from "../../api/types"; import { Pagination } from "../Pagination/Pagination"; import { useState } from "react"; +import { SEARCH_LIMIT } from "../../api/openLibrary"; interface BookGridProps { onBookSelect: (book: Book) => void; @@ -25,7 +26,7 @@ function BookGrid({ onBookSelect }: BookGridProps) { ); } - if (data && data.length === 0) { + if (!data || data.books.length === 0) { return (

No books found for this search @@ -37,7 +38,7 @@ function BookGrid({ onBookSelect }: BookGridProps) { <> {!isPending ? (

    - {data.map((book) => ( + {data.books.map((book) => (
  • @@ -48,7 +49,11 @@ function BookGrid({ onBookSelect }: BookGridProps) { Loading books...

    )} - + ); } From 80fd2edbddf08652fa892c8e9bb8dae2ea80198f Mon Sep 17 00:00:00 2001 From: Martin Alexander Kaminski Date: Wed, 16 Sep 2026 17:38:18 +0200 Subject: [PATCH 4/6] refactor: move page state to parent component This will make the parent "remember" the page number, so we end up in the same place after looking at a book's details --- src/App.tsx | 7 ++++++- src/components/BookGrid/BookGrid.tsx | 9 ++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 385ecc3..d4a7658 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,6 +7,7 @@ import type { Book } from "./api/types.ts"; function App() { const [selectedBook, setSelectedBook] = useState(null); + const [page, setPage] = useState(1); return ( <> @@ -18,7 +19,11 @@ function App() { onGoBack={() => setSelectedBook(null)} /> ) : ( - + )}