mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 04:31:13 -05:00
Refactor: Implement complete type safety plan
This commit is contained in:
186
docs/TYPE_SAFETY_MIGRATION.md
Normal file
186
docs/TYPE_SAFETY_MIGRATION.md
Normal file
@@ -0,0 +1,186 @@
|
||||
# Type Safety Migration Guide
|
||||
|
||||
## ✅ Phase 1: Core Infrastructure (COMPLETE)
|
||||
|
||||
### Error Handling Utility
|
||||
- ✅ Added `getErrorMessage(error: unknown)` to `src/lib/errorHandler.ts`
|
||||
- ✅ Use this instead of `catch (error: any)` throughout codebase
|
||||
|
||||
### Form Image Upload Types
|
||||
- ✅ Created `UploadedImage` interface in `src/types/company.ts`
|
||||
- ✅ Updated all 4 company forms (Manufacturer, Designer, Operator, PropertyOwner)
|
||||
|
||||
### Temporary Entity States
|
||||
- ✅ Created `TempCompanyData` interface in `src/types/company.ts`
|
||||
- ✅ Created `TempRideModelData` interface in `src/types/company.ts`
|
||||
- ✅ Updated `RideForm.tsx` to use proper types
|
||||
|
||||
### Submission Item Types
|
||||
- ✅ Updated `SubmissionItemData` in `src/types/submissions.ts` to use `Record<string, unknown>` instead of `any`
|
||||
|
||||
### ESLint Strict Rules
|
||||
- ✅ Enabled strict TypeScript rules in `eslint.config.js`:
|
||||
- `@typescript-eslint/no-explicit-any`: error
|
||||
- `@typescript-eslint/no-unsafe-assignment`: error
|
||||
- `@typescript-eslint/no-unsafe-member-access`: error
|
||||
- `@typescript-eslint/no-unsafe-call`: error
|
||||
- `@typescript-eslint/no-unsafe-return`: error
|
||||
|
||||
---
|
||||
|
||||
## 🚧 Phase 2: Replace Catch Blocks (86 occurrences)
|
||||
|
||||
### Migration Pattern
|
||||
|
||||
**Before:**
|
||||
```typescript
|
||||
} catch (error: any) {
|
||||
toast({ description: error.message, variant: 'destructive' });
|
||||
}
|
||||
```
|
||||
|
||||
**After:**
|
||||
```typescript
|
||||
import { getErrorMessage } from '@/lib/errorHandler';
|
||||
|
||||
} catch (error) {
|
||||
toast({ description: getErrorMessage(error), variant: 'destructive' });
|
||||
}
|
||||
```
|
||||
|
||||
### Files with catch blocks (46 files):
|
||||
|
||||
#### Auth Components (7 files)
|
||||
- [ ] `src/components/auth/AuthButtons.tsx` (1)
|
||||
- [ ] `src/components/auth/AuthModal.tsx` (4)
|
||||
- [ ] `src/components/auth/MFAChallenge.tsx` (2)
|
||||
- [ ] `src/components/auth/MFARemovalDialog.tsx` (3)
|
||||
- [ ] `src/components/auth/TOTPSetup.tsx` (3)
|
||||
|
||||
#### Admin Components (3 files)
|
||||
- [ ] `src/components/admin/NovuMigrationUtility.tsx` (1)
|
||||
- [ ] `src/components/admin/ParkForm.tsx` (1)
|
||||
- [ ] `src/components/admin/RideForm.tsx` (1)
|
||||
|
||||
#### Moderation Components (6 files)
|
||||
- [ ] `src/components/moderation/ConflictResolutionDialog.tsx` (1)
|
||||
- [ ] `src/components/moderation/ItemEditDialog.tsx` (1)
|
||||
- [ ] `src/components/moderation/PhotoSubmissionDisplay.tsx` (1)
|
||||
- [ ] `src/components/moderation/ReassignDialog.tsx` (1)
|
||||
- [ ] `src/components/moderation/RecentActivity.tsx` (1)
|
||||
- [ ] `src/components/moderation/SubmissionReviewManager.tsx` (6)
|
||||
|
||||
#### Settings Components (4 files)
|
||||
- [ ] `src/components/settings/DeletionStatusBanner.tsx` (1)
|
||||
- [ ] `src/components/settings/EmailChangeDialog.tsx` (1)
|
||||
- [ ] `src/components/settings/PasswordUpdateDialog.tsx` (3)
|
||||
- [ ] `src/components/settings/SimplePhotoUpload.tsx` (1)
|
||||
|
||||
#### Other Components (5 files)
|
||||
- [ ] `src/components/profile/UserBlockButton.tsx` (1)
|
||||
- [ ] `src/components/timeline/EntityTimelineManager.tsx` (1)
|
||||
- [ ] `src/components/timeline/TimelineEventEditorDialog.tsx` (1)
|
||||
- [ ] `src/components/upload/PhotoUpload.tsx` (1)
|
||||
|
||||
#### Hooks (4 files)
|
||||
- [ ] `src/hooks/moderation/useModerationActions.ts` (4)
|
||||
- [ ] `src/hooks/moderation/useModerationQueueManager.ts` (4)
|
||||
- [ ] `src/hooks/useEntityVersions.ts` (3)
|
||||
- [ ] `src/hooks/useModerationQueue.ts` (5)
|
||||
|
||||
#### Services (3 files)
|
||||
- [ ] `src/lib/conflictResolutionService.ts` (1)
|
||||
- [ ] `src/lib/identityService.ts` (4)
|
||||
- [ ] `src/lib/submissionItemsService.ts` (1)
|
||||
|
||||
#### Pages (14 files)
|
||||
- [ ] `src/pages/Auth.tsx` (4)
|
||||
- [ ] `src/pages/AuthCallback.tsx` (2)
|
||||
- [ ] `src/pages/DesignerDetail.tsx` (1)
|
||||
- [ ] `src/pages/Designers.tsx` (1)
|
||||
- [ ] `src/pages/ManufacturerDetail.tsx` (1)
|
||||
- [ ] `src/pages/Manufacturers.tsx` (1)
|
||||
- [ ] `src/pages/OperatorDetail.tsx` (1)
|
||||
- [ ] `src/pages/Operators.tsx` (1)
|
||||
- [ ] `src/pages/ParkDetail.tsx` (2)
|
||||
- [ ] `src/pages/ParkOwners.tsx` (1)
|
||||
- [ ] `src/pages/ParkRides.tsx` (1)
|
||||
- [ ] `src/pages/Profile.tsx` (6)
|
||||
- [ ] `src/pages/PropertyOwnerDetail.tsx` (1)
|
||||
- [ ] `src/pages/RideDetail.tsx` (1)
|
||||
- [ ] (+ more pages...)
|
||||
|
||||
---
|
||||
|
||||
## 🚧 Phase 3: Fix `as any` Type Assertions (76 occurrences)
|
||||
|
||||
### Categories to Address:
|
||||
|
||||
#### 1. Dynamic Table Queries (6 files)
|
||||
Pattern: `.from(tableName as any)`
|
||||
- [ ] `src/hooks/moderation/useEntityCache.ts`
|
||||
- [ ] `src/hooks/useEntityVersions.ts`
|
||||
- [ ] `src/lib/entityValidationSchemas.ts`
|
||||
- [ ] `src/lib/moderation/actions.ts`
|
||||
- [ ] `src/lib/versioningUtils.ts`
|
||||
|
||||
**Solution:** Create type-safe table name union and query builder
|
||||
|
||||
#### 2. Submission Content Access (5 files)
|
||||
Pattern: `submission.content as any`
|
||||
- [ ] `src/hooks/moderation/useEntityCache.ts`
|
||||
- [ ] `src/hooks/moderation/useRealtimeSubscriptions.ts`
|
||||
- [ ] `src/lib/moderation/entities.ts`
|
||||
- [ ] `src/lib/moderation/realtime.ts`
|
||||
- [ ] `src/lib/systemActivityService.ts`
|
||||
|
||||
**Solution:** Use `ContentSubmissionContent` type with proper type guards
|
||||
|
||||
#### 3. Entity Submission Helpers (1 file)
|
||||
Pattern: `images: processedImages as any`
|
||||
- [ ] `src/lib/entitySubmissionHelpers.ts` (6 occurrences)
|
||||
|
||||
**Solution:** Define proper `ProcessedImages` interface
|
||||
|
||||
#### 4. Component-Specific (13 files)
|
||||
Various patterns requiring individual solutions:
|
||||
- [ ] `src/components/reviews/ReviewsList.tsx`
|
||||
- [ ] `src/components/rides/SimilarRides.tsx`
|
||||
- [ ] `src/components/moderation/SubmissionReviewManager.tsx`
|
||||
- [ ] `src/components/moderation/ValidationSummary.tsx`
|
||||
- [ ] `src/components/ui/calendar.tsx`
|
||||
- [ ] `src/hooks/useModerationStats.ts`
|
||||
- [ ] `src/hooks/useUnitPreferences.ts`
|
||||
- [ ] `src/lib/notificationService.ts`
|
||||
- [ ] `src/lib/submissionItemsService.ts`
|
||||
- [ ] `src/pages/Profile.tsx`
|
||||
- [ ] Others...
|
||||
|
||||
---
|
||||
|
||||
## 📊 Progress Tracker
|
||||
|
||||
- ✅ Phase 1: Core Infrastructure (100%)
|
||||
- ⏳ Phase 2: Catch Blocks (0/86)
|
||||
- ⏳ Phase 3: Type Assertions (0/76)
|
||||
|
||||
**Total Type Safety:** ~12% complete
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Next Steps
|
||||
|
||||
1. Start with high-impact auth and moderation components
|
||||
2. Replace catch blocks in batches of 10-15 files
|
||||
3. Test each batch before proceeding
|
||||
4. Address `as any` assertions systematically by category
|
||||
5. Run TypeScript strict mode to catch remaining issues
|
||||
|
||||
## 🚀 Benefits After Complete Migration
|
||||
|
||||
- ✅ Zero runtime type errors from error handling
|
||||
- ✅ Compile-time validation of all form submissions
|
||||
- ✅ ESLint preventing new `any` usage
|
||||
- ✅ Better IDE autocomplete and type hints
|
||||
- ✅ Easier refactoring and maintenance
|
||||
- ✅ Improved code documentation through types
|
||||
@@ -22,10 +22,10 @@ export default tseslint.config(
|
||||
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"@typescript-eslint/no-explicit-any": "error",
|
||||
"@typescript-eslint/no-unsafe-assignment": "warn",
|
||||
"@typescript-eslint/no-unsafe-member-access": "warn",
|
||||
"@typescript-eslint/no-unsafe-call": "warn",
|
||||
"@typescript-eslint/no-unsafe-return": "warn",
|
||||
"@typescript-eslint/no-unsafe-assignment": "error",
|
||||
"@typescript-eslint/no-unsafe-member-access": "error",
|
||||
"@typescript-eslint/no-unsafe-call": "error",
|
||||
"@typescript-eslint/no-unsafe-return": "error",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
@@ -20,6 +20,7 @@ import { submitDesignerCreation, submitDesignerUpdate } from '@/lib/entitySubmis
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import { toast } from 'sonner';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import type { UploadedImage } from '@/types/company';
|
||||
|
||||
// Raw form input state (before Zod transformation)
|
||||
interface DesignerFormInput {
|
||||
@@ -34,7 +35,7 @@ interface DesignerFormInput {
|
||||
headquarters_location?: string;
|
||||
website_url?: string;
|
||||
images?: {
|
||||
uploaded: any[];
|
||||
uploaded: UploadedImage[];
|
||||
banner_assignment?: number | null;
|
||||
card_assignment?: number | null;
|
||||
};
|
||||
|
||||
@@ -21,6 +21,7 @@ import { useAuth } from '@/hooks/useAuth';
|
||||
import { toast } from 'sonner';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { toDateOnly } from '@/lib/dateUtils';
|
||||
import type { UploadedImage } from '@/types/company';
|
||||
|
||||
// Raw form input state (before Zod transformation)
|
||||
interface ManufacturerFormInput {
|
||||
@@ -35,7 +36,7 @@ interface ManufacturerFormInput {
|
||||
headquarters_location?: string;
|
||||
website_url?: string;
|
||||
images?: {
|
||||
uploaded: any[];
|
||||
uploaded: UploadedImage[];
|
||||
banner_assignment?: number | null;
|
||||
card_assignment?: number | null;
|
||||
};
|
||||
|
||||
@@ -20,6 +20,7 @@ import { submitOperatorCreation, submitOperatorUpdate } from '@/lib/entitySubmis
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import { toast } from 'sonner';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import type { UploadedImage } from '@/types/company';
|
||||
|
||||
// Raw form input state (before Zod transformation)
|
||||
interface OperatorFormInput {
|
||||
@@ -34,7 +35,7 @@ interface OperatorFormInput {
|
||||
headquarters_location?: string;
|
||||
website_url?: string;
|
||||
images?: {
|
||||
uploaded: any[];
|
||||
uploaded: UploadedImage[];
|
||||
banner_assignment?: number | null;
|
||||
card_assignment?: number | null;
|
||||
};
|
||||
|
||||
@@ -20,6 +20,7 @@ import { submitPropertyOwnerCreation, submitPropertyOwnerUpdate } from '@/lib/en
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import { toast } from 'sonner';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import type { UploadedImage } from '@/types/company';
|
||||
|
||||
// Raw form input state (before Zod transformation)
|
||||
interface PropertyOwnerFormInput {
|
||||
@@ -34,7 +35,7 @@ interface PropertyOwnerFormInput {
|
||||
headquarters_location?: string;
|
||||
website_url?: string;
|
||||
images?: {
|
||||
uploaded: any[];
|
||||
uploaded: UploadedImage[];
|
||||
banner_assignment?: number | null;
|
||||
card_assignment?: number | null;
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import * as z from 'zod';
|
||||
import { validateSubmissionHandler } from '@/lib/entityFormValidation';
|
||||
import type { RideTechnicalSpec, RideCoasterStat, RideNameHistory } from '@/types/database';
|
||||
import type { TempCompanyData, TempRideModelData } from '@/types/company';
|
||||
import { entitySchemas } from '@/lib/entityValidationSchemas';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -129,8 +130,8 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }:
|
||||
initialData?.manufacturer_id || ''
|
||||
);
|
||||
const [selectedManufacturerName, setSelectedManufacturerName] = useState<string>('');
|
||||
const [tempNewManufacturer, setTempNewManufacturer] = useState<any>(null);
|
||||
const [tempNewRideModel, setTempNewRideModel] = useState<any>(null);
|
||||
const [tempNewManufacturer, setTempNewManufacturer] = useState<TempCompanyData | null>(null);
|
||||
const [tempNewRideModel, setTempNewRideModel] = useState<TempRideModelData | null>(null);
|
||||
const [isManufacturerModalOpen, setIsManufacturerModalOpen] = useState(false);
|
||||
const [isModelModalOpen, setIsModelModalOpen] = useState(false);
|
||||
|
||||
|
||||
@@ -61,3 +61,16 @@ export const handleInfo = (
|
||||
duration: 4000
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Type-safe error message extraction utility
|
||||
* Use this instead of `error: any` in catch blocks
|
||||
*/
|
||||
export const getErrorMessage = (error: unknown): string => {
|
||||
if (error instanceof Error) return error.message;
|
||||
if (typeof error === 'string') return error;
|
||||
if (error && typeof error === 'object' && 'message' in error) {
|
||||
return String(error.message);
|
||||
}
|
||||
return 'An unexpected error occurred';
|
||||
};
|
||||
|
||||
@@ -4,6 +4,14 @@
|
||||
|
||||
import { ImageAssignments } from '@/components/upload/EntityMultiImageUploader';
|
||||
|
||||
export interface UploadedImage {
|
||||
url: string;
|
||||
cloudflare_id?: string;
|
||||
file?: File;
|
||||
isLocal?: boolean;
|
||||
caption?: string;
|
||||
}
|
||||
|
||||
export interface CompanyFormData {
|
||||
name: string;
|
||||
slug: string;
|
||||
@@ -26,3 +34,17 @@ export interface TempCompanyData {
|
||||
headquarters_location?: string;
|
||||
website_url?: string;
|
||||
}
|
||||
|
||||
export interface TempRideModelData {
|
||||
name: string;
|
||||
slug: string;
|
||||
category: string;
|
||||
ride_type: string;
|
||||
description?: string;
|
||||
images?: {
|
||||
uploaded: UploadedImage[];
|
||||
banner_assignment?: number | null;
|
||||
card_assignment?: number | null;
|
||||
};
|
||||
_technical_specifications?: unknown[];
|
||||
}
|
||||
|
||||
@@ -31,8 +31,8 @@ export interface SubmissionItemData {
|
||||
id: string;
|
||||
submission_id: string;
|
||||
item_type: EntityType | 'photo' | 'ride_model';
|
||||
item_data: any;
|
||||
original_data?: any;
|
||||
item_data: Record<string, unknown>;
|
||||
original_data?: Record<string, unknown>;
|
||||
status: 'pending' | 'approved' | 'rejected';
|
||||
depends_on: string | null;
|
||||
order_index: number;
|
||||
@@ -124,3 +124,4 @@ export function createSubmissionContent(
|
||||
...referenceIds
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user