mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 09:51:13 -05:00
feat: Complete app-wide error coverage
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { handleError } from '@/lib/errorHandler';
|
||||
|
||||
// Profile schema (matches database JSONB structure)
|
||||
const ProfileSchema = z.object({
|
||||
@@ -101,8 +101,11 @@ export function validateModerationItems(data: unknown): {
|
||||
const result = ModerationItemArraySchema.safeParse(data);
|
||||
|
||||
if (!result.success) {
|
||||
logger.error('❌ Data validation failed', {
|
||||
errors: result.error.issues.slice(0, 5) // Log first 5 issues
|
||||
handleError(result.error, {
|
||||
action: 'Data validation failed',
|
||||
metadata: {
|
||||
errors: result.error.issues.slice(0, 5)
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import { supabase } from '@/lib/supabaseClient';
|
||||
import { logger } from './logger';
|
||||
import { handleError, handleNonCriticalError } from './errorHandler';
|
||||
|
||||
export interface SubmissionMetadataInsert {
|
||||
submission_id: string;
|
||||
@@ -37,7 +37,10 @@ export async function writeSubmissionMetadata(
|
||||
.insert(entries);
|
||||
|
||||
if (error) {
|
||||
logger.error('Failed to write submission metadata', { error, submissionId });
|
||||
handleError(error, {
|
||||
action: 'Write submission metadata',
|
||||
metadata: { submissionId }
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -56,7 +59,10 @@ export async function readSubmissionMetadata(
|
||||
.order('display_order');
|
||||
|
||||
if (error) {
|
||||
logger.error('Failed to read submission metadata', { error, submissionId });
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Read submission metadata',
|
||||
metadata: { submissionId }
|
||||
});
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { supabase } from '@/lib/supabaseClient';
|
||||
import type { ParkSubmissionData, RideSubmissionData, CompanySubmissionData, RideModelSubmissionData } from '@/types/submission-data';
|
||||
import { logger } from './logger';
|
||||
import { handleNonCriticalError } from './errorHandler';
|
||||
import {
|
||||
randomInt,
|
||||
randomFloat,
|
||||
@@ -352,12 +352,18 @@ export async function clearTestData(): Promise<{ deleted: number }> {
|
||||
.neq('id', '00000000-0000-0000-0000-000000000000'); // Delete all records
|
||||
|
||||
if (registryError) {
|
||||
logger.error('Error clearing test data registry', { error: registryError });
|
||||
handleNonCriticalError(registryError, {
|
||||
action: 'Clear test data registry',
|
||||
metadata: { operation: 'clearTestData' }
|
||||
});
|
||||
}
|
||||
|
||||
return { deleted: submissionCount };
|
||||
} catch (error: unknown) {
|
||||
logger.error('Error clearing test data', { error: error instanceof Error ? error.message : String(error) });
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Clear test data',
|
||||
metadata: { operation: 'clearTestData' }
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
import { supabase } from '@/lib/supabaseClient';
|
||||
import type { EntityType } from '@/types/versioning';
|
||||
import { createTableQuery } from './supabaseHelpers';
|
||||
import { logger } from './logger';
|
||||
import { handleNonCriticalError } from './errorHandler';
|
||||
|
||||
/**
|
||||
* Manually trigger cleanup of old versions for a specific entity type
|
||||
@@ -38,7 +38,10 @@ export async function cleanupVersions(
|
||||
});
|
||||
|
||||
if (error) {
|
||||
logger.error('Version cleanup failed', { error, entityType, keepCount });
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Version cleanup',
|
||||
metadata: { entityType, keepCount }
|
||||
});
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -98,7 +101,10 @@ export async function getVersionStats(
|
||||
const { data, error } = result;
|
||||
|
||||
if (error || !data) {
|
||||
logger.error('Failed to fetch version stats', { error, entityType, entityId });
|
||||
handleNonCriticalError(error || new Error('No data returned'), {
|
||||
action: 'Fetch version stats',
|
||||
metadata: { entityType, entityId }
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { supabase } from '@/lib/supabaseClient';
|
||||
import { logger } from './logger';
|
||||
import { handleNonCriticalError } from './errorHandler';
|
||||
|
||||
// Generate anonymous session hash (no PII)
|
||||
function getSessionHash(): string {
|
||||
@@ -41,6 +41,9 @@ export async function trackPageView(
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
// Fail silently - don't break the page if tracking fails
|
||||
logger.error('Failed to track page view', { entityType, entityId });
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Track page view',
|
||||
metadata: { entityType, entityId }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { ArrowLeft, MapPin, Star, Globe, Calendar, Edit, Ruler } from 'lucide-re
|
||||
import { Company } from '@/types/database';
|
||||
import { supabase } from '@/lib/supabaseClient';
|
||||
import { DesignerPhotoGallery } from '@/components/companies/DesignerPhotoGallery';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { handleNonCriticalError } from '@/lib/errorHandler';
|
||||
|
||||
// Lazy load admin form
|
||||
const DesignerForm = lazy(() => import('@/components/admin/DesignerForm').then(m => ({ default: m.DesignerForm })));
|
||||
@@ -82,7 +82,10 @@ export default function DesignerDetail() {
|
||||
fetchStatistics(data.id);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error fetching designer', { error });
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Fetch designer',
|
||||
metadata: { slug }
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -109,7 +112,10 @@ export default function DesignerDetail() {
|
||||
if (photosError) throw photosError;
|
||||
setTotalPhotos(photosCount || 0);
|
||||
} catch (error) {
|
||||
logger.error('Error fetching statistics', { error });
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Fetch designer statistics',
|
||||
metadata: { designerId }
|
||||
});
|
||||
} finally {
|
||||
setStatsLoading(false);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import { toast } from '@/hooks/use-toast';
|
||||
import { useAuthModal } from '@/hooks/useAuthModal';
|
||||
import { useDocumentTitle } from '@/hooks/useDocumentTitle';
|
||||
import { useOpenGraph } from '@/hooks/useOpenGraph';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { handleNonCriticalError } from '@/lib/errorHandler';
|
||||
|
||||
export default function DesignerRides() {
|
||||
const { designerSlug } = useParams<{ designerSlug: string }>();
|
||||
@@ -91,7 +91,10 @@ export default function DesignerRides() {
|
||||
setRides((ridesData || []) as any);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error fetching data', { error });
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Fetch designer rides',
|
||||
metadata: { designerSlug }
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import { DesignerCard } from '@/components/designers/DesignerCard';
|
||||
import { DesignerListView } from '@/components/designers/DesignerListView';
|
||||
import { DesignerForm } from '@/components/admin/DesignerForm';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { handleNonCriticalError } from '@/lib/errorHandler';
|
||||
import { useUserRole } from '@/hooks/useUserRole';
|
||||
import { toast } from '@/hooks/use-toast';
|
||||
import { submitCompanyCreation } from '@/lib/companyHelpers';
|
||||
@@ -89,7 +89,10 @@ export default function Designers() {
|
||||
const { data } = await query;
|
||||
setCompanies(data || []);
|
||||
} catch (error) {
|
||||
logger.error('Error fetching companies', { error });
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Fetch designers',
|
||||
metadata: { page: 'designers' }
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useNavigate } from "react-router-dom";
|
||||
import { supabase } from "@/lib/supabaseClient";
|
||||
import { authStorage } from "@/lib/authStorage";
|
||||
import { useDocumentTitle } from '@/hooks/useDocumentTitle';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { handleError } from '@/lib/errorHandler';
|
||||
|
||||
/**
|
||||
* ForceLogout - Hidden endpoint for completely clearing auth session
|
||||
@@ -16,26 +16,23 @@ const ForceLogout = () => {
|
||||
|
||||
useEffect(() => {
|
||||
const performFullLogout = async () => {
|
||||
logger.info('[ForceLogout] Starting complete auth cleanup');
|
||||
|
||||
try {
|
||||
// 1. Sign out from Supabase
|
||||
logger.info('[ForceLogout] Signing out from Supabase');
|
||||
await supabase.auth.signOut();
|
||||
|
||||
// 2. Clear all auth-related storage
|
||||
logger.info('[ForceLogout] Clearing all auth storage');
|
||||
authStorage.clearAll();
|
||||
|
||||
// 3. Brief delay to ensure cleanup completes
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
logger.info('[ForceLogout] Auth cleanup complete, redirecting to home');
|
||||
|
||||
// 4. Redirect to home page
|
||||
navigate('/', { replace: true });
|
||||
} catch (error) {
|
||||
logger.error('[ForceLogout] Error during logout', { error });
|
||||
handleError(error, {
|
||||
action: 'Force logout',
|
||||
metadata: { operation: 'forceLogout' }
|
||||
});
|
||||
// Still redirect even if there's an error
|
||||
navigate('/', { replace: true });
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import { ArrowLeft, MapPin, Star, Globe, Calendar, Edit, Factory, FerrisWheel }
|
||||
import { Company } from '@/types/database';
|
||||
import { supabase } from '@/lib/supabaseClient';
|
||||
import { ManufacturerPhotoGallery } from '@/components/companies/ManufacturerPhotoGallery';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { handleNonCriticalError } from '@/lib/errorHandler';
|
||||
|
||||
// Lazy load admin form
|
||||
const ManufacturerForm = lazy(() => import('@/components/admin/ManufacturerForm').then(m => ({ default: m.ManufacturerForm })));
|
||||
@@ -83,7 +83,10 @@ export default function ManufacturerDetail() {
|
||||
fetchStatistics(data.id);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error fetching manufacturer', { error });
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Fetch manufacturer',
|
||||
metadata: { slug }
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -119,7 +122,10 @@ export default function ManufacturerDetail() {
|
||||
if (photosError) throw photosError;
|
||||
setTotalPhotos(photosCount || 0);
|
||||
} catch (error) {
|
||||
logger.error('Error fetching statistics', { error });
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Fetch manufacturer statistics',
|
||||
metadata: { manufacturerId }
|
||||
});
|
||||
} finally {
|
||||
setStatsLoading(false);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import { toast } from '@/hooks/use-toast';
|
||||
import { useAuthModal } from '@/hooks/useAuthModal';
|
||||
import { useDocumentTitle } from '@/hooks/useDocumentTitle';
|
||||
import { useOpenGraph } from '@/hooks/useOpenGraph';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { handleNonCriticalError } from '@/lib/errorHandler';
|
||||
|
||||
interface RideModelWithCount extends RideModel {
|
||||
ride_count: number;
|
||||
@@ -86,7 +86,10 @@ export default function ManufacturerModels() {
|
||||
setModels(modelsWithCounts);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error fetching data', { error });
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Fetch manufacturer models',
|
||||
metadata: { manufacturerSlug }
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import { toast } from '@/hooks/use-toast';
|
||||
import { useAuthModal } from '@/hooks/useAuthModal';
|
||||
import { useDocumentTitle } from '@/hooks/useDocumentTitle';
|
||||
import { useOpenGraph } from '@/hooks/useOpenGraph';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { handleNonCriticalError } from '@/lib/errorHandler';
|
||||
|
||||
export default function ManufacturerRides() {
|
||||
const { manufacturerSlug } = useParams<{ manufacturerSlug: string }>();
|
||||
@@ -91,7 +91,10 @@ export default function ManufacturerRides() {
|
||||
setRides((ridesData || []) as any);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error fetching data', { error });
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Fetch manufacturer rides',
|
||||
metadata: { manufacturerSlug }
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import { ManufacturerCard } from '@/components/manufacturers/ManufacturerCard';
|
||||
import { ManufacturerListView } from '@/components/manufacturers/ManufacturerListView';
|
||||
import { ManufacturerForm } from '@/components/admin/ManufacturerForm';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { handleNonCriticalError } from '@/lib/errorHandler';
|
||||
import { useUserRole } from '@/hooks/useUserRole';
|
||||
import { toast } from '@/hooks/use-toast';
|
||||
import { submitCompanyCreation } from '@/lib/companyHelpers';
|
||||
@@ -76,7 +76,10 @@ export default function Manufacturers() {
|
||||
const { data } = await query;
|
||||
setCompanies(data || []);
|
||||
} catch (error) {
|
||||
logger.error('Error fetching companies', { error });
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Fetch manufacturers',
|
||||
metadata: { page: 'manufacturers' }
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import { Company, Park } from '@/types/database';
|
||||
import { supabase } from '@/lib/supabaseClient';
|
||||
import { OperatorPhotoGallery } from '@/components/companies/OperatorPhotoGallery';
|
||||
import { ParkCard } from '@/components/parks/ParkCard';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { handleNonCriticalError } from '@/lib/errorHandler';
|
||||
|
||||
// Lazy load admin form
|
||||
const OperatorForm = lazy(() => import('@/components/admin/OperatorForm').then(m => ({ default: m.OperatorForm })));
|
||||
|
||||
@@ -17,7 +17,7 @@ import { Grid3X3, List } from 'lucide-react';
|
||||
import { FilterState, SortState } from './Parks';
|
||||
import { useDocumentTitle } from '@/hooks/useDocumentTitle';
|
||||
import { useOpenGraph } from '@/hooks/useOpenGraph';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { handleNonCriticalError } from '@/lib/errorHandler';
|
||||
|
||||
const initialFilters: FilterState = {
|
||||
search: '',
|
||||
|
||||
@@ -17,7 +17,7 @@ import { Grid3X3, List } from 'lucide-react';
|
||||
import { FilterState, SortState } from './Parks';
|
||||
import { useDocumentTitle } from '@/hooks/useDocumentTitle';
|
||||
import { useOpenGraph } from '@/hooks/useOpenGraph';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { handleNonCriticalError } from '@/lib/errorHandler';
|
||||
|
||||
const initialFilters: FilterState = {
|
||||
search: '',
|
||||
|
||||
@@ -14,8 +14,7 @@ import { RideSubmissionData } from '@/types/submission-data';
|
||||
import { supabase } from '@/lib/supabaseClient';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import { toast } from '@/hooks/use-toast';
|
||||
import { getErrorMessage } from '@/lib/errorHandler';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { getErrorMessage, handleNonCriticalError } from '@/lib/errorHandler';
|
||||
import { useAuthModal } from '@/hooks/useAuthModal';
|
||||
import { useDocumentTitle } from '@/hooks/useDocumentTitle';
|
||||
import { useOpenGraph } from '@/hooks/useOpenGraph';
|
||||
|
||||
@@ -23,8 +23,7 @@ import { User, MapPin, Calendar, Star, Trophy, Settings, Camera, Edit3, Save, X,
|
||||
import { Profile as ProfileType } from '@/types/database';
|
||||
import { supabase } from '@/lib/supabaseClient';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { getErrorMessage } from '@/lib/errorHandler';
|
||||
import { getErrorMessage, handleNonCriticalError } from '@/lib/errorHandler';
|
||||
import { PhotoUpload } from '@/components/upload/PhotoUpload';
|
||||
import { profileEditSchema } from '@/lib/validation';
|
||||
import { LocationDisplay } from '@/components/profile/LocationDisplay';
|
||||
|
||||
@@ -14,7 +14,7 @@ import { Company, Park } from '@/types/database';
|
||||
import { supabase } from '@/lib/supabaseClient';
|
||||
import { PropertyOwnerPhotoGallery } from '@/components/companies/PropertyOwnerPhotoGallery';
|
||||
import { ParkCard } from '@/components/parks/ParkCard';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { handleNonCriticalError } from '@/lib/errorHandler';
|
||||
|
||||
// Lazy load admin form
|
||||
const PropertyOwnerForm = lazy(() => import('@/components/admin/PropertyOwnerForm').then(m => ({ default: m.PropertyOwnerForm })));
|
||||
|
||||
@@ -15,8 +15,7 @@ import { getCloudflareImageUrl } from '@/lib/cloudflareImageUtils';
|
||||
import { useAuthModal } from '@/hooks/useAuthModal';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import { toast } from '@/hooks/use-toast';
|
||||
import { getErrorMessage } from '@/lib/errorHandler';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { getErrorMessage, handleNonCriticalError } from '@/lib/errorHandler';
|
||||
import { ManufacturerPhotoGallery } from '@/components/companies/ManufacturerPhotoGallery';
|
||||
|
||||
// Lazy load admin form
|
||||
|
||||
@@ -14,8 +14,7 @@ import { RideForm } from '@/components/admin/RideForm';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import { useAuthModal } from '@/hooks/useAuthModal';
|
||||
import { toast } from '@/hooks/use-toast';
|
||||
import { getErrorMessage } from '@/lib/errorHandler';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { getErrorMessage, handleNonCriticalError } from '@/lib/errorHandler';
|
||||
import type { Ride, Company, RideModel } from "@/types/database";
|
||||
import { useDocumentTitle } from '@/hooks/useDocumentTitle';
|
||||
|
||||
|
||||
@@ -75,8 +75,7 @@ import { DialogFooter } from '@/components/ui/dialog';
|
||||
import { useTheme } from '@/components/theme/ThemeProvider';
|
||||
import { useUserRole } from '@/hooks/useUserRole';
|
||||
import { useDocumentTitle } from '@/hooks/useDocumentTitle';
|
||||
import { handleError, handleSuccess } from '@/lib/errorHandler';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { handleError, handleSuccess, handleNonCriticalError } from '@/lib/errorHandler';
|
||||
import { contactCategories } from '@/lib/contactValidation';
|
||||
import { invokeWithTracking } from '@/lib/edgeFunctionTracking';
|
||||
import { AdminLayout } from '@/components/layout/AdminLayout';
|
||||
@@ -199,7 +198,10 @@ export default function AdminContact() {
|
||||
const { data, error } = await query;
|
||||
|
||||
if (error) {
|
||||
logger.error('Failed to fetch contact submissions', { error: error.message });
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Fetch contact submissions',
|
||||
metadata: { error: error.message }
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
||||
@@ -219,7 +221,10 @@ export default function AdminContact() {
|
||||
.order('created_at', { ascending: true })
|
||||
.then(({ data, error }) => {
|
||||
if (error) {
|
||||
logger.error('Failed to fetch email threads', { error });
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Fetch email threads',
|
||||
metadata: { submissionId: selectedSubmission.id }
|
||||
});
|
||||
setEmailThreads([]);
|
||||
} else {
|
||||
setEmailThreads((data as EmailThread[]) || []);
|
||||
@@ -263,7 +268,10 @@ export default function AdminContact() {
|
||||
.eq('id', submissionId);
|
||||
|
||||
if (statusError) {
|
||||
logger.error('Failed to update status', { error: statusError });
|
||||
handleError(statusError, {
|
||||
action: 'Update contact status',
|
||||
metadata: { submissionId, newStatus }
|
||||
});
|
||||
throw statusError;
|
||||
}
|
||||
}
|
||||
@@ -465,8 +473,10 @@ export default function AdminContact() {
|
||||
.order('created_at', { ascending: true })
|
||||
.then(({ data, error }) => {
|
||||
if (error) {
|
||||
logger.error('Failed to refresh email threads', { error });
|
||||
handleError(error, { action: 'Refresh Email Threads' });
|
||||
handleError(error, {
|
||||
action: 'Refresh Email Threads',
|
||||
metadata: { submissionId: selectedSubmission.id }
|
||||
});
|
||||
} else {
|
||||
setEmailThreads((data as EmailThread[]) || []);
|
||||
handleSuccess('Refreshed', 'Email thread updated');
|
||||
|
||||
Reference in New Issue
Block a user