diff --git a/client/src/App.tsx b/client/src/App.tsx index 9e704ea..25cc53c 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,10 +1,8 @@ import { BrowserRouter, Routes, Route } from "react-router"; import AppShell from "./components/AppShell/AppShell"; +import { BrowsePage } from "./pages/BrowsePage"; /* ---------- Temporary placeholder pages (delete when the real ones exist) ---------- */ -function BrowsePlaceholder() { -return

Browse page

; -} function ShelfPlaceholder() { return

My shelf page

; } @@ -18,7 +16,7 @@ export default function App() { }> - } /> + } /> } /> } /> diff --git a/client/src/data/mockBooks.ts b/client/src/data/mockBooks.ts new file mode 100644 index 0000000..d1d408d --- /dev/null +++ b/client/src/data/mockBooks.ts @@ -0,0 +1,11 @@ +import type {Book} from '../types/book'; +import booksJson from '../../../server/test-data/books.json'; + +export type ShelfEntry = {bookId: Book['id']; rating?: number}; + +export const mockBooks: Book[] = booksJson.map(({googleBooksId, ...rest}) => ({ + id: googleBooksId, + ...rest, +})); + +export const mockShelfEntries: ShelfEntry[] = []; \ No newline at end of file diff --git a/client/src/pages/BrowsePage.module.css b/client/src/pages/BrowsePage.module.css new file mode 100644 index 0000000..417a4ff --- /dev/null +++ b/client/src/pages/BrowsePage.module.css @@ -0,0 +1,46 @@ +/*mobile first */ + /* TODO: replace the raw values with the shared design tokens in src/styles/. */ + +.page { + max-width: 1200px; + margin: 0 auto; + padding: 1.5rem 1rem; +} + +.header { + display: flex; + flex-wrap: wrap; + align-items: baseline; + justify-content: space-between; + gap: 0.25rem 1rem; + margin-bottom: 1.25rem; +} + +.heading { + margin: 0; +} + +.count { + margin: 0; + opacity: 0.75; +} + +.grid { + list-style: none; + margin: 0; + padding: 0; + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 1.5rem 1rem; +} + +@media (min-width: 40rem) { /*tablets and bigger devices*/ + .page { + padding: 2rem 1.5rem; + } + + .grid { + grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); + gap: 2rem 1.5rem; + } +} \ No newline at end of file diff --git a/client/src/pages/BrowsePage.tsx b/client/src/pages/BrowsePage.tsx new file mode 100644 index 0000000..7092a7e --- /dev/null +++ b/client/src/pages/BrowsePage.tsx @@ -0,0 +1,49 @@ +import {useState} from 'react'; +import type {Book} from '../types/book'; +import {BookCard} from '../components/BookCard/BookCard'; +import {mockBooks, mockShelfEntries, type ShelfEntry} from '../data/mockBooks'; +import styles from './BrowsePage.module.css'; + +export function BrowsePage(){ + const [shelf, setShelf] = useState>( () => new Map(mockShelfEntries.map((entry) => [entry.bookId, entry])) + ); + + function toggleSaved(bookId: Book['id']) { + setShelf((prev) => { + const next = new Map(prev); + if (next.has(bookId)) { + next.delete(bookId); + } else { + next.set(bookId, {bookId}); + } + return next; + }); + } + + const books = mockBooks; + + return ( +
+
+

+ browse +

+

{books.length} books

+
+
    + {books.map((book) => { + const entry = shelf.get(book.id); + return ( + toggleSaved(book.id)} + /> + ); + })} +
+
+ ); +} \ No newline at end of file