diff --git a/.gitignore b/.gitignore index a547bf3..d8d200b 100644 --- a/.gitignore +++ b/.gitignore @@ -13,8 +13,6 @@ dist-ssr *.local # Editor directories and files -.vscode/* -!.vscode/extensions.json .idea .DS_Store *.suo @@ -22,3 +20,9 @@ dist-ssr *.njsproj *.sln *.sw? + +.env + +# React Router +/.react-router/ +/build/ diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 5cfa8e5..ff7b3ae 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,10 +1,10 @@ { - "recommendations": [ - "formulahendry.auto-rename-tag", - "streetsidesoftware.code-spell-checker", - "dbaeumer.vscode-eslint", - "esbenp.prettier-vscode", - "YoavBls.pretty-ts-errors", - "bradlc.vscode-tailwindcss" - ] + "recommendations": [ + "formulahendry.auto-rename-tag", + "streetsidesoftware.code-spell-checker", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "YoavBls.pretty-ts-errors", + "bradlc.vscode-tailwindcss" + ] } diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9fddbe2 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "js/ts.preferences.importModuleSpecifier": "non-relative" +} diff --git a/README.md b/README.md index 07b29d7..ac6da4b 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ pnpm test:watch ``` Test files live next to the code they test (`*.test.ts` / `*.test.tsx`). -Shared helpers and fixtures live in `src/test/`: +Shared helpers and fixtures live in `app/test/`: - `setup.ts` adds the [jest-dom](https://github.com/testing-library/jest-dom) matchers (e.g. `toBeInTheDocument`) and clears the DOM and `localStorage` @@ -71,7 +71,7 @@ We test where there is logic, state or several parts working together. Static markup (like `Header` and `Footer`) is not tested: such tests break on every intended change without catching real bugs. -- **Unit tests** for the API layer (`src/api/openLibrary.test.ts`): `toBook` +- **Unit tests** for the API layer (`app/api/openLibrary.test.ts`): `toBook` and `toDescription` map Open Library data to our types (with fallbacks for missing fields), and `searchBooks` and `fetchWork` build the right request and throw on HTTP errors. @@ -90,7 +90,7 @@ intended change without catching real bugs. No test sends requests to openlibrary.org: - `openLibrary.test.ts` replaces the global `fetch` with a mock (`vi.spyOn`) - that answers with the saved responses in `src/test/fixtures/`. + that answers with the saved responses in `app/test/fixtures/`. - The `App` and `BookDetails` tests replace `searchBooks` and `fetchWork` with mocks (`vi.mock`), so the components receive the fixture data (`searchResponse.json`, `workResponse.json`) right away. @@ -164,5 +164,5 @@ short, it was used for: the `description` field of a work. Where AI contributed directly to a piece of code, there is a comment saying -so, for example in [src/api/types.ts](src/api/types.ts) and -[src/main.tsx](src/main.tsx). +so, for example in [app/api/types.ts](app/api/types.ts) and +[app/root.tsx](app/root.tsx). diff --git a/src/api/languageCodes.ts b/app/api/languageCodes.ts similarity index 100% rename from src/api/languageCodes.ts rename to app/api/languageCodes.ts diff --git a/src/api/openLibrary.test.ts b/app/api/openLibrary.test.ts similarity index 94% rename from src/api/openLibrary.test.ts rename to app/api/openLibrary.test.ts index c81ee2a..10081a3 100644 --- a/src/api/openLibrary.test.ts +++ b/app/api/openLibrary.test.ts @@ -1,8 +1,13 @@ import { describe, expect, it, vi } from "vitest"; -import { searchBooks, toBook, fetchWork, toDescription } from "./openLibrary"; -import type { OpenLibraryDoc, OpenLibraryWork } from "./types"; -import searchResponse from "../test/fixtures/searchResponse.json"; -import workResponse from "../test/fixtures/workResponse.json"; +import { + searchBooks, + toBook, + fetchWork, + toDescription, +} from "~/api/openLibrary"; +import type { OpenLibraryDoc, OpenLibraryWork } from "~/api/types"; +import searchResponse from "~/test/fixtures/searchResponse.json"; +import workResponse from "~/test/fixtures/workResponse.json"; describe("toBook", () => { it("maps an Open Library doc to our Book type", () => { diff --git a/src/api/openLibrary.ts b/app/api/openLibrary.ts similarity index 99% rename from src/api/openLibrary.ts rename to app/api/openLibrary.ts index cbe07ae..09165be 100644 --- a/src/api/openLibrary.ts +++ b/app/api/openLibrary.ts @@ -5,7 +5,7 @@ import type { Book, BookFilters, OpenLibraryWork, -} from "./types"; +} from "~/api/types"; const BASE_URL = "https://openlibrary.org"; const SEARCH_LIMIT = 12; diff --git a/src/api/types.ts b/app/api/types.ts similarity index 100% rename from src/api/types.ts rename to app/api/types.ts diff --git a/src/api/useBookDescription.ts b/app/api/useBookDescription.ts similarity index 79% rename from src/api/useBookDescription.ts rename to app/api/useBookDescription.ts index 6a04c3d..fdd7eba 100644 --- a/src/api/useBookDescription.ts +++ b/app/api/useBookDescription.ts @@ -1,5 +1,5 @@ import { useQuery } from "@tanstack/react-query"; -import { fetchWork, toDescription } from "./openLibrary"; +import { fetchWork, toDescription } from "~/api/openLibrary"; function useBookDescription(key: string) { return useQuery({ diff --git a/src/api/useBooks.ts b/app/api/useBooks.ts similarity index 85% rename from src/api/useBooks.ts rename to app/api/useBooks.ts index ec3fcf6..ddd6c4b 100644 --- a/src/api/useBooks.ts +++ b/app/api/useBooks.ts @@ -1,6 +1,6 @@ import { useQuery } from "@tanstack/react-query"; -import { searchBooks, toBook } from "./openLibrary"; -import type { BookSearchParams, OpenLibrarySearchResponse } from "./types"; +import { searchBooks, toBook } from "~/api/openLibrary"; +import type { BookSearchParams, OpenLibrarySearchResponse } from "~/api/types"; function parseBooks(data: OpenLibrarySearchResponse) { return { diff --git a/src/components/BookCard/BookCard.module.css b/app/components/BookCard/BookCard.module.css similarity index 100% rename from src/components/BookCard/BookCard.module.css rename to app/components/BookCard/BookCard.module.css diff --git a/src/components/BookCard/BookCard.test.tsx b/app/components/BookCard/BookCard.test.tsx similarity index 91% rename from src/components/BookCard/BookCard.test.tsx rename to app/components/BookCard/BookCard.test.tsx index 9d4fdb1..8cc14d6 100644 --- a/src/components/BookCard/BookCard.test.tsx +++ b/app/components/BookCard/BookCard.test.tsx @@ -1,8 +1,8 @@ import { describe, expect, it, vi } from "vitest"; import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; -import { BookCard } from "./BookCard"; -import { bookWithCover as book } from "../../test/fixtures/books"; +import { BookCard } from "~/components/BookCard/BookCard"; +import { bookWithCover as book } from "~/test/fixtures/books"; describe("BookCard", () => { it("shows title, authors and year from the book prop", () => { diff --git a/src/components/BookCard/BookCard.tsx b/app/components/BookCard/BookCard.tsx similarity index 81% rename from src/components/BookCard/BookCard.tsx rename to app/components/BookCard/BookCard.tsx index c092513..d07d5e6 100644 --- a/src/components/BookCard/BookCard.tsx +++ b/app/components/BookCard/BookCard.tsx @@ -1,7 +1,7 @@ import styles from "./BookCard.module.css"; -import { BookCover } from "../BookCover/BookCover"; -import { FavoriteButton } from "../FavoriteButton/FavoriteButton"; -import type { Book } from "../../api/types"; +import { BookCover } from "~/components/BookCover/BookCover"; +import { FavoriteButton } from "~/components/FavoriteButton/FavoriteButton"; +import type { Book } from "~/api/types"; interface BookCardProps { book: Book; diff --git a/src/components/BookCard/__snapshots__/BookCard.test.tsx.snap b/app/components/BookCard/__snapshots__/BookCard.test.tsx.snap similarity index 100% rename from src/components/BookCard/__snapshots__/BookCard.test.tsx.snap rename to app/components/BookCard/__snapshots__/BookCard.test.tsx.snap diff --git a/src/components/BookCover/BookCover.module.css b/app/components/BookCover/BookCover.module.css similarity index 100% rename from src/components/BookCover/BookCover.module.css rename to app/components/BookCover/BookCover.module.css diff --git a/src/components/BookCover/BookCover.tsx b/app/components/BookCover/BookCover.tsx similarity index 89% rename from src/components/BookCover/BookCover.tsx rename to app/components/BookCover/BookCover.tsx index 6468650..f71bca2 100644 --- a/src/components/BookCover/BookCover.tsx +++ b/app/components/BookCover/BookCover.tsx @@ -1,5 +1,5 @@ import styles from "./BookCover.module.css"; -import type { Book } from "../../api/types"; +import type { Book } from "~/api/types"; interface BookCoverProps { book: Book; diff --git a/src/components/BookDetails/BookDetails.module.css b/app/components/BookDetails/BookDetails.module.css similarity index 100% rename from src/components/BookDetails/BookDetails.module.css rename to app/components/BookDetails/BookDetails.module.css diff --git a/src/components/BookDetails/BookDetails.test.tsx b/app/components/BookDetails/BookDetails.test.tsx similarity index 78% rename from src/components/BookDetails/BookDetails.test.tsx rename to app/components/BookDetails/BookDetails.test.tsx index 3a9947a..3d53cfe 100644 --- a/src/components/BookDetails/BookDetails.test.tsx +++ b/app/components/BookDetails/BookDetails.test.tsx @@ -1,15 +1,15 @@ import { describe, expect, it, vi } from "vitest"; import { screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; -import { BookDetails } from "./BookDetails"; -import { fetchWork } from "../../api/openLibrary"; -import { renderWithQueryClient } from "../../test/renderWithQueryClient"; -import { bookWithCover as book } from "../../test/fixtures/books"; -import workResponse from "../../test/fixtures/workResponse.json"; +import { BookDetails } from "~/components/BookDetails/BookDetails"; +import { fetchWork } from "~/api/openLibrary"; +import { renderWithQueryClient } from "~/test/renderWithQueryClient"; +import { bookWithCover as book } from "~/test/fixtures/books"; +import workResponse from "~/test/fixtures/workResponse.json"; // BookDetails loads the description through useBookDescription -> fetchWork. // Mock fetchWork so the component gets the saved work from the fixture. -vi.mock(import("../../api/openLibrary"), async (importOriginal) => ({ +vi.mock(import("~/api/openLibrary"), async (importOriginal) => ({ ...(await importOriginal()), fetchWork: vi.fn(), })); diff --git a/src/components/BookDetails/BookDetails.tsx b/app/components/BookDetails/BookDetails.tsx similarity index 90% rename from src/components/BookDetails/BookDetails.tsx rename to app/components/BookDetails/BookDetails.tsx index 38c9f5d..a2ecfb6 100644 --- a/src/components/BookDetails/BookDetails.tsx +++ b/app/components/BookDetails/BookDetails.tsx @@ -1,8 +1,8 @@ import styles from "./BookDetails.module.css"; -import { BookCover } from "../BookCover/BookCover"; -import { FavoriteButton } from "../FavoriteButton/FavoriteButton"; -import type { Book } from "../../api/types"; -import { useBookDescription } from "../../api/useBookDescription"; +import { BookCover } from "~/components/BookCover/BookCover"; +import { FavoriteButton } from "~/components/FavoriteButton/FavoriteButton"; +import type { Book } from "~/api/types"; +import { useBookDescription } from "~/api/useBookDescription"; interface BookDetailsProps { book: Book; diff --git a/src/components/BookGrid/BookGrid.module.css b/app/components/BookGrid/BookGrid.module.css similarity index 100% rename from src/components/BookGrid/BookGrid.module.css rename to app/components/BookGrid/BookGrid.module.css diff --git a/src/components/BookGrid/BookGrid.test.tsx b/app/components/BookGrid/BookGrid.test.tsx similarity index 90% rename from src/components/BookGrid/BookGrid.test.tsx rename to app/components/BookGrid/BookGrid.test.tsx index b618205..4d9bbcf 100644 --- a/src/components/BookGrid/BookGrid.test.tsx +++ b/app/components/BookGrid/BookGrid.test.tsx @@ -2,11 +2,11 @@ import type { ComponentProps } from "react"; import { describe, expect, it, vi } from "vitest"; import { screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; -import { BookGrid } from "./BookGrid"; -import { toBook } from "../../api/openLibrary"; -import { renderWithQueryClient } from "../../test/renderWithQueryClient"; -import searchResponse from "../../test/fixtures/searchResponse.json"; -import { useBooks } from "../../api/useBooks"; +import { BookGrid } from "~/components/BookGrid/BookGrid"; +import { toBook } from "~/api/openLibrary"; +import { renderWithQueryClient } from "~/test/renderWithQueryClient"; +import searchResponse from "~/test/fixtures/searchResponse.json"; +import { useBooks } from "~/api/useBooks"; // Helper to create mock booksQuery results function createMockBooksQuery(overrides = {}) { diff --git a/src/components/BookGrid/BookGrid.tsx b/app/components/BookGrid/BookGrid.tsx similarity index 84% rename from src/components/BookGrid/BookGrid.tsx rename to app/components/BookGrid/BookGrid.tsx index 5d760a4..0242fc0 100644 --- a/src/components/BookGrid/BookGrid.tsx +++ b/app/components/BookGrid/BookGrid.tsx @@ -1,10 +1,10 @@ -import { BookCard } from "../BookCard/BookCard"; -import { SearchBar } from "../SearchBar/SearchBar"; +import { BookCard } from "~/components/BookCard/BookCard"; +import { SearchBar } from "~/components/SearchBar/SearchBar"; import styles from "./BookGrid.module.css"; -import { useBooks } from "../../api/useBooks"; -import type { Book, BookSearchParams } from "../../api/types"; -import { Pagination } from "../Pagination/Pagination"; -import { SEARCH_LIMIT } from "../../api/openLibrary"; +import { useBooks } from "~/api/useBooks"; +import type { Book, BookSearchParams } from "~/api/types"; +import { Pagination } from "~/components/Pagination/Pagination"; +import { SEARCH_LIMIT } from "~/api/openLibrary"; interface BookGridProps { // The result of useBooks, owned by App so it outlives this component. diff --git a/src/components/BookGrid/useDebounce.ts b/app/components/BookGrid/useDebounce.ts similarity index 100% rename from src/components/BookGrid/useDebounce.ts rename to app/components/BookGrid/useDebounce.ts diff --git a/src/components/FavoriteButton/FavoriteButton.module.css b/app/components/FavoriteButton/FavoriteButton.module.css similarity index 100% rename from src/components/FavoriteButton/FavoriteButton.module.css rename to app/components/FavoriteButton/FavoriteButton.module.css diff --git a/src/components/FavoriteButton/FavoriteButton.test.tsx b/app/components/FavoriteButton/FavoriteButton.test.tsx similarity index 91% rename from src/components/FavoriteButton/FavoriteButton.test.tsx rename to app/components/FavoriteButton/FavoriteButton.test.tsx index dc84fed..1021add 100644 --- a/src/components/FavoriteButton/FavoriteButton.test.tsx +++ b/app/components/FavoriteButton/FavoriteButton.test.tsx @@ -1,8 +1,8 @@ import { describe, expect, it } from "vitest"; import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; -import { FavoriteButton } from "./FavoriteButton"; -import { bookWithCover as book } from "../../test/fixtures/books"; +import { FavoriteButton } from "~/components/FavoriteButton/FavoriteButton"; +import { bookWithCover as book } from "~/test/fixtures/books"; describe("FavoriteButton", () => { it("is not pressed when the book is not a favorite", () => { diff --git a/src/components/FavoriteButton/FavoriteButton.tsx b/app/components/FavoriteButton/FavoriteButton.tsx similarity index 93% rename from src/components/FavoriteButton/FavoriteButton.tsx rename to app/components/FavoriteButton/FavoriteButton.tsx index 8f95239..f2acc15 100644 --- a/src/components/FavoriteButton/FavoriteButton.tsx +++ b/app/components/FavoriteButton/FavoriteButton.tsx @@ -1,7 +1,7 @@ import { useState } from "react"; import styles from "./FavoriteButton.module.css"; -import { CheckIcon } from "../icons/CheckIcon"; -import type { Book } from "../../api/types"; +import { CheckIcon } from "~/components/icons/CheckIcon"; +import type { Book } from "~/api/types"; interface FavoriteButtonProps { book: Book; diff --git a/src/components/FilterBooksSection/FilterBooksSection.module.css b/app/components/FilterBooksSection/FilterBooksSection.module.css similarity index 100% rename from src/components/FilterBooksSection/FilterBooksSection.module.css rename to app/components/FilterBooksSection/FilterBooksSection.module.css diff --git a/src/components/FilterBooksSection/FilterBooksSection.tsx b/app/components/FilterBooksSection/FilterBooksSection.tsx similarity index 94% rename from src/components/FilterBooksSection/FilterBooksSection.tsx rename to app/components/FilterBooksSection/FilterBooksSection.tsx index 38e8cc5..75322b6 100644 --- a/src/components/FilterBooksSection/FilterBooksSection.tsx +++ b/app/components/FilterBooksSection/FilterBooksSection.tsx @@ -1,8 +1,8 @@ import { useState } from "react"; import styles from "./FilterBooksSection.module.css"; -import type { BookFilters } from "../../api/types"; -import { languageCodes } from "../../api/languageCodes"; -import { CircleQuestionMark } from "../icons/CircleQuestionMark"; +import type { BookFilters } from "~/api/types"; +import { languageCodes } from "~/api/languageCodes"; +import { CircleQuestionMark } from "~/components/icons/CircleQuestionMark"; const FILTER_INPUTS: { name: keyof BookFilters; diff --git a/src/components/Footer/Footer.module.css b/app/components/Footer/Footer.module.css similarity index 100% rename from src/components/Footer/Footer.module.css rename to app/components/Footer/Footer.module.css diff --git a/src/components/Footer/Footer.tsx b/app/components/Footer/Footer.tsx similarity index 100% rename from src/components/Footer/Footer.tsx rename to app/components/Footer/Footer.tsx diff --git a/src/components/Header/Header.module.css b/app/components/Header/Header.module.css similarity index 100% rename from src/components/Header/Header.module.css rename to app/components/Header/Header.module.css diff --git a/src/components/Header/Header.tsx b/app/components/Header/Header.tsx similarity index 100% rename from src/components/Header/Header.tsx rename to app/components/Header/Header.tsx diff --git a/app/components/MainLoading/MainLoading.module.css b/app/components/MainLoading/MainLoading.module.css new file mode 100644 index 0000000..b8804fc --- /dev/null +++ b/app/components/MainLoading/MainLoading.module.css @@ -0,0 +1,7 @@ +.main-loading { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 1rem; +} diff --git a/app/components/MainLoading/MainLoading.tsx b/app/components/MainLoading/MainLoading.tsx new file mode 100644 index 0000000..6e319e2 --- /dev/null +++ b/app/components/MainLoading/MainLoading.tsx @@ -0,0 +1,11 @@ +import styles from "./MainLoading.module.css"; + +function MainLoading() { + return ( +
+

