mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 06:51:12 -05:00
Integrate transaction resilience hook
Integrate the `useTransactionResilience` hook into `SubmissionReviewManager.tsx` to add timeout detection, auto-release functionality, and idempotency key management to moderation actions. The `handleApprove` and `handleReject` functions have been updated to use the `executeTransaction` wrapper for these operations.
This commit is contained in:
@@ -6,6 +6,7 @@ import { handleError, getErrorMessage } from '@/lib/errorHandler';
|
||||
import { invokeWithTracking } from '@/lib/edgeFunctionTracking';
|
||||
import { moderationReducer, canApprove, canReject, hasActiveLock } from '@/lib/moderationStateMachine';
|
||||
import { useLockMonitor } from '@/lib/moderation/lockMonitor';
|
||||
import { useTransactionResilience } from '@/hooks/useTransactionResilience';
|
||||
import {
|
||||
fetchSubmissionItems,
|
||||
buildDependencyTree,
|
||||
@@ -92,6 +93,15 @@ export function SubmissionReviewManager({
|
||||
// Lock monitoring integration
|
||||
const { extendLock } = useLockMonitor(state, dispatch, submissionId);
|
||||
|
||||
// Transaction resilience (timeout detection & auto-release)
|
||||
const { executeTransaction } = useTransactionResilience({
|
||||
submissionId,
|
||||
timeoutMs: 30000, // 30s timeout
|
||||
autoReleaseOnUnload: true,
|
||||
autoReleaseOnInactivity: true,
|
||||
inactivityMinutes: 10,
|
||||
});
|
||||
|
||||
// Moderation actions
|
||||
const { escalateSubmission } = useModerationActions({
|
||||
user,
|
||||
@@ -230,6 +240,7 @@ export function SubmissionReviewManager({
|
||||
}
|
||||
|
||||
const selectedItems = items.filter(item => selectedItemIds.has(item.id));
|
||||
const selectedIds = Array.from(selectedItemIds);
|
||||
|
||||
// Transition: reviewing → approving
|
||||
dispatch({ type: 'START_APPROVAL' });
|
||||
@@ -258,6 +269,7 @@ export function SubmissionReviewManager({
|
||||
id: item.id
|
||||
}))
|
||||
);
|
||||
|
||||
|
||||
setValidationResults(validationResultsMap);
|
||||
|
||||
@@ -324,64 +336,73 @@ export function SubmissionReviewManager({
|
||||
return; // Ask for confirmation
|
||||
}
|
||||
|
||||
// Proceed with approval
|
||||
const { supabase } = await import('@/integrations/supabase/client');
|
||||
|
||||
// Call the edge function for backend processing
|
||||
const { data, error, requestId } = await invokeWithTracking(
|
||||
'process-selective-approval',
|
||||
{
|
||||
itemIds: Array.from(selectedItemIds),
|
||||
submissionId
|
||||
},
|
||||
user?.id
|
||||
// Proceed with approval - wrapped with transaction resilience
|
||||
await executeTransaction(
|
||||
'approval',
|
||||
selectedIds,
|
||||
async (idempotencyKey) => {
|
||||
const { supabase } = await import('@/integrations/supabase/client');
|
||||
|
||||
// Call the edge function for backend processing
|
||||
const { data, error, requestId } = await invokeWithTracking(
|
||||
'process-selective-approval',
|
||||
{
|
||||
itemIds: selectedIds,
|
||||
submissionId,
|
||||
idempotencyKey, // Pass idempotency key to edge function
|
||||
},
|
||||
user?.id
|
||||
);
|
||||
|
||||
if (error) {
|
||||
throw new Error(error.message || 'Failed to process approval');
|
||||
}
|
||||
|
||||
if (!data?.success) {
|
||||
throw new Error(data?.error || 'Approval processing failed');
|
||||
}
|
||||
|
||||
// Transition: approving → complete
|
||||
dispatch({ type: 'COMPLETE', payload: { result: 'approved' } });
|
||||
|
||||
toast({
|
||||
title: 'Items Approved',
|
||||
description: `Successfully approved ${selectedIds.length} item(s)${requestId ? ` (Request: ${requestId.substring(0, 8)})` : ''}`,
|
||||
});
|
||||
|
||||
interface ApprovalResult { success: boolean; item_id: string; error?: string }
|
||||
const successCount = data.results.filter((r: ApprovalResult) => r.success).length;
|
||||
const failCount = data.results.filter((r: ApprovalResult) => !r.success).length;
|
||||
|
||||
const allFailed = failCount > 0 && successCount === 0;
|
||||
const someFailed = failCount > 0 && successCount > 0;
|
||||
|
||||
toast({
|
||||
title: allFailed ? 'Approval Failed' : someFailed ? 'Partial Approval' : 'Approval Complete',
|
||||
description: failCount > 0
|
||||
? `Approved ${successCount} item(s), ${failCount} failed`
|
||||
: `Successfully approved ${successCount} item(s)`,
|
||||
variant: allFailed ? 'destructive' : someFailed ? 'default' : 'default',
|
||||
});
|
||||
|
||||
// Reset warning confirmation state after approval
|
||||
setUserConfirmedWarnings(false);
|
||||
|
||||
// If ALL items failed, don't close dialog - show errors
|
||||
if (allFailed) {
|
||||
dispatch({ type: 'ERROR', payload: { error: 'All items failed' } });
|
||||
return data;
|
||||
}
|
||||
|
||||
// Reset warning confirmation state after approval
|
||||
setUserConfirmedWarnings(false);
|
||||
|
||||
onComplete();
|
||||
onOpenChange(false);
|
||||
|
||||
return data;
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
throw new Error(error.message || 'Failed to process approval');
|
||||
}
|
||||
|
||||
if (!data?.success) {
|
||||
throw new Error(data?.error || 'Approval processing failed');
|
||||
}
|
||||
|
||||
// Transition: approving → complete
|
||||
dispatch({ type: 'COMPLETE', payload: { result: 'approved' } });
|
||||
|
||||
toast({
|
||||
title: 'Items Approved',
|
||||
description: `Successfully approved ${selectedItemIds.size} item(s)${requestId ? ` (Request: ${requestId.substring(0, 8)})` : ''}`,
|
||||
});
|
||||
|
||||
interface ApprovalResult { success: boolean; item_id: string; error?: string }
|
||||
const successCount = data.results.filter((r: ApprovalResult) => r.success).length;
|
||||
const failCount = data.results.filter((r: ApprovalResult) => !r.success).length;
|
||||
|
||||
const allFailed = failCount > 0 && successCount === 0;
|
||||
const someFailed = failCount > 0 && successCount > 0;
|
||||
|
||||
toast({
|
||||
title: allFailed ? 'Approval Failed' : someFailed ? 'Partial Approval' : 'Approval Complete',
|
||||
description: failCount > 0
|
||||
? `Approved ${successCount} item(s), ${failCount} failed`
|
||||
: `Successfully approved ${successCount} item(s)`,
|
||||
variant: allFailed ? 'destructive' : someFailed ? 'default' : 'default',
|
||||
});
|
||||
|
||||
// Reset warning confirmation state after approval
|
||||
setUserConfirmedWarnings(false);
|
||||
|
||||
// If ALL items failed, don't close dialog - show errors
|
||||
if (allFailed) {
|
||||
dispatch({ type: 'ERROR', payload: { error: 'All items failed' } });
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset warning confirmation state after approval
|
||||
setUserConfirmedWarnings(false);
|
||||
|
||||
onComplete();
|
||||
onOpenChange(false);
|
||||
} catch (error: unknown) {
|
||||
dispatch({ type: 'ERROR', payload: { error: getErrorMessage(error) } });
|
||||
handleError(error, {
|
||||
@@ -438,23 +459,34 @@ export function SubmissionReviewManager({
|
||||
|
||||
if (!user?.id) return;
|
||||
|
||||
const selectedItems = items.filter(item => selectedItemIds.has(item.id));
|
||||
const selectedIds = selectedItems.map(item => item.id);
|
||||
|
||||
// Transition: reviewing → rejecting
|
||||
dispatch({ type: 'START_REJECTION' });
|
||||
|
||||
try {
|
||||
const selectedItems = items.filter(item => selectedItemIds.has(item.id));
|
||||
await rejectSubmissionItems(selectedItems, reason, user.id, cascade);
|
||||
|
||||
// Transition: rejecting → complete
|
||||
dispatch({ type: 'COMPLETE', payload: { result: 'rejected' } });
|
||||
|
||||
toast({
|
||||
title: 'Items Rejected',
|
||||
description: `Successfully rejected ${selectedItems.length} item${selectedItems.length !== 1 ? 's' : ''}`,
|
||||
});
|
||||
// Wrap rejection with transaction resilience
|
||||
await executeTransaction(
|
||||
'rejection',
|
||||
selectedIds,
|
||||
async (idempotencyKey) => {
|
||||
await rejectSubmissionItems(selectedItems, reason, user.id, cascade);
|
||||
|
||||
// Transition: rejecting → complete
|
||||
dispatch({ type: 'COMPLETE', payload: { result: 'rejected' } });
|
||||
|
||||
toast({
|
||||
title: 'Items Rejected',
|
||||
description: `Successfully rejected ${selectedItems.length} item${selectedItems.length !== 1 ? 's' : ''}`,
|
||||
});
|
||||
|
||||
onComplete();
|
||||
onOpenChange(false);
|
||||
onComplete();
|
||||
onOpenChange(false);
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
);
|
||||
} catch (error: unknown) {
|
||||
dispatch({ type: 'ERROR', payload: { error: getErrorMessage(error) } });
|
||||
handleError(error, {
|
||||
|
||||
Reference in New Issue
Block a user