import { memo, useCallback, useState } from 'react'; import { useDebouncedCallback } from 'use-debounce'; import { AlertCircle, Edit, Info, ExternalLink, ChevronDown, ListTree, Calendar, Crown, Unlock } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { ActionButton } from '@/components/ui/action-button'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; import { UserAvatar } from '@/components/ui/user-avatar'; import { format } from 'date-fns'; import type { ModerationItem } from '@/types/moderation'; import { sanitizeURL, sanitizePlainText } from '@/lib/sanitize'; import { getErrorMessage } from '@/lib/errorHandler'; interface QueueItemActionsProps { item: ModerationItem; isMobile: boolean; actionLoading: string | null; isLockedByMe: boolean; isLockedByOther: boolean; currentLockSubmissionId?: string; notes: Record; isAdmin: boolean; isSuperuser: boolean; queueIsLoading: boolean; isClaiming: boolean; onNoteChange: (id: string, value: string) => void; onApprove: (item: ModerationItem, action: 'approved' | 'rejected', notes?: string) => void; onResetToPending: (item: ModerationItem) => void; onRetryFailed: (item: ModerationItem) => void; onOpenReviewManager: (submissionId: string) => void; onOpenItemEditor: (submissionId: string) => void; onDeleteSubmission: (item: ModerationItem) => void; onInteractionFocus: (id: string) => void; onInteractionBlur: (id: string) => void; onClaim: () => void; onSuperuserReleaseLock?: (submissionId: string) => Promise; } export const QueueItemActions = memo(({ item, isMobile, actionLoading, isLockedByMe, isLockedByOther, currentLockSubmissionId, notes, isAdmin, isSuperuser, queueIsLoading, isClaiming, onNoteChange, onApprove, onResetToPending, onRetryFailed, onOpenReviewManager, onOpenItemEditor, onDeleteSubmission, onInteractionFocus, onInteractionBlur, onClaim, onSuperuserReleaseLock }: QueueItemActionsProps) => { // Error state for retry functionality const [actionError, setActionError] = useState<{ message: string; errorId?: string; action: 'approve' | 'reject'; } | null>(null); // Memoize all handlers to prevent re-renders const handleNoteChange = useCallback((e: React.ChangeEvent) => { onNoteChange(item.id, e.target.value); }, [onNoteChange, item.id]); // Debounced handlers with error tracking const handleApprove = useDebouncedCallback( async () => { if (actionLoading === item.id) return; try { setActionError(null); await onApprove(item, 'approved', notes[item.id]); } catch (error: any) { setActionError({ message: getErrorMessage(error), errorId: error.errorId, action: 'approve', }); } }, 300, { leading: true, trailing: false } ); const handleReject = useDebouncedCallback( async () => { if (actionLoading === item.id) return; try { setActionError(null); await onApprove(item, 'rejected', notes[item.id]); } catch (error: any) { setActionError({ message: getErrorMessage(error), errorId: error.errorId, action: 'reject', }); } }, 300, { leading: true, trailing: false } ); const handleResetToPending = useCallback(() => { onResetToPending(item); }, [onResetToPending, item]); const handleRetryFailed = useCallback(() => { onRetryFailed(item); }, [onRetryFailed, item]); const handleOpenReviewManager = useCallback(() => { onOpenReviewManager(item.id); }, [onOpenReviewManager, item.id]); const handleOpenItemEditor = useCallback(() => { onOpenItemEditor(item.id); }, [onOpenItemEditor, item.id]); const handleDeleteSubmission = useCallback(() => { onDeleteSubmission(item); }, [onDeleteSubmission, item]); const handleFocus = useCallback(() => { onInteractionFocus(item.id); }, [onInteractionFocus, item.id]); const handleBlur = useCallback(() => { onInteractionBlur(item.id); }, [onInteractionBlur, item.id]); const handleReverseNoteChange = useCallback((e: React.ChangeEvent) => { onNoteChange(`reverse-${item.id}`, e.target.value); }, [onNoteChange, item.id]); const handleReverseApprove = useDebouncedCallback( () => { if (actionLoading === item.id) { return; } onApprove(item, 'approved', notes[`reverse-${item.id}`]); }, 300, { leading: true, trailing: false } ); const handleReverseReject = useDebouncedCallback( () => { if (actionLoading === item.id) { return; } onApprove(item, 'rejected', notes[`reverse-${item.id}`]); }, 300, { leading: true, trailing: false } ); return ( <> {/* Error Display with Retry */} {actionError && ( Action Failed: {actionError.action}

{actionError.message}

{actionError.errorId && (

Reference ID: {actionError.errorId.slice(0, 8)}

)}
)} {/* Action buttons based on status */} {(item.status === 'pending' || item.status === 'flagged') && ( <> {/* Claim button for unclaimed submissions */} {!isLockedByOther && currentLockSubmissionId !== item.id && (
Unclaimed Submission
Claim this submission to lock it for 15 minutes while you review
)} {/* Superuser Lock Override - Show for locked items */} {isSuperuser && isLockedByOther && onSuperuserReleaseLock && ( Superuser Override

This submission is locked by another moderator. You can force-release this lock.

)}
{/* Submitter Context - shown before moderator can add their notes */} {(item.submission_items?.[0]?.item_data?.source_url || item.submission_items?.[0]?.item_data?.submission_notes) && (

Submitter Context

{item.submission_items?.[0]?.item_data?.source_url && ( )} {item.submission_items?.[0]?.item_data?.submission_notes && (
Submitter Notes:

{sanitizePlainText(item.submission_items[0].item_data.submission_notes)}

)}
)} {/* Left: Notes textarea */}