feat: Execute production readiness plan

This commit is contained in:
gpt-engineer-app[bot]
2025-10-20 13:41:54 +00:00
parent 368b97da04
commit 7f425ecb94
10 changed files with 108 additions and 141 deletions

View File

@@ -6,6 +6,7 @@ import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card'; import { Card } from '@/components/ui/card';
import { MapPin, Loader2, X } from 'lucide-react'; import { MapPin, Loader2, X } from 'lucide-react';
import { ParkLocationMap } from '@/components/maps/ParkLocationMap'; import { ParkLocationMap } from '@/components/maps/ParkLocationMap';
import { logger } from '@/lib/logger';
interface LocationResult { interface LocationResult {
place_id: number; place_id: number;

View File

@@ -6,9 +6,8 @@ import { Label } from '@/components/ui/label';
import { Alert, AlertDescription } from '@/components/ui/alert'; import { Alert, AlertDescription } from '@/components/ui/alert';
import { AlertCircle } from 'lucide-react'; import { AlertCircle } from 'lucide-react';
import { type DependencyConflict, type SubmissionItemWithDeps } from '@/lib/submissionItemsService'; import { type DependencyConflict, type SubmissionItemWithDeps } from '@/lib/submissionItemsService';
import { useToast } from '@/hooks/use-toast';
import { useAuth } from '@/hooks/useAuth'; import { useAuth } from '@/hooks/useAuth';
import { getErrorMessage } from '@/lib/errorHandler'; import { handleError, handleSuccess } from '@/lib/errorHandler';
interface ConflictResolutionDialogProps { interface ConflictResolutionDialogProps {
open: boolean; open: boolean;
@@ -26,7 +25,6 @@ export function ConflictResolutionDialog({
onResolve, onResolve,
}: ConflictResolutionDialogProps) { }: ConflictResolutionDialogProps) {
const [resolutions, setResolutions] = useState<Record<string, string>>({}); const [resolutions, setResolutions] = useState<Record<string, string>>({});
const { toast } = useToast();
const { user } = useAuth(); const { user } = useAuth();
const handleResolutionChange = (itemId: string, action: string) => { const handleResolutionChange = (itemId: string, action: string) => {
@@ -39,10 +37,9 @@ export function ConflictResolutionDialog({
const handleApply = async () => { const handleApply = async () => {
if (!user?.id) { if (!user?.id) {
toast({ handleError(new Error('Authentication required'), {
title: 'Authentication Required', action: 'Resolve Conflicts',
description: 'You must be logged in to resolve conflicts', metadata: { conflictCount: conflicts.length }
variant: 'destructive',
}); });
return; return;
} }
@@ -53,27 +50,22 @@ export function ConflictResolutionDialog({
const result = await resolveConflicts(conflicts, resolutions, items, user.id); const result = await resolveConflicts(conflicts, resolutions, items, user.id);
if (!result.success) { if (!result.success) {
toast({ handleError(new Error(result.error || 'Failed to resolve conflicts'), {
title: 'Resolution Failed', action: 'Resolve Conflicts',
description: result.error || 'Failed to resolve conflicts', userId: user.id,
variant: 'destructive', metadata: { conflictCount: conflicts.length }
}); });
return; return;
} }
toast({ handleSuccess('Conflicts Resolved', 'All conflicts have been resolved successfully');
title: 'Conflicts Resolved',
description: 'All conflicts have been resolved successfully',
});
onResolve(); onResolve();
onOpenChange(false); onOpenChange(false);
} catch (error) { } catch (error: unknown) {
const errorMsg = getErrorMessage(error); handleError(error, {
toast({ action: 'Resolve Conflicts',
title: 'Error', userId: user.id,
description: errorMsg, metadata: { conflictCount: conflicts.length }
variant: 'destructive',
}); });
} }
}; };

View File

@@ -4,6 +4,7 @@ import { Card, CardContent } from '@/components/ui/card';
import { supabase } from '@/integrations/supabase/client'; import { supabase } from '@/integrations/supabase/client';
import { Image as ImageIcon } from 'lucide-react'; import { Image as ImageIcon } from 'lucide-react';
import { PhotoModal } from './PhotoModal'; import { PhotoModal } from './PhotoModal';
import { handleError } from '@/lib/errorHandler';
interface EntityEditPreviewProps { interface EntityEditPreviewProps {
submissionId: string; submissionId: string;
@@ -162,8 +163,11 @@ export const EntityEditPreview = ({ submissionId, entityType, entityName }: Enti
setChangedFields(changed); setChangedFields(changed);
} }
} catch (error) { } catch (error: unknown) {
console.error('EntityEditPreview.fetchSubmissionItems: Error fetching submission items:', error); handleError(error, {
action: 'Load Submission Preview',
metadata: { submissionId, entityType }
});
} finally { } finally {
setLoading(false); setLoading(false);
} }

View File

@@ -10,7 +10,7 @@ import { Badge } from '@/components/ui/badge';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; 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 { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from '@/components/ui/alert-dialog';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { useToast } from '@/hooks/use-toast'; import { handleError, handleSuccess } from '@/lib/errorHandler';
interface UserProfile { interface UserProfile {
id: string; id: string;
@@ -26,7 +26,6 @@ interface UserProfile {
export function ProfileManager() { export function ProfileManager() {
const { user } = useAuth(); const { user } = useAuth();
const { permissions, loading: roleLoading } = useUserRole(); const { permissions, loading: roleLoading } = useUserRole();
const { toast } = useToast();
const [profiles, setProfiles] = useState<UserProfile[]>([]); const [profiles, setProfiles] = useState<UserProfile[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
@@ -69,12 +68,10 @@ export function ProfileManager() {
); );
setProfiles(profilesWithRoles); setProfiles(profilesWithRoles);
} catch (error) { } catch (error: unknown) {
console.error('Error fetching profiles:', error); handleError(error, {
toast({ action: 'Load User Profiles',
title: "Error", userId: user?.id
description: "Failed to fetch user profiles.",
variant: "destructive",
}); });
} finally { } finally {
setLoading(false); setLoading(false);
@@ -105,19 +102,15 @@ export function ProfileManager() {
if (logError) console.error('Error logging action:', logError); if (logError) console.error('Error logging action:', logError);
toast({ handleSuccess('Success', `User ${ban ? 'banned' : 'unbanned'} successfully.`);
title: "Success",
description: `User ${ban ? 'banned' : 'unbanned'} successfully.`,
});
// Refresh profiles // Refresh profiles
fetchProfiles(); fetchProfiles();
} catch (error) { } catch (error: unknown) {
console.error('Error updating user ban status:', error); handleError(error, {
toast({ action: `${ban ? 'Ban' : 'Unban'} User`,
title: "Error", userId: user?.id,
description: `Failed to ${ban ? 'ban' : 'unban'} user.`, metadata: { targetUserId, ban }
variant: "destructive",
}); });
} finally { } finally {
setActionLoading(null); setActionLoading(null);
@@ -144,19 +137,19 @@ export function ProfileManager() {
} else { } else {
// Check permissions before allowing role assignment // Check permissions before allowing role assignment
if (newRole === 'admin' && !permissions.can_manage_admin_roles) { if (newRole === 'admin' && !permissions.can_manage_admin_roles) {
toast({ handleError(new Error('Insufficient permissions'), {
title: "Access Denied", action: 'Assign Admin Role',
description: "You don't have permission to assign admin roles.", userId: user?.id,
variant: "destructive", metadata: { targetUserId, attemptedRole: newRole }
}); });
return; return;
} }
if (newRole === 'superuser') { if (newRole === 'superuser') {
toast({ handleError(new Error('Cannot assign superuser via UI'), {
title: "Access Denied", action: 'Assign Superuser Role',
description: "Superuser roles can only be assigned directly in the database.", userId: user?.id,
variant: "destructive", metadata: { targetUserId }
}); });
return; return;
} }
@@ -184,19 +177,15 @@ export function ProfileManager() {
if (logError) console.error('Error logging action:', logError); if (logError) console.error('Error logging action:', logError);
toast({ handleSuccess('Success', 'User role updated successfully.');
title: "Success",
description: `User role updated successfully.`,
});
// Refresh profiles // Refresh profiles
fetchProfiles(); fetchProfiles();
} catch (error) { } catch (error: unknown) {
console.error('Error updating user role:', error); handleError(error, {
toast({ action: 'Update User Role',
title: "Error", userId: user?.id,
description: "Failed to update user role.", metadata: { targetUserId, newRole, previousRoles: currentRoles }
variant: "destructive",
}); });
} finally { } finally {
setActionLoading(null); setActionLoading(null);

View File

@@ -20,6 +20,7 @@ import { ValidationSummary } from './ValidationSummary';
import type { ValidationResult } from '@/lib/entityValidationSchemas'; import type { ValidationResult } from '@/lib/entityValidationSchemas';
import type { LockStatus } from '@/lib/moderation/lockHelpers'; import type { LockStatus } from '@/lib/moderation/lockHelpers';
import type { ModerationItem } from '@/types/moderation'; import type { ModerationItem } from '@/types/moderation';
import { handleError } from '@/lib/errorHandler';
interface QueueItemProps { interface QueueItemProps {
item: ModerationItem; item: ModerationItem;
@@ -105,8 +106,11 @@ export const QueueItem = memo(({
try { try {
await onClaimSubmission(item.id); await onClaimSubmission(item.id);
// On success, component will re-render with new lock state // On success, component will re-render with new lock state
} catch (error) { } catch (error: unknown) {
console.error('Failed to claim submission:', error); handleError(error, {
action: 'Claim Submission',
metadata: { submissionId: item.id }
});
} finally { } finally {
// Always reset claiming state, even on success // Always reset claiming state, even on success
setIsClaiming(false); setIsClaiming(false);

View File

@@ -18,8 +18,8 @@ import {
SelectValue, SelectValue,
} from '@/components/ui/select'; } from '@/components/ui/select';
import { supabase } from '@/integrations/supabase/client'; import { supabase } from '@/integrations/supabase/client';
import { useToast } from '@/hooks/use-toast'; import { handleError } from '@/lib/errorHandler';
import { getErrorMessage } from '@/lib/errorHandler'; import { logger } from '@/lib/logger';
interface Moderator { interface Moderator {
user_id: string; user_id: string;
@@ -45,7 +45,6 @@ export function ReassignDialog({
const [moderators, setModerators] = useState<Moderator[]>([]); const [moderators, setModerators] = useState<Moderator[]>([]);
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const { toast } = useToast();
useEffect(() => { useEffect(() => {
if (open) { if (open) {
@@ -88,13 +87,10 @@ export function ReassignDialog({
}); });
setModerators(moderatorsList); setModerators(moderatorsList);
} catch (error) { } catch (error: unknown) {
const errorMsg = getErrorMessage(error); handleError(error, {
console.error('Error fetching moderators:', errorMsg); action: 'Load Moderators List',
toast({ metadata: { submissionType }
title: 'Error',
description: 'Failed to load moderators list',
variant: 'destructive',
}); });
} finally { } finally {
setLoading(false); setLoading(false);

View File

@@ -1,8 +1,7 @@
import { useState, useEffect, forwardRef, useImperativeHandle } from 'react'; import { useState, useEffect, forwardRef, useImperativeHandle } from 'react';
import { supabase } from '@/integrations/supabase/client'; import { supabase } from '@/integrations/supabase/client';
import { useAuth } from '@/hooks/useAuth'; import { useAuth } from '@/hooks/useAuth';
import { useToast } from '@/hooks/use-toast'; import { handleError } from '@/lib/errorHandler';
import { getErrorMessage } from '@/lib/errorHandler';
import { ActivityCard } from './ActivityCard'; import { ActivityCard } from './ActivityCard';
import { Skeleton } from '@/components/ui/skeleton'; import { Skeleton } from '@/components/ui/skeleton';
import { Activity as ActivityIcon } from 'lucide-react'; import { Activity as ActivityIcon } from 'lucide-react';
@@ -33,7 +32,6 @@ export const RecentActivity = forwardRef<RecentActivityRef>((props, ref) => {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [isSilentRefresh, setIsSilentRefresh] = useState(false); const [isSilentRefresh, setIsSilentRefresh] = useState(false);
const { user } = useAuth(); const { user } = useAuth();
const { toast } = useToast();
const { getAutoRefreshStrategy } = useAdminSettings(); const { getAutoRefreshStrategy } = useAdminSettings();
const refreshStrategy = getAutoRefreshStrategy(); const refreshStrategy = getAutoRefreshStrategy();
@@ -151,13 +149,10 @@ export const RecentActivity = forwardRef<RecentActivityRef>((props, ref) => {
// Full replacement for non-silent refreshes or 'replace' strategy // Full replacement for non-silent refreshes or 'replace' strategy
setActivities(recentActivities); setActivities(recentActivities);
} }
} catch (error) { } catch (error: unknown) {
const errorMsg = getErrorMessage(error); handleError(error, {
console.error('Error fetching recent activity:', errorMsg); action: 'Load Recent Activity',
toast({ userId: user?.id
title: "Error",
description: "Failed to load recent activity",
variant: "destructive",
}); });
} finally { } finally {
if (!silent) { if (!silent) {

View File

@@ -16,12 +16,12 @@ import {
PaginationPrevious, PaginationPrevious,
} from '@/components/ui/pagination'; } from '@/components/ui/pagination';
import { supabase } from '@/integrations/supabase/client'; import { supabase } from '@/integrations/supabase/client';
import { useToast } from '@/hooks/use-toast';
import { format } from 'date-fns'; import { format } from 'date-fns';
import { useAdminSettings } from '@/hooks/useAdminSettings'; import { useAdminSettings } from '@/hooks/useAdminSettings';
import { useAuth } from '@/hooks/useAuth'; import { useAuth } from '@/hooks/useAuth';
import { useIsMobile } from '@/hooks/use-mobile'; import { useIsMobile } from '@/hooks/use-mobile';
import { smartMergeArray } from '@/lib/smartStateUpdate'; import { smartMergeArray } from '@/lib/smartStateUpdate';
import { handleError, handleSuccess } from '@/lib/errorHandler';
// Type-safe reported content interfaces // Type-safe reported content interfaces
interface ReportedReview { interface ReportedReview {
@@ -112,7 +112,6 @@ export const ReportsQueue = forwardRef<ReportsQueueRef>((props, ref) => {
const [isInitialLoad, setIsInitialLoad] = useState(true); const [isInitialLoad, setIsInitialLoad] = useState(true);
const [actionLoading, setActionLoading] = useState<string | null>(null); const [actionLoading, setActionLoading] = useState<string | null>(null);
const [newReportsCount, setNewReportsCount] = useState(0); const [newReportsCount, setNewReportsCount] = useState(0);
const { toast } = useToast();
const { user } = useAuth(); const { user } = useAuth();
// Pagination state // Pagination state
@@ -301,12 +300,11 @@ export const ReportsQueue = forwardRef<ReportsQueueRef>((props, ref) => {
setReports(reportsWithContent); setReports(reportsWithContent);
setNewReportsCount(0); setNewReportsCount(0);
} }
} catch (error) { } catch (error: unknown) {
console.error('Error fetching reports:', error); handleError(error, {
toast({ action: 'Load Reports',
title: "Error", userId: user?.id,
description: "Failed to load reports queue", metadata: { currentPage, pageSize }
variant: "destructive",
}); });
} finally { } finally {
// Only clear loading if it was set // Only clear loading if it was set
@@ -352,10 +350,7 @@ export const ReportsQueue = forwardRef<ReportsQueueRef>((props, ref) => {
if (error) throw error; if (error) throw error;
toast({ handleSuccess(`Report ${action}`, `The report has been marked as ${action}`);
title: `Report ${action}`,
description: `The report has been marked as ${action}`,
});
// Remove report from queue // Remove report from queue
setReports(prev => { setReports(prev => {
@@ -366,12 +361,11 @@ export const ReportsQueue = forwardRef<ReportsQueueRef>((props, ref) => {
} }
return newReports; return newReports;
}); });
} catch (error) { } catch (error: unknown) {
console.error('Error updating report:', error); handleError(error, {
toast({ action: `${action === 'reviewed' ? 'Resolve' : 'Dismiss'} Report`,
title: "Error", userId: user?.id,
description: `Failed to ${action} report`, metadata: { reportId, action }
variant: "destructive",
}); });
} finally { } finally {
setActionLoading(null); setActionLoading(null);

View File

@@ -9,7 +9,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
import { supabase } from '@/integrations/supabase/client'; import { supabase } from '@/integrations/supabase/client';
import { useAuth } from '@/hooks/useAuth'; import { useAuth } from '@/hooks/useAuth';
import { useUserRole } from '@/hooks/useUserRole'; import { useUserRole } from '@/hooks/useUserRole';
import { useToast } from '@/hooks/use-toast'; import { handleError, handleSuccess } from '@/lib/errorHandler';
// Type-safe role definitions // Type-safe role definitions
const VALID_ROLES = ['admin', 'moderator', 'user'] as const; const VALID_ROLES = ['admin', 'moderator', 'user'] as const;
@@ -66,9 +66,6 @@ export function UserRoleManager() {
isSuperuser, isSuperuser,
permissions permissions
} = useUserRole(); } = useUserRole();
const {
toast
} = useToast();
const fetchUserRoles = async () => { const fetchUserRoles = async () => {
try { try {
const { const {
@@ -99,12 +96,10 @@ export function UserRoleManager() {
profiles: profileMap.get(role.user_id) profiles: profileMap.get(role.user_id)
})); }));
setUserRoles(userRolesWithProfiles); setUserRoles(userRolesWithProfiles);
} catch (error) { } catch (error: unknown) {
console.error('Error fetching user roles:', error); handleError(error, {
toast({ action: 'Load User Roles',
title: "Error", userId: user?.id
description: "Failed to load user roles",
variant: "destructive"
}); });
} finally { } finally {
setLoading(false); setLoading(false);
@@ -144,10 +139,10 @@ export function UserRoleManager() {
// Double-check role validity before database operation // Double-check role validity before database operation
if (!isValidRole(role)) { if (!isValidRole(role)) {
toast({ handleError(new Error('Invalid role'), {
title: "Invalid Role", action: 'Grant Role',
description: "The selected role is not valid", userId: user?.id,
variant: "destructive" metadata: { targetUserId: userId, attemptedRole: role }
}); });
return; return;
} }
@@ -164,20 +159,16 @@ export function UserRoleManager() {
if (error) throw error; if (error) throw error;
toast({ handleSuccess('Role Granted', `User has been granted ${getRoleLabel(role)} role`);
title: "Role Granted",
description: `User has been granted ${getRoleLabel(role)} role`
});
setNewUserSearch(''); setNewUserSearch('');
setNewRole(''); setNewRole('');
setSearchResults([]); setSearchResults([]);
fetchUserRoles(); fetchUserRoles();
} catch (error) { } catch (error: unknown) {
console.error('Error granting role:', error); handleError(error, {
toast({ action: 'Grant Role',
title: "Error", userId: user?.id,
description: "Failed to grant role", metadata: { targetUserId: userId, role }
variant: "destructive"
}); });
} finally { } finally {
setActionLoading(null); setActionLoading(null);
@@ -191,17 +182,13 @@ export function UserRoleManager() {
error error
} = await supabase.from('user_roles').delete().eq('id', roleId); } = await supabase.from('user_roles').delete().eq('id', roleId);
if (error) throw error; if (error) throw error;
toast({ handleSuccess('Role Revoked', 'User role has been revoked');
title: "Role Revoked",
description: "User role has been revoked"
});
fetchUserRoles(); fetchUserRoles();
} catch (error) { } catch (error: unknown) {
console.error('Error revoking role:', error); handleError(error, {
toast({ action: 'Revoke Role',
title: "Error", userId: user?.id,
description: "Failed to revoke role", metadata: { roleId }
variant: "destructive"
}); });
} finally { } finally {
setActionLoading(null); setActionLoading(null);
@@ -273,10 +260,10 @@ export function UserRoleManager() {
} else if (selectedUser && newRole) { } else if (selectedUser && newRole) {
// This should never happen due to Select component constraints, // This should never happen due to Select component constraints,
// but provides safety in case of UI bugs // but provides safety in case of UI bugs
toast({ handleError(new Error('Invalid role selected'), {
title: "Invalid Role", action: 'Grant Role',
description: "Please select a valid role", userId: user?.id,
variant: "destructive" 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"> }} disabled={!newRole || !isValidRole(newRole) || !searchResults.find(p => (p.display_name || p.username) === newUserSearch) || actionLoading === 'grant'} className="w-full md:w-auto">

View File

@@ -5,6 +5,7 @@ import { Button } from '@/components/ui/button';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
import { validateEntityData, ValidationResult } from '@/lib/entityValidationSchemas'; import { validateEntityData, ValidationResult } from '@/lib/entityValidationSchemas';
import { logger } from '@/lib/logger';
interface ValidationSummaryProps { interface ValidationSummaryProps {
item: { item: {
@@ -76,8 +77,12 @@ export function ValidationSummary({ item, onValidationChange, compact = false, v
setValidationResult(result); setValidationResult(result);
onValidationChange?.(result); onValidationChange?.(result);
} catch (error) { } catch (error: unknown) {
console.error('Validation error:', error); logger.error('Entity validation failed', {
action: 'validate_entity',
entityType: item.item_type,
error: error instanceof Error ? error.message : String(error)
});
setValidationResult({ setValidationResult({
isValid: false, isValid: false,
blockingErrors: [{ field: 'validation', message: 'Failed to validate', severity: 'blocking' }], blockingErrors: [{ field: 'validation', message: 'Failed to validate', severity: 'blocking' }],