diff --git a/documentation/frontend/components.md b/documentation/frontend/components.md index ccc8fa3..2299eab 100644 --- a/documentation/frontend/components.md +++ b/documentation/frontend/components.md @@ -109,10 +109,15 @@ interface AudiobookDetailsModalProps { 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 } diff --git a/src/components/requests/RequestCard.tsx b/src/components/requests/RequestCard.tsx index 0295881..15764d3 100644 --- a/src/components/requests/RequestCard.tsx +++ b/src/components/requests/RequestCard.tsx @@ -273,7 +273,6 @@ export function RequestCard({ request, showActions = true }: RequestCardProps) { onClose={() => setShowDetailsModal(false)} requestStatus={request.status} isAvailable={COMPLETED_STATUSES.includes(request.status as typeof COMPLETED_STATUSES[number])} - hideRequestActions /> )} diff --git a/tests/components/audiobooks/AudiobookDetailsModal.test.tsx b/tests/components/audiobooks/AudiobookDetailsModal.test.tsx index 04eebc0..63d5712 100644 --- a/tests/components/audiobooks/AudiobookDetailsModal.test.tsx +++ b/tests/components/audiobooks/AudiobookDetailsModal.test.tsx @@ -305,4 +305,26 @@ describe('AudiobookDetailsModal', () => { expect(screen.queryByText('Request failed')).toBeNull(); }); + + it('renders sticky footer with status pill and admin icons when opened from a pending request', async () => { + useAuthMock.mockReturnValue({ user: { id: 'admin-1', username: 'admin', role: 'admin' } }); + const { AudiobookDetailsModal } = await import('@/components/audiobooks/AudiobookDetailsModal'); + + render( + + ); + + await act(async () => {}); + + const statusPill = screen.getByRole('button', { name: 'Requested' }); + expect(statusPill).toBeDisabled(); + expect(screen.getByTitle('Interactive Search')).toBeInTheDocument(); + expect(screen.getByTitle('Manual Import')).toBeInTheDocument(); + }); }); diff --git a/tests/components/requests/RequestCard.test.tsx b/tests/components/requests/RequestCard.test.tsx index 9002f27..d34d592 100644 --- a/tests/components/requests/RequestCard.test.tsx +++ b/tests/components/requests/RequestCard.test.tsx @@ -10,11 +10,19 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; const cancelRequestMock = vi.hoisted(() => vi.fn()); +const detailsModalSpy = vi.hoisted(() => vi.fn()); vi.mock('@/lib/hooks/useRequests', () => ({ useCancelRequest: () => ({ cancelRequest: cancelRequestMock, isLoading: false }), })); +vi.mock('@/components/audiobooks/AudiobookDetailsModal', () => ({ + AudiobookDetailsModal: (props: any) => { + detailsModalSpy(props); + return
; + }, +})); + vi.mock('next/image', () => ({ __esModule: true, default: (props: any) => , @@ -52,6 +60,7 @@ const baseRequest = { describe('RequestCard', () => { beforeEach(() => { cancelRequestMock.mockReset(); + detailsModalSpy.mockReset(); }); afterEach(() => { @@ -218,4 +227,25 @@ describe('RequestCard', () => { expect(screen.queryByText(/^Releases /)).toBeNull(); }); + + it('opens AudiobookDetailsModal without hiding request actions when details are viewed', async () => { + const { RequestCard } = await import('@/components/requests/RequestCard'); + + render( + + ); + + fireEvent.click(screen.getByRole('button', { name: baseRequest.audiobook.title })); + + expect(detailsModalSpy).toHaveBeenCalled(); + const props = detailsModalSpy.mock.calls.at(-1)?.[0]; + expect(props.isOpen).toBe(true); + expect(props.asin).toBe('ASIN123'); + expect(props.hideRequestActions).toBeUndefined(); + }); });