Archived
- Backend: Book model with reading progress, DRF ViewSet with full CRUD, search, sort, filter, pagination, mark-as-finished, stats endpoint - Frontend: Library grid, BookCard, BookDetail, BookForm components with React 19 + TypeScript + Vite - Tests: 29 passing tests covering models, API, serializers, permissions - Spec: backend api-spec.md and frontend component-spec.md in docs/ Closes crisleo-hermes/cloud-reader#3
82 lines
3.2 KiB
Markdown
82 lines
3.2 KiB
Markdown
# Frontend Component Specification — Cloud Reader
|
|
|
|
## Overview
|
|
|
|
React 19 + TypeScript SPA using Vite for development and production builds. State managed via React hooks and Context API. Routing via `react-router-dom` v7 with `lazy`/`Suspense` for code splitting.
|
|
|
|
## Components
|
|
|
|
### App (root)
|
|
|
|
- **Path:** `/`
|
|
- **Layout:** Header with logo + `<Routes>` wrapper
|
|
- **Routing:** `/` → `Library`, `*` → redirect to `/`
|
|
- **Code splitting:** `Library` loaded via `React.lazy` + `<Suspense>`
|
|
|
|
### Library
|
|
|
|
- **State:** `books[]`, `stats`, `search`, `sortBy`, `statusFilter`, `currentPage`, `view`
|
|
- **Views:** `library` (grid), `detail` (selected book), `add` (modal form)
|
|
- **Sub-components:** `BookCard`, `BookDetail`, `BookForm`
|
|
- **Data flow:** Calls `fetchBooks()` on mount and when filters/page change
|
|
|
|
### BookCard
|
|
|
|
- **Props:** `{ book: Book, onClick: (book: Book) => void }`
|
|
- **Display:** Cover image (or first-letter placeholder), title, author, genre badge, reading progress bar with status color
|
|
- **Interaction:** Click/keyboard-accessible (Enter/Space)
|
|
|
|
### BookDetail
|
|
|
|
- **Props:** `{ bookId: number, onBack: () => void, onUpdated: () => void }`
|
|
- **Sections:** Cover, metadata (title, author, genre, ISBN), progress bar with page count, action buttons
|
|
- **Actions:** Mark as Finished, Edit Details (switches to BookForm), Delete (with confirmation)
|
|
- **States:** Loading, error, editing mode
|
|
|
|
### BookForm
|
|
|
|
- **Props:** `{ initialData?: Book, onSubmit: (data: BookFormData) => Promise<void>, onCancel: () => void }`
|
|
- **Fields:** Title*, Author*, Genre, Description, Cover URL, ISBN, Total Pages, Current Page, Status
|
|
- **Client validation:** Title/author required, page ≤ total pages
|
|
- **Loading state:** Submit button shows "Saving..." when `isLoading`
|
|
|
|
## Types
|
|
|
|
```typescript
|
|
interface Book {
|
|
id: number; title: string; author: string; genre: string;
|
|
description: string; cover_image_url: string; isbn: string;
|
|
total_pages: number; current_page: number;
|
|
reading_status: ReadingStatus; reading_progress: number;
|
|
owner: string; created_at: string; updated_at: string;
|
|
}
|
|
|
|
type ReadingStatus = 'not_started' | 'reading' | 'finished' | 'dnf';
|
|
|
|
interface BookFormData {
|
|
title: string; author: string; genre: string;
|
|
description: string; cover_image_url: string; isbn: string;
|
|
total_pages: number; current_page: number; reading_status: ReadingStatus;
|
|
}
|
|
```
|
|
|
|
## API Client (`api/books.ts`)
|
|
|
|
| Function | HTTP Call |
|
|
|----------------------|------------------------------------|
|
|
| `fetchBooks(params)` | `GET /api/books/` |
|
|
| `fetchBook(id)` | `GET /api/books/{id}/` |
|
|
| `createBook(data)` | `POST /api/books/` |
|
|
| `updateBook(id, data)` | `PATCH /api/books/{id}/` |
|
|
| `deleteBook(id)` | `DELETE /api/books/{id}/` |
|
|
| `markAsFinished(id)` | `POST /api/books/{id}/mark_finished/` |
|
|
| `fetchBookStats()` | `GET /api/books/stats/` |
|
|
|
|
## Styling
|
|
|
|
Dark theme with CSS custom properties. All styles in `App.css`. Responsive grid layout with breakpoint at 768px.
|
|
|
|
## Build & Dev
|
|
|
|
- `yarn workspace frontend dev` — Vite dev server on port 5173 with API proxy to 127.0.0.1:8000
|
|
- `yarn workspace frontend build` — TypeScript check + Vite production build |