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)}
/>
) : (
-
+
)}
diff --git a/src/components/BookGrid/BookGrid.tsx b/src/components/BookGrid/BookGrid.tsx
index 7b90f38..3b9ab73 100644
--- a/src/components/BookGrid/BookGrid.tsx
+++ b/src/components/BookGrid/BookGrid.tsx
@@ -3,16 +3,15 @@ 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";
import { SEARCH_LIMIT } from "../../api/openLibrary";
interface BookGridProps {
+ page: number;
onBookSelect: (book: Book) => void;
+ onPageChange: (page: number) => void;
}
-function BookGrid({ onBookSelect }: BookGridProps) {
- const [page, setPage] = useState(1);
-
+function BookGrid({ page, onBookSelect, onPageChange }: BookGridProps) {
const { data, isPending, isError, error } = useBooks({
query: "subject:fantasy",
page,
@@ -52,7 +51,7 @@ function BookGrid({ onBookSelect }: BookGridProps) {
>
);
From 8deab27e2779e8151874e11b4744aa62fb603cd1 Mon Sep 17 00:00:00 2001
From: Martin Alexander Kaminski
Date: Wed, 16 Sep 2026 22:01:51 +0200
Subject: [PATCH 5/6] fix: show "Loading books..." when changing pages
---
src/components/BookGrid/BookGrid.tsx | 28 +++++++++++++++-------------
1 file changed, 15 insertions(+), 13 deletions(-)
diff --git a/src/components/BookGrid/BookGrid.tsx b/src/components/BookGrid/BookGrid.tsx
index 3b9ab73..b13a909 100644
--- a/src/components/BookGrid/BookGrid.tsx
+++ b/src/components/BookGrid/BookGrid.tsx
@@ -17,6 +17,14 @@ function BookGrid({ page, onBookSelect, onPageChange }: BookGridProps) {
page,
});
+ if (isPending) {
+ return (
+
+ Loading books...
+
+ );
+ }
+
if (isError) {
return (
@@ -35,19 +43,13 @@ function BookGrid({ page, onBookSelect, onPageChange }: BookGridProps) {
return (
<>
- {!isPending ? (
-
- {data.books.map((book) => (
- -
-
-
- ))}
-
- ) : (
-
- Loading books...
-
- )}
+
+ {data.books.map((book) => (
+ -
+
+
+ ))}
+
Date: Wed, 16 Sep 2026 22:03:20 +0200
Subject: [PATCH 6/6] style: use kebab-case for CSS classnames for Pagination
component
---
src/components/Pagination/Pagination.module.css | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/components/Pagination/Pagination.module.css b/src/components/Pagination/Pagination.module.css
index de5df68..0211cc7 100644
--- a/src/components/Pagination/Pagination.module.css
+++ b/src/components/Pagination/Pagination.module.css
@@ -6,7 +6,7 @@
padding: 0.75rem 0;
}
-.paginationButton {
+.pagination-button {
display: flex;
align-items: center;
justify-content: center;
@@ -20,19 +20,19 @@
transition: 0.15s ease-in-out;
}
-.paginationButton:hover:not(:disabled) {
+.pagination-button:hover:not(:disabled) {
background-color: #f3f4f6;
border-color: #9ca3af;
}
-.paginationButton:disabled {
+.pagination-button:disabled {
color: var(--muted);
background-color: var(--muted-background);
border-color: var(--border-color);
cursor: not-allowed;
}
-.paginationInfo {
+.pagination-info {
font-weight: 500;
width: 5rem;
text-align: center;