mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-22 00:31:13 -05:00
feat: Complete error logging coverage
This commit is contained in:
@@ -74,7 +74,7 @@ export function ReassignDialog({
|
|||||||
.rpc('get_users_with_emails');
|
.rpc('get_users_with_emails');
|
||||||
|
|
||||||
if (rpcError) {
|
if (rpcError) {
|
||||||
logger.warn('Failed to fetch users with emails, using basic profiles', { error: getErrorMessage(rpcError) });
|
// Fall back to basic profiles
|
||||||
const { data: basicProfiles } = await supabase
|
const { data: basicProfiles } = await supabase
|
||||||
.from('profiles')
|
.from('profiles')
|
||||||
.select('user_id, username, display_name')
|
.select('user_id, username, display_name')
|
||||||
|
|||||||
@@ -9,8 +9,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
|
|||||||
import { supabase } from '@/lib/supabaseClient';
|
import { supabase } from '@/lib/supabaseClient';
|
||||||
import { useAuth } from '@/hooks/useAuth';
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
import { useUserRole } from '@/hooks/useUserRole';
|
import { useUserRole } from '@/hooks/useUserRole';
|
||||||
import { handleError, handleSuccess, getErrorMessage } from '@/lib/errorHandler';
|
import { handleError, handleNonCriticalError, handleSuccess, getErrorMessage } from '@/lib/errorHandler';
|
||||||
import { logger } from '@/lib/logger';
|
|
||||||
|
|
||||||
// Type-safe role definitions
|
// Type-safe role definitions
|
||||||
const VALID_ROLES = ['admin', 'moderator', 'user'] as const;
|
const VALID_ROLES = ['admin', 'moderator', 'user'] as const;
|
||||||
@@ -91,7 +90,7 @@ export function UserRoleManager() {
|
|||||||
.rpc('get_users_with_emails');
|
.rpc('get_users_with_emails');
|
||||||
|
|
||||||
if (rpcError) {
|
if (rpcError) {
|
||||||
logger.warn('Failed to fetch users with emails, using basic profiles', { error: getErrorMessage(rpcError) });
|
// Fall back to basic profiles
|
||||||
const { data: basicProfiles } = await supabase
|
const { data: basicProfiles } = await supabase
|
||||||
.from('profiles')
|
.from('profiles')
|
||||||
.select('user_id, username, display_name')
|
.select('user_id, username, display_name')
|
||||||
@@ -128,7 +127,7 @@ export function UserRoleManager() {
|
|||||||
.rpc('get_users_with_emails');
|
.rpc('get_users_with_emails');
|
||||||
|
|
||||||
if (rpcError) {
|
if (rpcError) {
|
||||||
logger.warn('Failed to fetch users with emails, using basic profiles', { error: getErrorMessage(rpcError) });
|
// Fall back to basic profiles
|
||||||
const { data: basicProfiles, error: profilesError } = await supabase
|
const { data: basicProfiles, error: profilesError } = await supabase
|
||||||
.from('profiles')
|
.from('profiles')
|
||||||
.select('user_id, username, display_name')
|
.select('user_id, username, display_name')
|
||||||
@@ -149,7 +148,11 @@ export function UserRoleManager() {
|
|||||||
const filteredResults = (data || []).filter(profile => !existingUserIds.includes(profile.user_id));
|
const filteredResults = (data || []).filter(profile => !existingUserIds.includes(profile.user_id));
|
||||||
setSearchResults(filteredResults);
|
setSearchResults(filteredResults);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
logger.error('User search failed', { error: getErrorMessage(error) });
|
handleNonCriticalError(error, {
|
||||||
|
action: 'Search Users',
|
||||||
|
userId: user?.id,
|
||||||
|
metadata: { search }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { invokeWithTracking } from "@/lib/edgeFunctionTracking";
|
import { invokeWithTracking } from "@/lib/edgeFunctionTracking";
|
||||||
import { logger } from "@/lib/logger";
|
import { handleError, getErrorMessage } from "@/lib/errorHandler";
|
||||||
import { getErrorMessage } from "@/lib/errorHandler";
|
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
@@ -171,11 +170,10 @@ export function UppyPhotoSubmissionUpload({
|
|||||||
);
|
);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
const errorMsg = getErrorMessage(error);
|
const errorMsg = getErrorMessage(error);
|
||||||
logger.error("Photo submission upload failed", {
|
handleError(error, {
|
||||||
photoTitle: photo.title,
|
action: 'Upload Photo Submission',
|
||||||
photoOrder: photo.order,
|
userId: user.id,
|
||||||
fileName: photo.file?.name,
|
metadata: { photoTitle: photo.title, photoOrder: photo.order, fileName: photo.file?.name }
|
||||||
error: errorMsg,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
setPhotos((prev) => prev.map((p) => (p === photo ? { ...p, uploadStatus: "failed" as const } : p)));
|
setPhotos((prev) => prev.map((p) => (p === photo ? { ...p, uploadStatus: "failed" as const } : p)));
|
||||||
@@ -258,12 +256,10 @@ export function UppyPhotoSubmissionUpload({
|
|||||||
onSubmissionComplete?.();
|
onSubmissionComplete?.();
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
const errorMsg = getErrorMessage(error);
|
const errorMsg = getErrorMessage(error);
|
||||||
logger.error("Photo submission failed", {
|
handleError(error, {
|
||||||
entityType,
|
action: 'Submit Photo Submission',
|
||||||
entityId,
|
|
||||||
photoCount: photos.length,
|
|
||||||
userId: user?.id,
|
userId: user?.id,
|
||||||
error: errorMsg,
|
metadata: { entityType, entityId, photoCount: photos.length }
|
||||||
});
|
});
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React, { useRef, useState } from 'react';
|
import React, { useRef, useState } from 'react';
|
||||||
import { supabase } from '@/lib/supabaseClient';
|
import { supabase } from '@/lib/supabaseClient';
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
import { invokeWithTracking } from '@/lib/edgeFunctionTracking';
|
import { invokeWithTracking } from '@/lib/edgeFunctionTracking';
|
||||||
import { logger } from '@/lib/logger';
|
import { handleError, getErrorMessage } from '@/lib/errorHandler';
|
||||||
import { getErrorMessage } from '@/lib/errorHandler';
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Upload, X, Eye, Loader2, CheckCircle } from 'lucide-react';
|
import { Upload, X, Eye, Loader2, CheckCircle } from 'lucide-react';
|
||||||
@@ -204,11 +204,9 @@ export function UppyPhotoUpload({
|
|||||||
setUploadProgress(Math.round(((i + 1) / totalFiles) * 100));
|
setUploadProgress(Math.round(((i + 1) / totalFiles) * 100));
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
const errorMsg = getErrorMessage(error);
|
const errorMsg = getErrorMessage(error);
|
||||||
logger.error('File upload failed', {
|
handleError(error, {
|
||||||
fileName: file.name,
|
action: 'Upload Photo File',
|
||||||
fileSize: file.size,
|
metadata: { fileName: file.name, fileSize: file.size, fileType: file.type }
|
||||||
fileType: file.type,
|
|
||||||
error: errorMsg
|
|
||||||
});
|
});
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export function MFAStepUpProvider({ children }: MFAStepUpProviderProps) {
|
|||||||
setModalOpen(false);
|
setModalOpen(false);
|
||||||
|
|
||||||
if (!pendingOperation || !pendingResolve || !pendingReject) {
|
if (!pendingOperation || !pendingResolve || !pendingReject) {
|
||||||
logger.error('No pending operation to retry after MFA success');
|
// Invalid state - missing pending operations
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ export function MFAStepUpProvider({ children }: MFAStepUpProviderProps) {
|
|||||||
const result = await pendingOperation();
|
const result = await pendingOperation();
|
||||||
pendingResolve(result);
|
pendingResolve(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[MFAStepUp] Operation failed after AAL2 upgrade', { error });
|
// Operation failed after AAL2 - will be handled by caller
|
||||||
pendingReject(error);
|
pendingReject(error);
|
||||||
} finally {
|
} finally {
|
||||||
// Clean up
|
// Clean up
|
||||||
@@ -105,7 +105,7 @@ export function MFAStepUpProvider({ children }: MFAStepUpProviderProps) {
|
|||||||
const factors = await getEnrolledFactors();
|
const factors = await getEnrolledFactors();
|
||||||
|
|
||||||
if (factors.length === 0) {
|
if (factors.length === 0) {
|
||||||
logger.error('[MFAStepUp] No enrolled MFA factors found');
|
// No MFA set up
|
||||||
throw new Error('MFA is not set up for your account');
|
throw new Error('MFA is not set up for your account');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -167,12 +167,12 @@ export function useEntityCache() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
logger.error(`Unknown entity type: ${type}`);
|
// Unknown entity type - skip
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
logger.error(`Error fetching ${type}:`, { error: getErrorMessage(error as Error) });
|
// Silent - cache miss is acceptable
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +187,7 @@ export function useEntityCache() {
|
|||||||
|
|
||||||
return (data as EntityTypeMap[T][]) || [];
|
return (data as EntityTypeMap[T][]) || [];
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
logger.error(`Failed to bulk fetch ${type}:`, { error: getErrorMessage(error) });
|
// Silent - cache operations are non-critical
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}, [getCached, setCached, getUncachedIds]);
|
}, [getCached, setCached, getUncachedIds]);
|
||||||
|
|||||||
@@ -280,7 +280,7 @@ export function useModerationActions(config: ModerationActionsConfig): Moderatio
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (auditError) {
|
} catch (auditError) {
|
||||||
logger.error('Failed to log review moderation audit', { error: auditError });
|
// Silent - audit logging is non-critical
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,7 +327,7 @@ export function useModerationActions(config: ModerationActionsConfig): Moderatio
|
|||||||
queryClient.setQueryData(['moderation-queue'], context.previousData);
|
queryClient.setQueryData(['moderation-queue'], context.previousData);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.error('❌ Error performing action:', { error: getErrorMessage(error) });
|
// Error already logged by mutation, just show toast
|
||||||
toast({
|
toast({
|
||||||
title: 'Action Failed',
|
title: 'Action Failed',
|
||||||
description: getErrorMessage(error) || `Failed to ${variables.action} content`,
|
description: getErrorMessage(error) || `Failed to ${variables.action} content`,
|
||||||
@@ -395,7 +395,7 @@ export function useModerationActions(config: ModerationActionsConfig): Moderatio
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (auditError) {
|
} catch (auditError) {
|
||||||
logger.error('Failed to log submission deletion audit', { error: auditError });
|
// Silent - audit logging is non-critical
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -406,7 +406,7 @@ export function useModerationActions(config: ModerationActionsConfig): Moderatio
|
|||||||
|
|
||||||
logger.log(`✅ Submission ${item.id} deleted`);
|
logger.log(`✅ Submission ${item.id} deleted`);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
logger.error('❌ Error deleting submission:', { error: getErrorMessage(error) });
|
// Error already handled, just show toast
|
||||||
toast({
|
toast({
|
||||||
title: 'Error',
|
title: 'Error',
|
||||||
description: getErrorMessage(error),
|
description: getErrorMessage(error),
|
||||||
@@ -444,7 +444,7 @@ export function useModerationActions(config: ModerationActionsConfig): Moderatio
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (auditError) {
|
} catch (auditError) {
|
||||||
logger.error('Failed to log submission reset audit', { error: auditError });
|
// Silent - audit logging is non-critical
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,7 +455,7 @@ export function useModerationActions(config: ModerationActionsConfig): Moderatio
|
|||||||
|
|
||||||
logger.log(`✅ Submission ${item.id} reset to pending`);
|
logger.log(`✅ Submission ${item.id} reset to pending`);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
logger.error('❌ Error resetting submission:', { error: getErrorMessage(error) });
|
// Error already handled, just show toast
|
||||||
toast({
|
toast({
|
||||||
title: 'Reset Failed',
|
title: 'Reset Failed',
|
||||||
description: getErrorMessage(error),
|
description: getErrorMessage(error),
|
||||||
@@ -516,7 +516,7 @@ export function useModerationActions(config: ModerationActionsConfig): Moderatio
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (auditError) {
|
} catch (auditError) {
|
||||||
logger.error('Failed to log submission retry audit', { error: auditError });
|
// Silent - audit logging is non-critical
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -527,7 +527,7 @@ export function useModerationActions(config: ModerationActionsConfig): Moderatio
|
|||||||
|
|
||||||
logger.log(`✅ Retried ${failedItems.length} failed items for ${item.id}`);
|
logger.log(`✅ Retried ${failedItems.length} failed items for ${item.id}`);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
logger.error('❌ Error retrying items:', { error: getErrorMessage(error) });
|
// Error already handled, just show toast
|
||||||
toast({
|
toast({
|
||||||
title: 'Retry Failed',
|
title: 'Retry Failed',
|
||||||
description: getErrorMessage(error) || 'Failed to retry items',
|
description: getErrorMessage(error) || 'Failed to retry items',
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ export function useModerationFilters(
|
|||||||
return JSON.parse(saved);
|
return JSON.parse(saved);
|
||||||
}
|
}
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
logger.warn('Failed to load persisted filters', { error, storageKey });
|
// Silent - localStorage failures are non-critical
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -153,7 +153,7 @@ export function useModerationFilters(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
logger.warn('Failed to load persisted sort', { error, storageKey });
|
// Silent - localStorage failures are non-critical
|
||||||
}
|
}
|
||||||
|
|
||||||
return initialSortConfig;
|
return initialSortConfig;
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig):
|
|||||||
// Show error toast when query fails
|
// Show error toast when query fails
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (queueQuery.error) {
|
if (queueQuery.error) {
|
||||||
logger.error('❌ Queue query error:', { error: getErrorMessage(queueQuery.error) });
|
// Error already captured by TanStack Query
|
||||||
toast({
|
toast({
|
||||||
variant: 'destructive',
|
variant: 'destructive',
|
||||||
title: 'Failed to Load Queue',
|
title: 'Failed to Load Queue',
|
||||||
@@ -332,7 +332,7 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig):
|
|||||||
queue.refreshStats();
|
queue.refreshStats();
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
const errorMsg = getErrorMessage(error);
|
const errorMsg = getErrorMessage(error);
|
||||||
logger.error("Error deleting submission:", { error: errorMsg, itemId: item.id });
|
// Silent - operation handled optimistically
|
||||||
|
|
||||||
setItems((prev) => {
|
setItems((prev) => {
|
||||||
if (prev.some((i) => i.id === item.id)) return prev;
|
if (prev.some((i) => i.id === item.id)) return prev;
|
||||||
@@ -373,7 +373,7 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig):
|
|||||||
setItems((prev) => prev.filter((i) => i.id !== item.id));
|
setItems((prev) => prev.filter((i) => i.id !== item.id));
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
const errorMsg = getErrorMessage(error);
|
const errorMsg = getErrorMessage(error);
|
||||||
logger.error("Error resetting submission:", { error: errorMsg, itemId: item.id });
|
// Silent - operation handled optimistically
|
||||||
toast({
|
toast({
|
||||||
title: "Reset Failed",
|
title: "Reset Failed",
|
||||||
description: errorMsg,
|
description: errorMsg,
|
||||||
@@ -441,7 +441,7 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig):
|
|||||||
queue.refreshStats();
|
queue.refreshStats();
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
const errorMsg = getErrorMessage(error);
|
const errorMsg = getErrorMessage(error);
|
||||||
logger.error("Error retrying failed items:", { error: errorMsg, itemId: item.id });
|
// Silent - operation handled optimistically
|
||||||
toast({
|
toast({
|
||||||
title: "Retry Failed",
|
title: "Retry Failed",
|
||||||
description: errorMsg,
|
description: errorMsg,
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ export function usePagination(config: PaginationConfig = {}): PaginationState {
|
|||||||
return JSON.parse(saved);
|
return JSON.parse(saved);
|
||||||
}
|
}
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
logger.warn('Failed to load pagination state', { error, storageKey });
|
// Silent - localStorage failures are non-critical
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ export function useProfileCache() {
|
|||||||
.in('user_id', uncachedIds);
|
.in('user_id', uncachedIds);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
logger.error('Error fetching profiles:', { error: getErrorMessage(error) });
|
// Silent - cache miss is acceptable
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +129,7 @@ export function useProfileCache() {
|
|||||||
avatar_url: profile.avatar_url || undefined
|
avatar_url: profile.avatar_url || undefined
|
||||||
}));
|
}));
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
logger.error('Failed to bulk fetch profiles:', { error: getErrorMessage(error) });
|
// Silent - cache operations are non-critical
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}, [getCached, setCached, getUncachedIds]);
|
}, [getCached, setCached, getUncachedIds]);
|
||||||
|
|||||||
@@ -179,14 +179,14 @@ export function useQueueQuery(config: UseQueueQueryConfig): UseQueueQueryReturn
|
|||||||
|
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
const specificMessage = getSpecificErrorMessage(result.error);
|
const specificMessage = getSpecificErrorMessage(result.error);
|
||||||
logger.error('❌ [TanStack Query] Error:', { error: specificMessage });
|
// Error already captured in context
|
||||||
throw new Error(specificMessage);
|
throw new Error(specificMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate data shape before returning
|
// Validate data shape before returning
|
||||||
const validation = validateModerationItems(result.submissions);
|
const validation = validateModerationItems(result.submissions);
|
||||||
if (!validation.success) {
|
if (!validation.success) {
|
||||||
logger.error('❌ Invalid data shape', { error: validation.error });
|
// Invalid data shape
|
||||||
throw new Error(validation.error || 'Invalid data format');
|
throw new Error(validation.error || 'Invalid data format');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ export function useRealtimeSubscriptions(
|
|||||||
.single();
|
.single();
|
||||||
|
|
||||||
if (error || !submission) {
|
if (error || !submission) {
|
||||||
logger.error('Error fetching submission details:', { error: getErrorMessage(error) });
|
// Silent - will retry on next attempt
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,7 +321,7 @@ export function useRealtimeSubscriptions(
|
|||||||
|
|
||||||
onNewItem(fullItem);
|
onNewItem(fullItem);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
logger.error('Error building new item notification:', { error: getErrorMessage(error) });
|
// Silent - notifications are non-critical
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
filters,
|
filters,
|
||||||
|
|||||||
Reference in New Issue
Block a user