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 (
+
+