mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 07:51: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 }],
|
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
|
||||||
"@typescript-eslint/no-unused-vars": "off",
|
"@typescript-eslint/no-unused-vars": "off",
|
||||||
"@typescript-eslint/no-explicit-any": "error",
|
"@typescript-eslint/no-explicit-any": "error",
|
||||||
"@typescript-eslint/no-unsafe-assignment": "warn",
|
"@typescript-eslint/no-unsafe-assignment": "error",
|
||||||
"@typescript-eslint/no-unsafe-member-access": "warn",
|
"@typescript-eslint/no-unsafe-member-access": "error",
|
||||||
"@typescript-eslint/no-unsafe-call": "warn",
|
"@typescript-eslint/no-unsafe-call": "error",
|
||||||
"@typescript-eslint/no-unsafe-return": "warn",
|
"@typescript-eslint/no-unsafe-return": "error",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { submitDesignerCreation, submitDesignerUpdate } from '@/lib/entitySubmis
|
|||||||
import { useAuth } from '@/hooks/useAuth';
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import type { UploadedImage } from '@/types/company';
|
||||||
|
|
||||||
// Raw form input state (before Zod transformation)
|
// Raw form input state (before Zod transformation)
|
||||||
interface DesignerFormInput {
|
interface DesignerFormInput {
|
||||||
@@ -34,7 +35,7 @@ interface DesignerFormInput {
|
|||||||
headquarters_location?: string;
|
headquarters_location?: string;
|
||||||
website_url?: string;
|
website_url?: string;
|
||||||
images?: {
|
images?: {
|
||||||
uploaded: any[];
|
uploaded: UploadedImage[];
|
||||||
banner_assignment?: number | null;
|
banner_assignment?: number | null;
|
||||||
card_assignment?: number | null;
|
card_assignment?: number | null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { useAuth } from '@/hooks/useAuth';
|
|||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { toDateOnly } from '@/lib/dateUtils';
|
import { toDateOnly } from '@/lib/dateUtils';
|
||||||
|
import type { UploadedImage } from '@/types/company';
|
||||||
|
|
||||||
// Raw form input state (before Zod transformation)
|
// Raw form input state (before Zod transformation)
|
||||||
interface ManufacturerFormInput {
|
interface ManufacturerFormInput {
|
||||||
@@ -35,7 +36,7 @@ interface ManufacturerFormInput {
|
|||||||
headquarters_location?: string;
|
headquarters_location?: string;
|
||||||
website_url?: string;
|
website_url?: string;
|
||||||
images?: {
|
images?: {
|
||||||
uploaded: any[];
|
uploaded: UploadedImage[];
|
||||||
banner_assignment?: number | null;
|
banner_assignment?: number | null;
|
||||||
card_assignment?: number | null;
|
card_assignment?: number | null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { submitOperatorCreation, submitOperatorUpdate } from '@/lib/entitySubmis
|
|||||||
import { useAuth } from '@/hooks/useAuth';
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import type { UploadedImage } from '@/types/company';
|
||||||
|
|
||||||
// Raw form input state (before Zod transformation)
|
// Raw form input state (before Zod transformation)
|
||||||
interface OperatorFormInput {
|
interface OperatorFormInput {
|
||||||
@@ -34,7 +35,7 @@ interface OperatorFormInput {
|
|||||||
headquarters_location?: string;
|
headquarters_location?: string;
|
||||||
website_url?: string;
|
website_url?: string;
|
||||||
images?: {
|
images?: {
|
||||||
uploaded: any[];
|
uploaded: UploadedImage[];
|
||||||
banner_assignment?: number | null;
|
banner_assignment?: number | null;
|
||||||
card_assignment?: number | null;
|
card_assignment?: number | null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { submitPropertyOwnerCreation, submitPropertyOwnerUpdate } from '@/lib/en
|
|||||||
import { useAuth } from '@/hooks/useAuth';
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import type { UploadedImage } from '@/types/company';
|
||||||
|
|
||||||
// Raw form input state (before Zod transformation)
|
// Raw form input state (before Zod transformation)
|
||||||
interface PropertyOwnerFormInput {
|
interface PropertyOwnerFormInput {
|
||||||
@@ -34,7 +35,7 @@ interface PropertyOwnerFormInput {
|
|||||||
headquarters_location?: string;
|
headquarters_location?: string;
|
||||||
website_url?: string;
|
website_url?: string;
|
||||||
images?: {
|
images?: {
|
||||||
uploaded: any[];
|
uploaded: UploadedImage[];
|
||||||
banner_assignment?: number | null;
|
banner_assignment?: number | null;
|
||||||
card_assignment?: number | null;
|
card_assignment?: number | null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
|||||||
import * as z from 'zod';
|
import * as z from 'zod';
|
||||||
import { validateSubmissionHandler } from '@/lib/entityFormValidation';
|
import { validateSubmissionHandler } from '@/lib/entityFormValidation';
|
||||||
import type { RideTechnicalSpec, RideCoasterStat, RideNameHistory } from '@/types/database';
|
import type { RideTechnicalSpec, RideCoasterStat, RideNameHistory } from '@/types/database';
|
||||||
|
import type { TempCompanyData, TempRideModelData } from '@/types/company';
|
||||||
import { entitySchemas } from '@/lib/entityValidationSchemas';
|
import { entitySchemas } from '@/lib/entityValidationSchemas';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
@@ -129,8 +130,8 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }:
|
|||||||
initialData?.manufacturer_id || ''
|
initialData?.manufacturer_id || ''
|
||||||
);
|
);
|
||||||
const [selectedManufacturerName, setSelectedManufacturerName] = useState<string>('');
|
const [selectedManufacturerName, setSelectedManufacturerName] = useState<string>('');
|
||||||
const [tempNewManufacturer, setTempNewManufacturer] = useState<any>(null);
|
const [tempNewManufacturer, setTempNewManufacturer] = useState<TempCompanyData | null>(null);
|
||||||
const [tempNewRideModel, setTempNewRideModel] = useState<any>(null);
|
const [tempNewRideModel, setTempNewRideModel] = useState<TempRideModelData | null>(null);
|
||||||
const [isManufacturerModalOpen, setIsManufacturerModalOpen] = useState(false);
|
const [isManufacturerModalOpen, setIsManufacturerModalOpen] = useState(false);
|
||||||
const [isModelModalOpen, setIsModelModalOpen] = useState(false);
|
const [isModelModalOpen, setIsModelModalOpen] = useState(false);
|
||||||
|
|
||||||
|
|||||||
@@ -61,3 +61,16 @@ export const handleInfo = (
|
|||||||
duration: 4000
|
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';
|
import { ImageAssignments } from '@/components/upload/EntityMultiImageUploader';
|
||||||
|
|
||||||
|
export interface UploadedImage {
|
||||||
|
url: string;
|
||||||
|
cloudflare_id?: string;
|
||||||
|
file?: File;
|
||||||
|
isLocal?: boolean;
|
||||||
|
caption?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CompanyFormData {
|
export interface CompanyFormData {
|
||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
@@ -26,3 +34,17 @@ export interface TempCompanyData {
|
|||||||
headquarters_location?: string;
|
headquarters_location?: string;
|
||||||
website_url?: 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;
|
id: string;
|
||||||
submission_id: string;
|
submission_id: string;
|
||||||
item_type: EntityType | 'photo' | 'ride_model';
|
item_type: EntityType | 'photo' | 'ride_model';
|
||||||
item_data: any;
|
item_data: Record<string, unknown>;
|
||||||
original_data?: any;
|
original_data?: Record<string, unknown>;
|
||||||
status: 'pending' | 'approved' | 'rejected';
|
status: 'pending' | 'approved' | 'rejected';
|
||||||
depends_on: string | null;
|
depends_on: string | null;
|
||||||
order_index: number;
|
order_index: number;
|
||||||
@@ -124,3 +124,4 @@ export function createSubmissionContent(
|
|||||||
...referenceIds
|
...referenceIds
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user