Welcome to Readr!

+

Loading...

+
+ ); +} +export { MainLoading }; diff --git a/src/components/Pagination/Pagination.module.css b/app/components/Pagination/Pagination.module.css similarity index 100% rename from src/components/Pagination/Pagination.module.css rename to app/components/Pagination/Pagination.module.css diff --git a/src/components/Pagination/Pagination.test.tsx b/app/components/Pagination/Pagination.test.tsx similarity index 96% rename from src/components/Pagination/Pagination.test.tsx rename to app/components/Pagination/Pagination.test.tsx index 1a79b84..4d6640f 100644 --- a/src/components/Pagination/Pagination.test.tsx +++ b/app/components/Pagination/Pagination.test.tsx @@ -1,7 +1,7 @@ import { describe, expect, it, vi } from "vitest"; import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; -import { Pagination } from "./Pagination"; +import { Pagination } from "~/components/Pagination/Pagination"; describe("Pagination", () => { it("disables Previous on the first page and Next on the last page", () => { diff --git a/src/components/Pagination/Pagination.tsx b/app/components/Pagination/Pagination.tsx similarity index 100% rename from src/components/Pagination/Pagination.tsx rename to app/components/Pagination/Pagination.tsx diff --git a/src/components/SearchBar/SearchBar.module.css b/app/components/SearchBar/SearchBar.module.css similarity index 100% rename from src/components/SearchBar/SearchBar.module.css rename to app/components/SearchBar/SearchBar.module.css diff --git a/src/components/SearchBar/SearchBar.test.tsx b/app/components/SearchBar/SearchBar.test.tsx similarity index 93% rename from src/components/SearchBar/SearchBar.test.tsx rename to app/components/SearchBar/SearchBar.test.tsx index 44c7ebe..e89a63d 100644 --- a/src/components/SearchBar/SearchBar.test.tsx +++ b/app/components/SearchBar/SearchBar.test.tsx @@ -1,7 +1,7 @@ import { describe, expect, it, vi } from "vitest"; import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; -import { SearchBar } from "./SearchBar"; +import { SearchBar } from "~/components/SearchBar/SearchBar"; describe("SearchBar", () => { it("shows the query it is given", () => { diff --git a/src/components/SearchBar/SearchBar.tsx b/app/components/SearchBar/SearchBar.tsx similarity index 87% rename from src/components/SearchBar/SearchBar.tsx rename to app/components/SearchBar/SearchBar.tsx index 308d887..24d1da0 100644 --- a/src/components/SearchBar/SearchBar.tsx +++ b/app/components/SearchBar/SearchBar.tsx @@ -1,8 +1,8 @@ import { useState } from "react"; import styles from "./SearchBar.module.css"; -import { FilterIcon } from "../icons/FilterIcon"; -import { FilterBooksSection } from "../FilterBooksSection/FilterBooksSection"; -import type { BookFilters } from "../../api/types"; +import { FilterIcon } from "~/components/icons/FilterIcon"; +import { FilterBooksSection } from "~/components/FilterBooksSection/FilterBooksSection"; +import type { BookFilters } from "~/api/types"; interface SearchBarProps { query: string; diff --git a/src/components/icons/CheckIcon.tsx b/app/components/icons/CheckIcon.tsx similarity index 100% rename from src/components/icons/CheckIcon.tsx rename to app/components/icons/CheckIcon.tsx diff --git a/src/components/icons/CircleQuestionMark.tsx b/app/components/icons/CircleQuestionMark.tsx similarity index 100% rename from src/components/icons/CircleQuestionMark.tsx rename to app/components/icons/CircleQuestionMark.tsx diff --git a/src/components/icons/FilterIcon.tsx b/app/components/icons/FilterIcon.tsx similarity index 100% rename from src/components/icons/FilterIcon.tsx rename to app/components/icons/FilterIcon.tsx diff --git a/src/index.css b/app/index.css similarity index 92% rename from src/index.css rename to app/index.css index 1bbb4cc..9568450 100644 --- a/src/index.css +++ b/app/index.css @@ -19,14 +19,18 @@ ol { list-style: none; } -#root { +body { + background-color: var(--background-color); display: flex; flex-direction: column; min-height: 100svh; - background-color: var(--background-color); font-family: "Open Sans", sans-serif; } main { flex: 1; } + +footer { + margin-top: auto; +} diff --git a/app/root.tsx b/app/root.tsx new file mode 100644 index 0000000..105fe1d --- /dev/null +++ b/app/root.tsx @@ -0,0 +1,123 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import "./index.css"; +import { Header } from "~/components/Header/Header"; +import { Footer } from "~/components/Footer/Footer"; +import { + Meta, + Links, + Outlet, + ScrollRestoration, + Scripts, + isRouteErrorResponse, +} from "react-router"; +import { MainLoading } from "~/components/MainLoading/MainLoading"; +import type { Route } from "./+types/root"; + +// Created outside the component tree so the cache survives re-renders. +// With the help of claude, these were the default options that suited our needs the best. +// staleTime is how long fetched data counts as fresh. Stale data is not refetched right away, +// only the next time it is needed, e.g. when a component using it mounts. +// refetchOnWindowFocus is whether to refetch the data when the window is focused. +// retry is the number of times to retry the request if it fails. OpenLibrary has a rate limit of 1 request per second. +// Since we're building a digital library, we don't need to refresh the data too often, and I think these settings will work for us. +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 10 * 60 * 1000, + refetchOnWindowFocus: false, + retry: 1, + }, + }, +}); + +export function HydrateFallback() { + return ; +} + +export function Layout({ children }: { children: React.ReactNode }) { + return ( + + + + + Readr + + + + + + + + + + + + +
+ {children} +