Files
ReadMeABook/documentation/frontend/components.md
T
kikootwo e39e44ee44 Add modal props & update RequestCard/tests
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.
2026-05-16 11:30:44 -04:00

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_release and releaseDate is 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 via extractTitleTags render as slate chips in the metadata row (de-duped vs displayFormat); an explicit chevron-disclosure button toggles per-guid expand only when the title is truncated (via useIsTruncated), 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)