Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions client/src/data/searchBooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { Book } from '../types/book';

export function searchBooks(books: Book[], query: string): Book[] {
const term = query.trim().toLowerCase();
if (term === '') return books; //if the seach is empty, show everything

return books.filter(
(book) =>
book.title.toLowerCase().includes(term) ||
book.authors.some((author) => author.toLowerCase().includes(term)),
);
}
48 changes: 42 additions & 6 deletions client/src/pages/BrowsePage/BrowsePage.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
import { useState } from 'react';
import { useSearchParams } from 'react-router';
import type { Book } from '../../types/book';
import { BookGrid } from '../../components/BookGrid/BookGrid';
import { EmptyState } from '../../components/EmptyState/EmptyState';
import { LoadingState } from '../../components/LoadingState/LoadingState';
import { mockBooks, mockShelfEntries, type ShelfEntry } from '../../data/mockBooks';
import { searchBooks } from '../../data/searchBooks';
import styles from './BrowsePage.module.css';
import { LoadingState } from '../../components/LoadingState/LoadingState';

export function BrowsePage() {
const [searchParams, setSearchParams] = useSearchParams();
const query = searchParams.get('q') ?? '';

function handleSearch(next: string) {
setSearchParams((prev) => {
const params = new URLSearchParams(prev);
if (next) params.set('q', next);
else params.delete('q'); //empty search = clean URL
return params;
});
}
//shelf

const [shelf, setShelf] = useState<Map<Book['id'], ShelfEntry>>(
() => new Map(mockShelfEntries.map((entry) => [entry.bookId, entry])),
);
Expand All @@ -22,21 +38,41 @@ export function BrowsePage() {
});
}

const books = mockBooks;

//temporary: search the mock data, replaced by the apollo query
const books = searchBooks(mockBooks, query);
// ===== TEMPORARY: fake loading state, replace with the query's `loading` =====
const isLoading = false;
const noResults = !isLoading && query !== '' && books.length === 0;

return (
<section className={styles.page} aria-labelledby="browse-heading">
<header className={styles.header}>
<h1 id="browse-heading" className={styles.heading}>
browse
Browse
</h1>
<p className={styles.count}> {books.length} books</p>
<p className={styles.count} aria-live="polite">
{resultLabel(books.length, query)}
</p>
</header>

{isLoading && <LoadingState announceOnly />}
<BookGrid books={books} shelf={shelf} onToggleSaved={toggleSaved} isLoading={isLoading} />

{noResults ? (
<EmptyState
title="No books found"
message={`Nothing matches "${query}". Try another title or author.`}
action={{ label: 'Clear search', onClick: () => handleSearch('') }}
/>
) : (
<BookGrid books={books} shelf={shelf} onToggleSaved={toggleSaved} isLoading={isLoading} />
)}
</section>
);
}

//resultLabel: the count next to the heading
function resultLabel(count: number, query: string) {
if (!query) return count === 1 ? '1book' : `${count} books`;
const results = count === 1 ? 'result' : 'results';
return `Searchword: "${query}" gave ${count} ${results}`;
}