From 247fe88b99a64b313ef58577c454cc4a9f69ffbe Mon Sep 17 00:00:00 2001 From: kikootwo Date: Thu, 14 May 2026 15:43:30 -0400 Subject: [PATCH] Refactor approval buttons into reusable component Extract LoadingSpinner and ApprovalActionButtons components and replace duplicated approve/search/deny button blocks with the new ApprovalActionButtons to reduce duplication and centralize behavior/styles. Remove the inline LoadingSpinner in PendingApprovalSection, add an aria-label to the details button, and update the details modal's adminActions to use ApprovalActionButtons with callbacks that handle approval/denial/search and close modals as needed. Improves DRY, maintainability, and consistency of loading state handling. --- src/app/admin/page.tsx | 176 ++++++++++++++++++++--------------------- 1 file changed, 84 insertions(+), 92 deletions(-) diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index de2b9cd..d54fd9e 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -58,6 +58,63 @@ function formatTorrentSize(bytes: number): string { return gb >= 1 ? `${gb.toFixed(1)} GB` : `${mb.toFixed(0)} MB`; } +function LoadingSpinner() { + return ( + + + + + ); +} + +interface ApprovalActionButtonsProps { + isLoading: boolean; + onApprove: () => void; + onSearch: () => void; + onDeny: () => void; +} + +function ApprovalActionButtons({ isLoading, onApprove, onSearch, onDeny }: ApprovalActionButtonsProps) { + return ( + <> + + + + + ); +} + function PendingApprovalSection({ requests }: { requests: PendingApprovalRequest[] }) { const toast = useToast(); const [loadingStates, setLoadingStates] = useState>({}); @@ -133,13 +190,6 @@ function PendingApprovalSection({ requests }: { requests: PendingApprovalRequest await mutate('/api/admin/metrics'); }; - const LoadingSpinner = () => ( - - - - - ); - return (
{/* Section Header */} @@ -189,6 +239,7 @@ function PendingApprovalSection({ requests }: { requests: PendingApprovalRequest }} className="absolute top-2 right-2 z-10 p-1 text-gray-400 hover:text-blue-500 dark:hover:text-blue-400 transition-colors rounded-full hover:bg-gray-100 dark:hover:bg-gray-700" title="View book details" + aria-label="View book details" > @@ -336,42 +387,12 @@ function PendingApprovalSection({ requests }: { requests: PendingApprovalRequest {/* Action Buttons */}
- - - - - + handleApproveRequest(request.id)} + onSearch={() => setSearchModalRequestId(request.id)} + onDeny={() => handleDenyRequest(request.id)} + />
); @@ -406,55 +427,26 @@ function PendingApprovalSection({ requests }: { requests: PendingApprovalRequest onClose={() => { setDetailsAsin(null); setDetailsRequestId(null); }} requestStatus="awaiting_approval" requestedByUsername={detailsRequest?.user.plexUsername ?? null} - adminActions={(() => { - const isLoading = loadingStates[detailsRequestId] || false; - return ( - <> - - - - - ); - })()} + adminActions={ + { + await handleApproveRequest(detailsRequestId); + setDetailsAsin(null); + setDetailsRequestId(null); + }} + onSearch={() => { + setSearchModalRequestId(detailsRequestId); + setDetailsAsin(null); + setDetailsRequestId(null); + }} + onDeny={async () => { + await handleDenyRequest(detailsRequestId); + setDetailsAsin(null); + setDetailsRequestId(null); + }} + /> + } /> )}