mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-23 09:11:13 -05:00
feat: Implement timeline manager
This commit is contained in:
@@ -3,7 +3,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { supabase } from '@/lib/supabaseClient';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { getErrorMessage } from '@/lib/errorHandler';
|
||||
import { getErrorMessage, handleError, isSupabaseConnectionError } from '@/lib/errorHandler';
|
||||
import { validateMultipleItems } from '@/lib/entityValidationSchemas';
|
||||
import { invokeWithTracking } from '@/lib/edgeFunctionTracking';
|
||||
import type { User } from '@supabase/supabase-js';
|
||||
@@ -27,6 +27,7 @@ export interface ModerationActions {
|
||||
deleteSubmission: (item: ModerationItem) => Promise<void>;
|
||||
resetToPending: (item: ModerationItem) => Promise<void>;
|
||||
retryFailedItems: (item: ModerationItem) => Promise<void>;
|
||||
escalateSubmission: (item: ModerationItem, reason: string) => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -321,18 +322,29 @@ export function useModerationActions(config: ModerationActionsConfig): Moderatio
|
||||
|
||||
return { previousData };
|
||||
},
|
||||
onError: (error, variables, context) => {
|
||||
// Rollback on error
|
||||
onError: (error: any, variables, context) => {
|
||||
// Rollback optimistic update
|
||||
if (context?.previousData) {
|
||||
queryClient.setQueryData(['moderation-queue'], context.previousData);
|
||||
}
|
||||
|
||||
// Enhanced error handling with reference ID and network detection
|
||||
const isNetworkError = isSupabaseConnectionError(error);
|
||||
const errorMessage = getErrorMessage(error) || `Failed to ${variables.action} content`;
|
||||
|
||||
// Error already logged by mutation, just show toast
|
||||
toast({
|
||||
title: 'Action Failed',
|
||||
description: getErrorMessage(error) || `Failed to ${variables.action} content`,
|
||||
title: isNetworkError ? 'Connection Error' : 'Action Failed',
|
||||
description: errorMessage,
|
||||
variant: 'destructive',
|
||||
});
|
||||
|
||||
logger.error('Moderation action failed', {
|
||||
itemId: variables.item.id,
|
||||
action: variables.action,
|
||||
error: errorMessage,
|
||||
errorId: error.errorId,
|
||||
isNetworkError,
|
||||
});
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
if (data) {
|
||||
@@ -350,14 +362,34 @@ export function useModerationActions(config: ModerationActionsConfig): Moderatio
|
||||
});
|
||||
|
||||
/**
|
||||
* Wrapper for performAction mutation to maintain API compatibility
|
||||
* Wrapper function that handles loading states and error tracking
|
||||
*/
|
||||
const performAction = useCallback(
|
||||
async (item: ModerationItem, action: 'approved' | 'rejected', moderatorNotes?: string) => {
|
||||
onActionStart(item.id);
|
||||
await performActionMutation.mutateAsync({ item, action, moderatorNotes });
|
||||
try {
|
||||
await performActionMutation.mutateAsync({ item, action, moderatorNotes });
|
||||
} catch (error) {
|
||||
const errorId = handleError(error, {
|
||||
action: `Moderation ${action}`,
|
||||
userId: user?.id,
|
||||
metadata: {
|
||||
submissionId: item.id,
|
||||
submissionType: item.submission_type,
|
||||
itemType: item.type,
|
||||
hasSubmissionItems: item.submission_items?.length ?? 0,
|
||||
moderatorNotes: moderatorNotes?.substring(0, 100),
|
||||
},
|
||||
});
|
||||
|
||||
// Attach error ID for UI display
|
||||
const enhancedError = error instanceof Error
|
||||
? Object.assign(error, { errorId })
|
||||
: { message: getErrorMessage(error), errorId };
|
||||
throw enhancedError;
|
||||
}
|
||||
},
|
||||
[onActionStart, performActionMutation]
|
||||
[onActionStart, performActionMutation, user]
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -406,13 +438,23 @@ export function useModerationActions(config: ModerationActionsConfig): Moderatio
|
||||
|
||||
logger.log(`✅ Submission ${item.id} deleted`);
|
||||
} catch (error: unknown) {
|
||||
// Error already handled, just show toast
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: getErrorMessage(error),
|
||||
variant: 'destructive',
|
||||
const errorId = handleError(error, {
|
||||
action: 'Delete Submission',
|
||||
userId: user?.id,
|
||||
metadata: {
|
||||
submissionId: item.id,
|
||||
submissionType: item.submission_type,
|
||||
},
|
||||
});
|
||||
throw error;
|
||||
|
||||
logger.error('Failed to delete submission', {
|
||||
submissionId: item.id,
|
||||
errorId,
|
||||
});
|
||||
const enhancedError = error instanceof Error
|
||||
? Object.assign(error, { errorId })
|
||||
: { message: getErrorMessage(error), errorId };
|
||||
throw enhancedError;
|
||||
} finally {
|
||||
onActionComplete();
|
||||
}
|
||||
@@ -455,12 +497,23 @@ export function useModerationActions(config: ModerationActionsConfig): Moderatio
|
||||
|
||||
logger.log(`✅ Submission ${item.id} reset to pending`);
|
||||
} catch (error: unknown) {
|
||||
// Error already handled, just show toast
|
||||
toast({
|
||||
title: 'Reset Failed',
|
||||
description: getErrorMessage(error),
|
||||
variant: 'destructive',
|
||||
const errorId = handleError(error, {
|
||||
action: 'Reset to Pending',
|
||||
userId: user?.id,
|
||||
metadata: {
|
||||
submissionId: item.id,
|
||||
submissionType: item.submission_type,
|
||||
},
|
||||
});
|
||||
|
||||
logger.error('Failed to reset status', {
|
||||
submissionId: item.id,
|
||||
errorId,
|
||||
});
|
||||
const enhancedError = error instanceof Error
|
||||
? Object.assign(error, { errorId })
|
||||
: { message: getErrorMessage(error), errorId };
|
||||
throw enhancedError;
|
||||
} finally {
|
||||
onActionComplete();
|
||||
}
|
||||
@@ -474,6 +527,7 @@ export function useModerationActions(config: ModerationActionsConfig): Moderatio
|
||||
const retryFailedItems = useCallback(
|
||||
async (item: ModerationItem) => {
|
||||
onActionStart(item.id);
|
||||
let failedItemsCount = 0;
|
||||
|
||||
try {
|
||||
const { data: failedItems } = await supabase
|
||||
@@ -490,6 +544,8 @@ export function useModerationActions(config: ModerationActionsConfig): Moderatio
|
||||
return;
|
||||
}
|
||||
|
||||
failedItemsCount = failedItems.length;
|
||||
|
||||
const { data, error, requestId } = await invokeWithTracking(
|
||||
'process-selective-approval',
|
||||
{
|
||||
@@ -527,17 +583,112 @@ export function useModerationActions(config: ModerationActionsConfig): Moderatio
|
||||
|
||||
logger.log(`✅ Retried ${failedItems.length} failed items for ${item.id}`);
|
||||
} catch (error: unknown) {
|
||||
// Error already handled, just show toast
|
||||
toast({
|
||||
title: 'Retry Failed',
|
||||
description: getErrorMessage(error) || 'Failed to retry items',
|
||||
variant: 'destructive',
|
||||
const errorId = handleError(error, {
|
||||
action: 'Retry Failed Items',
|
||||
userId: user?.id,
|
||||
metadata: {
|
||||
submissionId: item.id,
|
||||
failedItemsCount,
|
||||
},
|
||||
});
|
||||
|
||||
logger.error('Failed to retry items', {
|
||||
submissionId: item.id,
|
||||
errorId,
|
||||
});
|
||||
const enhancedError = error instanceof Error
|
||||
? Object.assign(error, { errorId })
|
||||
: { message: getErrorMessage(error), errorId };
|
||||
throw enhancedError;
|
||||
} finally {
|
||||
onActionComplete();
|
||||
}
|
||||
},
|
||||
[toast, onActionStart, onActionComplete]
|
||||
[toast, onActionStart, onActionComplete, user]
|
||||
);
|
||||
|
||||
/**
|
||||
* Escalate submission for admin review
|
||||
* Consolidates escalation logic with comprehensive error handling
|
||||
*/
|
||||
const escalateSubmission = useCallback(
|
||||
async (item: ModerationItem, reason: string) => {
|
||||
if (!user?.id) {
|
||||
toast({
|
||||
title: 'Authentication Required',
|
||||
description: 'You must be logged in to escalate submissions',
|
||||
variant: 'destructive',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
onActionStart(item.id);
|
||||
|
||||
try {
|
||||
// Call edge function for email notification
|
||||
const { error: edgeFunctionError, requestId } = await invokeWithTracking(
|
||||
'send-escalation-notification',
|
||||
{
|
||||
submissionId: item.id,
|
||||
escalationReason: reason,
|
||||
escalatedBy: user.id,
|
||||
},
|
||||
user.id
|
||||
);
|
||||
|
||||
if (edgeFunctionError) {
|
||||
// Edge function failed - log and show fallback toast
|
||||
handleError(edgeFunctionError, {
|
||||
action: 'Send escalation notification',
|
||||
userId: user.id,
|
||||
metadata: {
|
||||
submissionId: item.id,
|
||||
reason: reason.substring(0, 100),
|
||||
fallbackUsed: true,
|
||||
},
|
||||
});
|
||||
|
||||
toast({
|
||||
title: 'Escalated (Email Failed)',
|
||||
description: 'Submission escalated but notification email could not be sent',
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
title: 'Escalated Successfully',
|
||||
description: `Submission escalated and admin notified${requestId ? ` (${requestId.substring(0, 8)})` : ''}`,
|
||||
});
|
||||
}
|
||||
|
||||
// Invalidate cache
|
||||
queryClient.invalidateQueries({ queryKey: ['moderation-queue'] });
|
||||
|
||||
logger.log(`✅ Submission ${item.id} escalated`);
|
||||
} catch (error: unknown) {
|
||||
const errorId = handleError(error, {
|
||||
action: 'Escalate Submission',
|
||||
userId: user.id,
|
||||
metadata: {
|
||||
submissionId: item.id,
|
||||
submissionType: item.submission_type,
|
||||
reason: reason.substring(0, 100),
|
||||
},
|
||||
});
|
||||
|
||||
logger.error('Escalation failed', {
|
||||
submissionId: item.id,
|
||||
errorId,
|
||||
});
|
||||
|
||||
// Re-throw to allow UI to show retry option
|
||||
const enhancedError = error instanceof Error
|
||||
? Object.assign(error, { errorId })
|
||||
: { message: getErrorMessage(error), errorId };
|
||||
throw enhancedError;
|
||||
} finally {
|
||||
onActionComplete();
|
||||
}
|
||||
},
|
||||
[user, toast, onActionStart, onActionComplete, queryClient]
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -545,5 +696,6 @@ export function useModerationActions(config: ModerationActionsConfig): Moderatio
|
||||
deleteSubmission,
|
||||
resetToPending,
|
||||
retryFailedItems,
|
||||
escalateSubmission,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user