feat: Implement Novu subscriber update

This commit is contained in:
gpt-engineer-app[bot]
2025-10-15 16:49:58 +00:00
parent fca235269f
commit 4b697fe45a
6 changed files with 128 additions and 118 deletions

View File

@@ -87,8 +87,10 @@ export const useModerationQueue = (config?: UseModerationQueueConfig) => {
// Start countdown timer for lock expiry
const startLockTimer = useCallback((expiresAt: Date) => {
// Clear any existing timer first to prevent leaks
if (lockTimerRef.current) {
clearInterval(lockTimerRef.current);
lockTimerRef.current = null;
}
lockTimerRef.current = setInterval(() => {
@@ -96,93 +98,39 @@ export const useModerationQueue = (config?: UseModerationQueueConfig) => {
const timeLeft = expiresAt.getTime() - now.getTime();
if (timeLeft <= 0) {
// Lock expired
setCurrentLock(null);
// Clear timer before showing toast to prevent double-firing
if (lockTimerRef.current) {
clearInterval(lockTimerRef.current);
lockTimerRef.current = null;
}
setCurrentLock(null);
toast({
title: 'Lock Expired',
description: 'Your submission lock has expired',
description: 'Your review lock has expired. Claim another submission to continue.',
variant: 'destructive',
});
if (onLockStateChange) {
onLockStateChange();
}
}
}, 1000); // Check every second
}, [toast]);
}, 1000);
}, [toast, onLockStateChange]); // Add dependencies to avoid stale closures
// Clean up timer on unmount
useEffect(() => {
return () => {
// Comprehensive cleanup on unmount
if (lockTimerRef.current) {
clearInterval(lockTimerRef.current);
lockTimerRef.current = null;
}
};
}, []);
// Claim next submission from queue
const claimNext = useCallback(async (): Promise<string | null> => {
if (!user?.id) {
toast({
title: 'Authentication Required',
description: 'You must be logged in to claim submissions',
variant: 'destructive',
});
return null;
}
setIsLoading(true);
try {
const { data, error } = await supabase.rpc('claim_next_submission', {
moderator_id: user.id,
lock_duration: '15 minutes',
});
if (error) throw error;
if (!data || data.length === 0) {
toast({
title: 'Queue Empty',
description: 'No submissions available to review',
});
return null;
}
const claimed = data[0] as QueuedSubmission;
const expiresAt = new Date(Date.now() + 15 * 60 * 1000);
setCurrentLock({
submissionId: claimed.submission_id,
expiresAt,
});
startLockTimer(expiresAt);
fetchStats();
toast({
title: 'Submission Claimed',
description: `${claimed.submission_type} submission (waiting ${formatInterval(claimed.waiting_time)})`,
});
// Trigger refresh callback
if (onLockStateChange) {
onLockStateChange();
}
return claimed.submission_id;
} catch (error: any) {
console.error('Error claiming submission:', error);
toast({
title: 'Error',
description: error.message || 'Failed to claim submission',
variant: 'destructive',
});
return null;
} finally {
setIsLoading(false);
}
}, [user, toast, startLockTimer, fetchStats]);
// Extend current lock
// Claim a specific submission (CRM-style claim any)
const extendLock = useCallback(async (submissionId: string): Promise<boolean> => {
if (!user?.id) return false;
@@ -248,6 +196,7 @@ export const useModerationQueue = (config?: UseModerationQueueConfig) => {
if (lockTimerRef.current) {
clearInterval(lockTimerRef.current);
lockTimerRef.current = null; // Explicitly null it out
}
fetchStats();
@@ -341,6 +290,13 @@ export const useModerationQueue = (config?: UseModerationQueueConfig) => {
setIsLoading(true);
try {
// Get submission details FIRST for better toast message
const { data: submission } = await supabase
.from('content_submissions')
.select('submission_type')
.eq('id', submissionId)
.single();
const expiresAt = new Date(Date.now() + 15 * 60 * 1000);
const { error } = await supabase
@@ -361,14 +317,17 @@ export const useModerationQueue = (config?: UseModerationQueueConfig) => {
});
startLockTimer(expiresAt);
fetchStats();
// Enhanced toast with submission type
const submissionType = submission?.submission_type || 'submission';
toast({
title: 'Submission Claimed',
description: 'You now have 15 minutes to review this submission',
title: 'Submission Claimed',
description: `${submissionType.charAt(0).toUpperCase() + submissionType.slice(1)} locked for 15 minutes. Start reviewing now.`,
duration: 4000,
});
// Trigger refresh callback
// Force UI refresh to update queue
fetchStats();
if (onLockStateChange) {
onLockStateChange();
}
@@ -377,15 +336,15 @@ export const useModerationQueue = (config?: UseModerationQueueConfig) => {
} catch (error: any) {
console.error('Error claiming submission:', error);
toast({
title: 'Error',
description: error.message || 'Failed to claim submission',
title: 'Failed to Claim Submission',
description: error.message || 'Could not claim this submission. Try again.',
variant: 'destructive',
});
return false;
} finally {
setIsLoading(false);
setIsLoading(false); // Always clear loading state
}
}, [user, toast, startLockTimer, fetchStats]);
}, [user, toast, startLockTimer, fetchStats, onLockStateChange]);
// Reassign submission
const reassignSubmission = useCallback(async (submissionId: string, newModeratorId: string): Promise<boolean> => {
@@ -474,7 +433,6 @@ export const useModerationQueue = (config?: UseModerationQueueConfig) => {
currentLock, // Exposed for reactive UI updates
queueStats,
isLoading,
claimNext,
claimSubmission,
extendLock,
releaseLock,