feat: Implement reusable button components

This commit is contained in:
gpt-engineer-app[bot]
2025-11-04 18:29:13 +00:00
parent cb01707c5e
commit b07004ed03
9 changed files with 304 additions and 112 deletions

View File

@@ -1,10 +1,10 @@
import { memo, useCallback } from 'react';
import { useDebouncedCallback } from 'use-debounce';
import {
CheckCircle, XCircle, RefreshCw, AlertCircle, Lock, Trash2,
Edit, Info, ExternalLink, ChevronDown, ListTree, Calendar
AlertCircle, Edit, Info, ExternalLink, ChevronDown, ListTree, Calendar
} 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';
@@ -159,24 +159,14 @@ export const QueueItemActions = memo(({
<AlertDescription className="text-blue-800 dark:text-blue-200">
<div className="flex items-center justify-between mt-2">
<span className="text-sm">Claim this submission to lock it for 15 minutes while you review</span>
<Button
<ActionButton
action="claim"
onClick={onClaim}
disabled={queueIsLoading || isClaiming}
isLoading={isClaiming}
size="sm"
className="ml-4"
>
{isClaiming ? (
<>
<RefreshCw className="w-4 h-4 mr-2 animate-spin" />
Claiming...
</>
) : (
<>
<Lock className="w-4 h-4 mr-2" />
Claim Submission
</>
)}
</Button>
/>
</div>
</AlertDescription>
</Alert>
@@ -274,46 +264,29 @@ export const QueueItemActions = memo(({
</>
)}
<Button
<ActionButton
action="approve"
onClick={handleApprove}
disabled={actionLoading === item.id || isLockedByOther || currentLockSubmissionId !== item.id}
className={`flex-1 ${isMobile ? 'h-11' : ''}`}
isLoading={actionLoading === item.id}
loadingText={
item.submission_items && item.submission_items.length > 5
? `Processing ${item.submission_items.length} items...`
: 'Processing...'
}
className="flex-1"
size={isMobile ? "default" : "default"}
>
{actionLoading === item.id ? (
<>
<RefreshCw className={`${isMobile ? "w-5 h-5" : "w-4 h-4"} mr-2 animate-spin`} />
{item.submission_items && item.submission_items.length > 5
? `Processing ${item.submission_items.length} items...`
: 'Processing...'
}
</>
) : (
<>
<CheckCircle className={isMobile ? "w-5 h-5 mr-2" : "w-4 h-4 mr-2"} />
Approve
</>
)}
</Button>
<Button
variant="destructive"
isMobile={isMobile}
/>
<ActionButton
action="reject"
onClick={handleReject}
disabled={actionLoading === item.id || isLockedByOther || currentLockSubmissionId !== item.id}
className={`flex-1 ${isMobile ? 'h-11' : ''}`}
isLoading={actionLoading === item.id}
className="flex-1"
size={isMobile ? "default" : "default"}
>
{actionLoading === item.id ? (
<>
<RefreshCw className={`${isMobile ? "w-5 h-5" : "w-4 h-4"} mr-2 animate-spin`} />
Processing...
</>
) : (
<>
<XCircle className={isMobile ? "w-5 h-5 mr-2" : "w-4 h-4 mr-2"} />
Reject
</>
)}
</Button>
isMobile={isMobile}
/>
</div>
</div>
</>
@@ -329,15 +302,13 @@ export const QueueItemActions = memo(({
<p className="text-xs mt-1">You can reset it to pending to re-review and approve it.</p>
</div>
</div>
<Button
<ActionButton
action="reset"
onClick={handleResetToPending}
disabled={actionLoading === item.id}
variant="outline"
isLoading={actionLoading === item.id}
className="w-full"
>
<RefreshCw className="w-4 h-4 mr-2" />
Reset to Pending
</Button>
/>
</div>
)}
@@ -361,23 +332,22 @@ export const QueueItemActions = memo(({
<ListTree className="w-4 h-4 mr-2" />
Review Items
</Button>
<Button
<ActionButton
action="reset"
onClick={handleResetToPending}
disabled={actionLoading === item.id}
variant="outline"
isLoading={actionLoading === item.id}
className="flex-1"
>
<RefreshCw className="w-4 h-4 mr-2" />
Reset All
</Button>
<Button
</ActionButton>
<ActionButton
action="retry"
onClick={handleRetryFailed}
disabled={actionLoading === item.id}
className="flex-1 bg-yellow-600 hover:bg-yellow-700"
>
<RefreshCw className="w-4 h-4 mr-2" />
Retry Failed
</Button>
isLoading={actionLoading === item.id}
className="flex-1"
/>
</div>
</div>
)}
@@ -461,27 +431,30 @@ export const QueueItemActions = memo(({
/>
<div className={`flex gap-2 ${isMobile ? 'flex-col' : ''}`}>
{item.status === 'approved' && (
<Button
variant="destructive"
<ActionButton
action="reject"
onClick={handleReverseReject}
disabled={actionLoading === item.id}
className={`flex-1 ${isMobile ? 'h-11' : ''}`}
isLoading={actionLoading === item.id}
className="flex-1"
size={isMobile ? "default" : "default"}
isMobile={isMobile}
>
<XCircle className={isMobile ? "w-5 h-5 mr-2" : "w-4 h-4 mr-2"} />
Change to Rejected
</Button>
</ActionButton>
)}
{item.status === 'rejected' && (
<Button
<ActionButton
action="approve"
onClick={handleReverseApprove}
disabled={actionLoading === item.id}
className={`flex-1 ${isMobile ? 'h-11' : ''}`}
isLoading={actionLoading === item.id}
className="flex-1"
size={isMobile ? "default" : "default"}
isMobile={isMobile}
>
<CheckCircle className={isMobile ? "w-5 h-5 mr-2" : "w-4 h-4 mr-2"} />
Change to Approved
</Button>
</ActionButton>
)}
</div>
</div>
@@ -491,16 +464,17 @@ export const QueueItemActions = memo(({
{/* Delete button for rejected submissions (admin/superadmin only) */}
{item.status === 'rejected' && item.type === 'content_submission' && (isAdmin || isSuperuser) && (
<div className="pt-2">
<Button
variant="destructive"
<ActionButton
action="delete"
onClick={handleDeleteSubmission}
disabled={actionLoading === item.id}
className={`w-full ${isMobile ? 'h-11' : ''}`}
isLoading={actionLoading === item.id}
className="w-full"
size={isMobile ? "default" : "default"}
isMobile={isMobile}
>
<Trash2 className={isMobile ? "w-5 h-5 mr-2" : "w-4 h-4 mr-2"} />
Delete Submission
</Button>
</ActionButton>
</div>
)}
</>