mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-02 20:30:10 +00:00
e39e44ee44
Extend AudiobookDetailsModal props with onStatusChange, onIgnoreChange, hideRequestActions, hasReportedIssue, and aiReason. Stop forcing hideRequestActions when opening the modal from RequestCard so the modal can control whether request actions are shown. Add tests: verify admin sticky footer/status pill in AudiobookDetailsModal for pending requests, and add a RequestCard test that mocks AudiobookDetailsModal to assert the modal receives isOpen, asin and that hideRequestActions is not forced. Reset the new mock between tests.
8.5 KiB
8.5 KiB
Frontend Components
Status: ⏳ In Development
React components for ReadMeABook UI built with Next.js 14+, TypeScript, and Tailwind CSS.
Structure
src/app/
├── (auth)/login/
├── (user)/page.tsx, search/, requests/, profile/
├── (admin)/admin/
└── setup/
src/components/
├── audiobooks/ # Audiobook display
├── requests/ # Request cards, status
├── layout/ # Header, nav, footer
└── ui/ # Reusable primitives
Key Components
Layout
- Header ✅ - Top nav, search input, user menu with "Change Password" option (local users only), logout
- Sidebar - Admin side nav
- Footer - Version, links
Audiobooks
- AudiobookCard ✅ - Cover, title, author, narrator, duration, request button, clickable to open details modal. Shows "Requested by [username]" when someone else has requested the book, "Requested" when current user has requested it
- AudiobookGrid - Responsive grid (1/2/3/4 cols)
- AudiobookDetailsModal ✅ - Full-screen modal with comprehensive metadata (description, genres, rating, release date, narrator, language, format, publisher, request functionality). Shows requesting user's name when applicable
Requests
- RequestCard ✅ - Cover, title, author, status badge, progress bar, timestamps, action buttons (cancel, manual search, interactive search). When status=
awaiting_releaseandreleaseDateis set, shows "Releases <Mon DD, YYYY>" next to the status badge (UTC-formatted) - StatusBadge - Color-coded status (pending=yellow, awaiting_search=yellow, searching=blue, downloading=purple, downloaded=green, processing=orange, awaiting_import=orange, available=green, completed=green, failed=red, warn=orange, cancelled=gray, awaiting_approval=yellow, awaiting_release=teal "Awaiting Release", denied=red). Shows "Initializing..." when downloading with 0% progress (fetching torrent info), "Downloading" when progress > 0%
- ProgressBar - Animated fill with percentage
- InteractiveTorrentSearchModal ✅ - Responsive table of ranked torrent results, uses ConfirmModal for downloads, hides columns on smaller screens (size on mobile, seeds on tablet, indexer on desktop). Titles render verbatim; bracketed tags (e.g.
[German],[Unabridged]) parsed viaextractTitleTagsrender as slate chips in the metadata row (de-duped vsdisplayFormat); an explicit chevron-disclosure button toggles per-guidexpand only when the title is truncated (viauseIsTruncated), state resets on close - Active indicator: "Setting up..." with spinner when progress = 0%, "Active" with pulsing dot when progress > 0%
Forms
- SearchBar - Debounced input with suggestions
- Button - Variants (primary/secondary/outline/ghost/danger), sizes (sm/md/lg), loading state
- Input - Label, error display, validation, icons
- Select - Custom styling, search/filter
- Modal ✅ - Dialog overlay with backdrop, sizes (sm/md/lg/xl/full), ESC to close, body scroll lock
- ConfirmModal ✅ - Confirmation dialog with customizable title, message, buttons, loading state, and variant (primary/danger)
- ChangePasswordModal ✅ - Password change form for local users. Three fields (current, new, confirm), real-time validation, success/error states, auto-closes on success. Only accessible to users with
authProvider='local' - Pagination ✅ - Traditional page navigation with prev/next buttons, smart ellipsis (shows 1...4 5 6...10)
- StickyPagination ✅ - Minimal floating pill at bottom center with prev/next arrows, quick jump input, section label. Shows/hides based on section visibility (IntersectionObserver). Rounded-full design, backdrop blur, subtle shadow, auto-scroll on page change
Auth
- ProtectedRoute ✅ - Auth check, loading state, redirects, admin role support
- LoginPage ✅ - Full-screen design, floating covers, Plex OAuth popup
Admin
- MetricCard - Icon, label, value, trend
- DataTable - Sorting, filtering, pagination
- Chart - Line/bar/pie
Pages Implemented ✅
Homepage (/)
- Popular Audiobooks and New Releases sections with distinct visual separation
- Sticky section headers with rounded-2xl design matching section card aesthetic
- Gradient accent bars for each section (blue/purple for Popular, emerald/teal for New Releases)
- Headers use rounded cards (bg-white/90 dark:bg-gray-800/90) with backdrop blur
- Section content wrapped in semi-transparent rounded cards (bg-white/40 dark:bg-gray-800/40)
- Cohesive rounded design language throughout (rounded-2xl on headers and containers)
- Floating pagination pill at bottom center of viewport
- Minimal design: section label | ← | Page X of Y | →
- Quick jump input (type page number + Enter)
- Auto-shows when scrolling through a section (IntersectionObserver)
- Auto-scrolls to section top on page change
- Rounded-full design with backdrop blur and subtle shadow
- Responsive grid layouts (1/2/3/4 cols)
- Enhanced CTA section with gradient background (blue-to-indigo)
Requests Page (/requests)
- Filter tabs: All, Active, Waiting, Completed, Failed, Cancelled
- Auto-refresh every 5s (SWR)
- Request counts per tab
- Cancel functionality
- Loading skeletons, empty states
- Waiting filter shows awaiting_search and awaiting_import statuses
Profile Page (/profile)
- User info card (avatar, username, email, role, Plex ID)
- Stats: Total/Active/Waiting/Completed/Failed/Cancelled requests
- Active downloads section
- Recent requests (last 5)
- Auto-refresh every 5s
- Waiting stat shows awaiting_search and awaiting_import statuses
Component APIs
interface AudiobookCardProps {
audiobook: {asin, title, author, narrator?, coverArtUrl?, rating?, durationMinutes?, isRequested?, requestStatus?, requestedByUsername?};
onRequest?: (asin: string) => void;
isRequested?: boolean;
requestStatus?: string;
onRequestSuccess?: () => void;
}
interface AudiobookDetailsModalProps {
asin: string;
isOpen: boolean;
onClose: () => void;
onRequestSuccess?: () => void;
onStatusChange?: (newStatus: string) => void;
onIgnoreChange?: (isIgnored: boolean) => void;
isRequested?: boolean;
requestStatus?: string | null;
isAvailable?: boolean;
requestedByUsername?: string | null;
hideRequestActions?: boolean; // Hides sticky action bar for read-only contexts (BookDate, ShelvesSection)
hasReportedIssue?: boolean;
aiReason?: string | null;
adminActions?: React.ReactNode; // Optional admin buttons (Approve/Search/Deny) rendered as second row in action bar
}
interface RequestCardProps {
request: {id, status, progress, audiobook: {title, author, coverArtUrl?}, createdAt, updatedAt};
showActions?: boolean;
}
interface ModalProps {
isOpen: boolean;
onClose: () => void;
title: string;
children: React.ReactNode;
size?: 'sm' | 'md' | 'lg' | 'xl' | 'full';
showCloseButton?: boolean;
}
interface InteractiveTorrentSearchModalProps {
isOpen: boolean;
onClose: () => void;
requestId: string;
audiobook: {title: string, author: string};
}
interface ConfirmModalProps {
isOpen: boolean;
onClose: () => void;
onConfirm: () => void;
title: string;
message: string;
confirmText?: string;
cancelText?: string;
isLoading?: boolean;
variant?: 'danger' | 'primary';
}
interface PaginationProps {
currentPage: number;
totalPages: number;
onPageChange: (page: number) => void;
className?: string;
}
interface StickyPaginationProps {
currentPage: number;
totalPages: number;
onPageChange: (page: number) => void;
sectionRef: React.RefObject<HTMLElement | null>;
label: string;
}
Custom Hooks
- useAuth -
{user, login, logout, isLoading} - useAudiobooks -
{audiobooks, isLoading, error, totalPages, hasMore} - useAudiobookDetails ✅ -
{audiobook, isLoading, error}- Fetches individual audiobook by ASIN - useRequest -
{createRequest, cancelRequest, isLoading}
Styling
Tailwind Patterns:
- Container:
container mx-auto px-4 py-8 max-w-7xl - Card:
bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 - Button:
bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md - Grid:
grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6
Dark Mode: Use dark: variant
Responsive Breakpoints
- Mobile: <768px (1 col)
- Tablet: 768-1024px (2 cols)
- Desktop: 1024-1280px (3 cols)
- Large: >1280px (4 cols)
Tech Stack
- Next.js 14+ App Router
- React 19
- Tailwind CSS 4
- Heroicons/Lucide React
- React Hook Form + Zod
- SWR (data fetching)
- date-fns (formatting)