diff --git a/src/components/admin/LocationSearch.tsx b/src/components/admin/LocationSearch.tsx index 7185e2aa..95c90c00 100644 --- a/src/components/admin/LocationSearch.tsx +++ b/src/components/admin/LocationSearch.tsx @@ -6,6 +6,7 @@ import { Button } from '@/components/ui/button'; import { Card } from '@/components/ui/card'; import { MapPin, Loader2, X } from 'lucide-react'; import { ParkLocationMap } from '@/components/maps/ParkLocationMap'; +import { logger } from '@/lib/logger'; interface LocationResult { place_id: number; diff --git a/src/components/moderation/ConflictResolutionDialog.tsx b/src/components/moderation/ConflictResolutionDialog.tsx index 130184be..296bbb2b 100644 --- a/src/components/moderation/ConflictResolutionDialog.tsx +++ b/src/components/moderation/ConflictResolutionDialog.tsx @@ -6,9 +6,8 @@ import { Label } from '@/components/ui/label'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { AlertCircle } from 'lucide-react'; import { type DependencyConflict, type SubmissionItemWithDeps } from '@/lib/submissionItemsService'; -import { useToast } from '@/hooks/use-toast'; import { useAuth } from '@/hooks/useAuth'; -import { getErrorMessage } from '@/lib/errorHandler'; +import { handleError, handleSuccess } from '@/lib/errorHandler'; interface ConflictResolutionDialogProps { open: boolean; @@ -26,7 +25,6 @@ export function ConflictResolutionDialog({ onResolve, }: ConflictResolutionDialogProps) { const [resolutions, setResolutions] = useState>({}); - const { toast } = useToast(); const { user } = useAuth(); const handleResolutionChange = (itemId: string, action: string) => { @@ -39,10 +37,9 @@ export function ConflictResolutionDialog({ const handleApply = async () => { if (!user?.id) { - toast({ - title: 'Authentication Required', - description: 'You must be logged in to resolve conflicts', - variant: 'destructive', + handleError(new Error('Authentication required'), { + action: 'Resolve Conflicts', + metadata: { conflictCount: conflicts.length } }); return; } @@ -53,27 +50,22 @@ export function ConflictResolutionDialog({ const result = await resolveConflicts(conflicts, resolutions, items, user.id); if (!result.success) { - toast({ - title: 'Resolution Failed', - description: result.error || 'Failed to resolve conflicts', - variant: 'destructive', + handleError(new Error(result.error || 'Failed to resolve conflicts'), { + action: 'Resolve Conflicts', + userId: user.id, + metadata: { conflictCount: conflicts.length } }); return; } - toast({ - title: 'Conflicts Resolved', - description: 'All conflicts have been resolved successfully', - }); - + handleSuccess('Conflicts Resolved', 'All conflicts have been resolved successfully'); onResolve(); onOpenChange(false); - } catch (error) { - const errorMsg = getErrorMessage(error); - toast({ - title: 'Error', - description: errorMsg, - variant: 'destructive', + } catch (error: unknown) { + handleError(error, { + action: 'Resolve Conflicts', + userId: user.id, + metadata: { conflictCount: conflicts.length } }); } }; diff --git a/src/components/moderation/EntityEditPreview.tsx b/src/components/moderation/EntityEditPreview.tsx index 04aae2f1..f6e0b79e 100644 --- a/src/components/moderation/EntityEditPreview.tsx +++ b/src/components/moderation/EntityEditPreview.tsx @@ -4,6 +4,7 @@ import { Card, CardContent } from '@/components/ui/card'; import { supabase } from '@/integrations/supabase/client'; import { Image as ImageIcon } from 'lucide-react'; import { PhotoModal } from './PhotoModal'; +import { handleError } from '@/lib/errorHandler'; interface EntityEditPreviewProps { submissionId: string; @@ -162,8 +163,11 @@ export const EntityEditPreview = ({ submissionId, entityType, entityName }: Enti setChangedFields(changed); } - } catch (error) { - console.error('EntityEditPreview.fetchSubmissionItems: Error fetching submission items:', error); + } catch (error: unknown) { + handleError(error, { + action: 'Load Submission Preview', + metadata: { submissionId, entityType } + }); } finally { setLoading(false); } diff --git a/src/components/moderation/ProfileManager.tsx b/src/components/moderation/ProfileManager.tsx index 8ab41c4d..eaef5a9a 100644 --- a/src/components/moderation/ProfileManager.tsx +++ b/src/components/moderation/ProfileManager.tsx @@ -10,7 +10,7 @@ import { Badge } from '@/components/ui/badge'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from '@/components/ui/alert-dialog'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; -import { useToast } from '@/hooks/use-toast'; +import { handleError, handleSuccess } from '@/lib/errorHandler'; interface UserProfile { id: string; @@ -26,7 +26,6 @@ interface UserProfile { export function ProfileManager() { const { user } = useAuth(); const { permissions, loading: roleLoading } = useUserRole(); - const { toast } = useToast(); const [profiles, setProfiles] = useState([]); const [loading, setLoading] = useState(true); @@ -69,12 +68,10 @@ export function ProfileManager() { ); setProfiles(profilesWithRoles); - } catch (error) { - console.error('Error fetching profiles:', error); - toast({ - title: "Error", - description: "Failed to fetch user profiles.", - variant: "destructive", + } catch (error: unknown) { + handleError(error, { + action: 'Load User Profiles', + userId: user?.id }); } finally { setLoading(false); @@ -105,19 +102,15 @@ export function ProfileManager() { if (logError) console.error('Error logging action:', logError); - toast({ - title: "Success", - description: `User ${ban ? 'banned' : 'unbanned'} successfully.`, - }); + handleSuccess('Success', `User ${ban ? 'banned' : 'unbanned'} successfully.`); // Refresh profiles fetchProfiles(); - } catch (error) { - console.error('Error updating user ban status:', error); - toast({ - title: "Error", - description: `Failed to ${ban ? 'ban' : 'unban'} user.`, - variant: "destructive", + } catch (error: unknown) { + handleError(error, { + action: `${ban ? 'Ban' : 'Unban'} User`, + userId: user?.id, + metadata: { targetUserId, ban } }); } finally { setActionLoading(null); @@ -144,19 +137,19 @@ export function ProfileManager() { } else { // Check permissions before allowing role assignment if (newRole === 'admin' && !permissions.can_manage_admin_roles) { - toast({ - title: "Access Denied", - description: "You don't have permission to assign admin roles.", - variant: "destructive", + handleError(new Error('Insufficient permissions'), { + action: 'Assign Admin Role', + userId: user?.id, + metadata: { targetUserId, attemptedRole: newRole } }); return; } if (newRole === 'superuser') { - toast({ - title: "Access Denied", - description: "Superuser roles can only be assigned directly in the database.", - variant: "destructive", + handleError(new Error('Cannot assign superuser via UI'), { + action: 'Assign Superuser Role', + userId: user?.id, + metadata: { targetUserId } }); return; } @@ -184,19 +177,15 @@ export function ProfileManager() { if (logError) console.error('Error logging action:', logError); - toast({ - title: "Success", - description: `User role updated successfully.`, - }); + handleSuccess('Success', 'User role updated successfully.'); // Refresh profiles fetchProfiles(); - } catch (error) { - console.error('Error updating user role:', error); - toast({ - title: "Error", - description: "Failed to update user role.", - variant: "destructive", + } catch (error: unknown) { + handleError(error, { + action: 'Update User Role', + userId: user?.id, + metadata: { targetUserId, newRole, previousRoles: currentRoles } }); } finally { setActionLoading(null); diff --git a/src/components/moderation/QueueItem.tsx b/src/components/moderation/QueueItem.tsx index 114ec3a3..23bb2766 100644 --- a/src/components/moderation/QueueItem.tsx +++ b/src/components/moderation/QueueItem.tsx @@ -20,6 +20,7 @@ import { ValidationSummary } from './ValidationSummary'; import type { ValidationResult } from '@/lib/entityValidationSchemas'; import type { LockStatus } from '@/lib/moderation/lockHelpers'; import type { ModerationItem } from '@/types/moderation'; +import { handleError } from '@/lib/errorHandler'; interface QueueItemProps { item: ModerationItem; @@ -105,8 +106,11 @@ export const QueueItem = memo(({ try { await onClaimSubmission(item.id); // On success, component will re-render with new lock state - } catch (error) { - console.error('Failed to claim submission:', error); + } catch (error: unknown) { + handleError(error, { + action: 'Claim Submission', + metadata: { submissionId: item.id } + }); } finally { // Always reset claiming state, even on success setIsClaiming(false); diff --git a/src/components/moderation/ReassignDialog.tsx b/src/components/moderation/ReassignDialog.tsx index cb0c2548..68998e87 100644 --- a/src/components/moderation/ReassignDialog.tsx +++ b/src/components/moderation/ReassignDialog.tsx @@ -18,8 +18,8 @@ import { SelectValue, } from '@/components/ui/select'; import { supabase } from '@/integrations/supabase/client'; -import { useToast } from '@/hooks/use-toast'; -import { getErrorMessage } from '@/lib/errorHandler'; +import { handleError } from '@/lib/errorHandler'; +import { logger } from '@/lib/logger'; interface Moderator { user_id: string; @@ -45,7 +45,6 @@ export function ReassignDialog({ const [moderators, setModerators] = useState([]); const [isSubmitting, setIsSubmitting] = useState(false); const [loading, setLoading] = useState(true); - const { toast } = useToast(); useEffect(() => { if (open) { @@ -88,13 +87,10 @@ export function ReassignDialog({ }); setModerators(moderatorsList); - } catch (error) { - const errorMsg = getErrorMessage(error); - console.error('Error fetching moderators:', errorMsg); - toast({ - title: 'Error', - description: 'Failed to load moderators list', - variant: 'destructive', + } catch (error: unknown) { + handleError(error, { + action: 'Load Moderators List', + metadata: { submissionType } }); } finally { setLoading(false); diff --git a/src/components/moderation/RecentActivity.tsx b/src/components/moderation/RecentActivity.tsx index e2e69fa3..b006304e 100644 --- a/src/components/moderation/RecentActivity.tsx +++ b/src/components/moderation/RecentActivity.tsx @@ -1,8 +1,7 @@ import { useState, useEffect, forwardRef, useImperativeHandle } from 'react'; import { supabase } from '@/integrations/supabase/client'; import { useAuth } from '@/hooks/useAuth'; -import { useToast } from '@/hooks/use-toast'; -import { getErrorMessage } from '@/lib/errorHandler'; +import { handleError } from '@/lib/errorHandler'; import { ActivityCard } from './ActivityCard'; import { Skeleton } from '@/components/ui/skeleton'; import { Activity as ActivityIcon } from 'lucide-react'; @@ -33,7 +32,6 @@ export const RecentActivity = forwardRef((props, ref) => { const [loading, setLoading] = useState(true); const [isSilentRefresh, setIsSilentRefresh] = useState(false); const { user } = useAuth(); - const { toast } = useToast(); const { getAutoRefreshStrategy } = useAdminSettings(); const refreshStrategy = getAutoRefreshStrategy(); @@ -151,13 +149,10 @@ export const RecentActivity = forwardRef((props, ref) => { // Full replacement for non-silent refreshes or 'replace' strategy setActivities(recentActivities); } - } catch (error) { - const errorMsg = getErrorMessage(error); - console.error('Error fetching recent activity:', errorMsg); - toast({ - title: "Error", - description: "Failed to load recent activity", - variant: "destructive", + } catch (error: unknown) { + handleError(error, { + action: 'Load Recent Activity', + userId: user?.id }); } finally { if (!silent) { diff --git a/src/components/moderation/ReportsQueue.tsx b/src/components/moderation/ReportsQueue.tsx index e3109e1c..330d5723 100644 --- a/src/components/moderation/ReportsQueue.tsx +++ b/src/components/moderation/ReportsQueue.tsx @@ -16,12 +16,12 @@ import { PaginationPrevious, } from '@/components/ui/pagination'; import { supabase } from '@/integrations/supabase/client'; -import { useToast } from '@/hooks/use-toast'; import { format } from 'date-fns'; import { useAdminSettings } from '@/hooks/useAdminSettings'; import { useAuth } from '@/hooks/useAuth'; import { useIsMobile } from '@/hooks/use-mobile'; import { smartMergeArray } from '@/lib/smartStateUpdate'; +import { handleError, handleSuccess } from '@/lib/errorHandler'; // Type-safe reported content interfaces interface ReportedReview { @@ -112,7 +112,6 @@ export const ReportsQueue = forwardRef((props, ref) => { const [isInitialLoad, setIsInitialLoad] = useState(true); const [actionLoading, setActionLoading] = useState(null); const [newReportsCount, setNewReportsCount] = useState(0); - const { toast } = useToast(); const { user } = useAuth(); // Pagination state @@ -297,16 +296,15 @@ export const ReportsQueue = forwardRef((props, ref) => { } } } else { - // Full replacement for non-silent refreshes or 'replace' strategy + // Full replacement for non-silent refreshes or 'replace' strategy setReports(reportsWithContent); setNewReportsCount(0); } - } catch (error) { - console.error('Error fetching reports:', error); - toast({ - title: "Error", - description: "Failed to load reports queue", - variant: "destructive", + } catch (error: unknown) { + handleError(error, { + action: 'Load Reports', + userId: user?.id, + metadata: { currentPage, pageSize } }); } finally { // Only clear loading if it was set @@ -352,10 +350,7 @@ export const ReportsQueue = forwardRef((props, ref) => { if (error) throw error; - toast({ - title: `Report ${action}`, - description: `The report has been marked as ${action}`, - }); + handleSuccess(`Report ${action}`, `The report has been marked as ${action}`); // Remove report from queue setReports(prev => { @@ -366,12 +361,11 @@ export const ReportsQueue = forwardRef((props, ref) => { } return newReports; }); - } catch (error) { - console.error('Error updating report:', error); - toast({ - title: "Error", - description: `Failed to ${action} report`, - variant: "destructive", + } catch (error: unknown) { + handleError(error, { + action: `${action === 'reviewed' ? 'Resolve' : 'Dismiss'} Report`, + userId: user?.id, + metadata: { reportId, action } }); } finally { setActionLoading(null); diff --git a/src/components/moderation/UserRoleManager.tsx b/src/components/moderation/UserRoleManager.tsx index 248e52de..e6731d41 100644 --- a/src/components/moderation/UserRoleManager.tsx +++ b/src/components/moderation/UserRoleManager.tsx @@ -9,7 +9,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@ import { supabase } from '@/integrations/supabase/client'; import { useAuth } from '@/hooks/useAuth'; import { useUserRole } from '@/hooks/useUserRole'; -import { useToast } from '@/hooks/use-toast'; +import { handleError, handleSuccess } from '@/lib/errorHandler'; // Type-safe role definitions const VALID_ROLES = ['admin', 'moderator', 'user'] as const; @@ -66,9 +66,6 @@ export function UserRoleManager() { isSuperuser, permissions } = useUserRole(); - const { - toast - } = useToast(); const fetchUserRoles = async () => { try { const { @@ -99,12 +96,10 @@ export function UserRoleManager() { profiles: profileMap.get(role.user_id) })); setUserRoles(userRolesWithProfiles); - } catch (error) { - console.error('Error fetching user roles:', error); - toast({ - title: "Error", - description: "Failed to load user roles", - variant: "destructive" + } catch (error: unknown) { + handleError(error, { + action: 'Load User Roles', + userId: user?.id }); } finally { setLoading(false); @@ -144,10 +139,10 @@ export function UserRoleManager() { // Double-check role validity before database operation if (!isValidRole(role)) { - toast({ - title: "Invalid Role", - description: "The selected role is not valid", - variant: "destructive" + handleError(new Error('Invalid role'), { + action: 'Grant Role', + userId: user?.id, + metadata: { targetUserId: userId, attemptedRole: role } }); return; } @@ -164,20 +159,16 @@ export function UserRoleManager() { if (error) throw error; - toast({ - title: "Role Granted", - description: `User has been granted ${getRoleLabel(role)} role` - }); + handleSuccess('Role Granted', `User has been granted ${getRoleLabel(role)} role`); setNewUserSearch(''); setNewRole(''); setSearchResults([]); fetchUserRoles(); - } catch (error) { - console.error('Error granting role:', error); - toast({ - title: "Error", - description: "Failed to grant role", - variant: "destructive" + } catch (error: unknown) { + handleError(error, { + action: 'Grant Role', + userId: user?.id, + metadata: { targetUserId: userId, role } }); } finally { setActionLoading(null); @@ -191,17 +182,13 @@ export function UserRoleManager() { error } = await supabase.from('user_roles').delete().eq('id', roleId); if (error) throw error; - toast({ - title: "Role Revoked", - description: "User role has been revoked" - }); + handleSuccess('Role Revoked', 'User role has been revoked'); fetchUserRoles(); - } catch (error) { - console.error('Error revoking role:', error); - toast({ - title: "Error", - description: "Failed to revoke role", - variant: "destructive" + } catch (error: unknown) { + handleError(error, { + action: 'Revoke Role', + userId: user?.id, + metadata: { roleId } }); } finally { setActionLoading(null); @@ -273,10 +260,10 @@ export function UserRoleManager() { } else if (selectedUser && newRole) { // This should never happen due to Select component constraints, // but provides safety in case of UI bugs - toast({ - title: "Invalid Role", - description: "Please select a valid role", - variant: "destructive" + handleError(new Error('Invalid role selected'), { + action: 'Grant Role', + userId: user?.id, + metadata: { selectedUser: selectedUser?.user_id, newRole } }); } }} disabled={!newRole || !isValidRole(newRole) || !searchResults.find(p => (p.display_name || p.username) === newUserSearch) || actionLoading === 'grant'} className="w-full md:w-auto"> diff --git a/src/components/moderation/ValidationSummary.tsx b/src/components/moderation/ValidationSummary.tsx index 0a30b0e8..6caa3058 100644 --- a/src/components/moderation/ValidationSummary.tsx +++ b/src/components/moderation/ValidationSummary.tsx @@ -5,6 +5,7 @@ import { Button } from '@/components/ui/button'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; import { validateEntityData, ValidationResult } from '@/lib/entityValidationSchemas'; +import { logger } from '@/lib/logger'; interface ValidationSummaryProps { item: { @@ -76,8 +77,12 @@ export function ValidationSummary({ item, onValidationChange, compact = false, v setValidationResult(result); onValidationChange?.(result); - } catch (error) { - console.error('Validation error:', error); + } catch (error: unknown) { + logger.error('Entity validation failed', { + action: 'validate_entity', + entityType: item.item_type, + error: error instanceof Error ? error.message : String(error) + }); setValidationResult({ isValid: false, blockingErrors: [{ field: 'validation', message: 'Failed to validate', severity: 'blocking' }],