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 { MapPin, Loader2, X } from 'lucide-react';
import { ParkLocationMap } from '@/components/maps/ParkLocationMap';
import { logger } from '@/lib/logger';
interface LocationResult {
place_id: number;

View File

@@ -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<Record<string, string>>({});
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 }
});
}
};

View File

@@ -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);
}

View File

@@ -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<UserProfile[]>([]);
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);

View File

@@ -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);

View File

@@ -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<Moderator[]>([]);
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);

View File

@@ -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<RecentActivityRef>((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<RecentActivityRef>((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) {

View File

@@ -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<ReportsQueueRef>((props, ref) => {
const [isInitialLoad, setIsInitialLoad] = useState(true);
const [actionLoading, setActionLoading] = useState<string | null>(null);
const [newReportsCount, setNewReportsCount] = useState(0);
const { toast } = useToast();
const { user } = useAuth();
// Pagination state
@@ -301,12 +300,11 @@ export const ReportsQueue = forwardRef<ReportsQueueRef>((props, ref) => {
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<ReportsQueueRef>((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<ReportsQueueRef>((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);

View File

@@ -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">

View File

@@ -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' }],