mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-24 06:51:13 -05:00
feat: Implement full type safety plan
This commit is contained in:
@@ -15,6 +15,7 @@ import { Layers, Save, X } from 'lucide-react';
|
||||
import { useUserRole } from '@/hooks/useUserRole';
|
||||
import { EntityMultiImageUploader, ImageAssignments } from '@/components/upload/EntityMultiImageUploader';
|
||||
import { TechnicalSpecsEditor } from './editors/TechnicalSpecsEditor';
|
||||
import { TechnicalSpecification } from '@/types/company';
|
||||
|
||||
const rideModelSchema = z.object({
|
||||
name: z.string().min(1, 'Name is required'),
|
||||
@@ -40,7 +41,7 @@ type RideModelFormData = z.infer<typeof rideModelSchema>;
|
||||
interface RideModelFormProps {
|
||||
manufacturerName: string;
|
||||
manufacturerId?: string;
|
||||
onSubmit: (data: RideModelFormData & { _technical_specifications?: unknown[] }) => void;
|
||||
onSubmit: (data: RideModelFormData & { _technical_specifications?: TechnicalSpecification[] }) => void;
|
||||
onCancel: () => void;
|
||||
initialData?: Partial<RideModelFormData & {
|
||||
id?: string;
|
||||
|
||||
@@ -6,7 +6,7 @@ import { ArrayFieldDiff } from './ArrayFieldDiff';
|
||||
import { SpecialFieldDisplay } from './SpecialFieldDisplay';
|
||||
|
||||
// Helper to format compact values (truncate long strings)
|
||||
function formatCompactValue(value: any, precision?: 'day' | 'month' | 'year', maxLength = 30): string {
|
||||
function formatCompactValue(value: unknown, precision?: 'day' | 'month' | 'year', maxLength = 30): string {
|
||||
const formatted = formatFieldValue(value, precision);
|
||||
if (formatted.length > maxLength) {
|
||||
return formatted.substring(0, maxLength) + '...';
|
||||
|
||||
@@ -36,7 +36,7 @@ export function ItemEditDialog({ item, open, onOpenChange, onComplete }: ItemEdi
|
||||
|
||||
if (!item) return null;
|
||||
|
||||
const handleSubmit = async (data: any) => {
|
||||
const handleSubmit = async (data: Record<string, unknown>) => {
|
||||
if (!user?.id) {
|
||||
toast({
|
||||
title: 'Authentication Required',
|
||||
@@ -59,7 +59,7 @@ export function ItemEditDialog({ item, open, onOpenChange, onComplete }: ItemEdi
|
||||
|
||||
onComplete();
|
||||
onOpenChange(false);
|
||||
} catch (error) {
|
||||
} catch (error: unknown) {
|
||||
const errorMsg = getErrorMessage(error);
|
||||
toast({
|
||||
title: 'Error',
|
||||
@@ -74,11 +74,13 @@ export function ItemEditDialog({ item, open, onOpenChange, onComplete }: ItemEdi
|
||||
const handlePhotoSubmit = async (caption: string, credit: string) => {
|
||||
const photoData = {
|
||||
...item.item_data,
|
||||
photos: item.item_data.photos?.map((photo: any) => ({
|
||||
...photo,
|
||||
caption,
|
||||
credit,
|
||||
})),
|
||||
photos: Array.isArray(item.item_data.photos)
|
||||
? item.item_data.photos.map((photo: unknown) => ({
|
||||
...(typeof photo === 'object' && photo !== null ? photo : {}),
|
||||
caption,
|
||||
credit,
|
||||
}))
|
||||
: [],
|
||||
};
|
||||
await handleSubmit(photoData);
|
||||
};
|
||||
@@ -211,13 +213,19 @@ export function ItemEditDialog({ item, open, onOpenChange, onComplete }: ItemEdi
|
||||
}
|
||||
|
||||
// Simple photo editing form for caption and credit
|
||||
interface PhotoItem {
|
||||
url: string;
|
||||
caption?: string;
|
||||
credit?: string;
|
||||
}
|
||||
|
||||
function PhotoEditForm({
|
||||
photos,
|
||||
onSubmit,
|
||||
onCancel,
|
||||
submitting
|
||||
}: {
|
||||
photos: any[];
|
||||
photos: PhotoItem[];
|
||||
onSubmit: (caption: string, credit: string) => void;
|
||||
onCancel: () => void;
|
||||
submitting: boolean;
|
||||
|
||||
@@ -46,5 +46,25 @@ export interface TempRideModelData {
|
||||
banner_assignment?: number | null;
|
||||
card_assignment?: number | null;
|
||||
};
|
||||
_technical_specifications?: unknown[];
|
||||
_technical_specifications?: TechnicalSpecification[];
|
||||
}
|
||||
|
||||
export interface TechnicalSpecification {
|
||||
id?: string;
|
||||
spec_name: string;
|
||||
spec_value: string;
|
||||
spec_type: 'string' | 'number' | 'boolean' | 'date';
|
||||
category?: string;
|
||||
unit?: string;
|
||||
display_order: number;
|
||||
}
|
||||
|
||||
export interface CoasterStat {
|
||||
id?: string;
|
||||
stat_type: string;
|
||||
value: string;
|
||||
unit?: string;
|
||||
}
|
||||
|
||||
export type CoasterStatValue = string | number | null;
|
||||
export type TechnicalSpecValue = string | number | null;
|
||||
|
||||
@@ -9,7 +9,7 @@ export interface UserIdentity {
|
||||
email?: string;
|
||||
full_name?: string;
|
||||
avatar_url?: string;
|
||||
[key: string]: any;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
provider: 'google' | 'discord' | 'email' | 'github' | string;
|
||||
created_at: string;
|
||||
|
||||
@@ -52,3 +52,29 @@ export interface LocationInfoSettings {
|
||||
accessibility: AccessibilityOptions;
|
||||
unitPreferences: UnitPreferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Location data structure
|
||||
*/
|
||||
export interface LocationData {
|
||||
country?: string;
|
||||
state_province?: string;
|
||||
city?: string;
|
||||
latitude?: number;
|
||||
longitude?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for location data
|
||||
*/
|
||||
export function isLocationData(data: unknown): data is LocationData {
|
||||
if (typeof data !== 'object' || data === null) return false;
|
||||
const loc = data as Record<string, unknown>;
|
||||
return (
|
||||
(loc.country === undefined || typeof loc.country === 'string') &&
|
||||
(loc.state_province === undefined || typeof loc.state_province === 'string') &&
|
||||
(loc.city === undefined || typeof loc.city === 'string') &&
|
||||
(loc.latitude === undefined || typeof loc.latitude === 'number') &&
|
||||
(loc.longitude === undefined || typeof loc.longitude === 'number')
|
||||
);
|
||||
}
|
||||
|
||||
@@ -29,6 +29,22 @@ export interface NormalizedPhoto {
|
||||
|
||||
// Photo data source types
|
||||
export type PhotoDataSource =
|
||||
| { type: 'review'; photos: any[] }
|
||||
| { type: 'submission_jsonb'; photos: any[] }
|
||||
| { type: 'review'; photos: PhotoItem[] }
|
||||
| { type: 'submission_jsonb'; photos: PhotoItem[] }
|
||||
| { type: 'submission_items'; items: PhotoSubmissionItem[] };
|
||||
|
||||
// Type guard for photo arrays
|
||||
export function isPhotoItem(data: unknown): data is PhotoItem {
|
||||
return (
|
||||
typeof data === 'object' &&
|
||||
data !== null &&
|
||||
'id' in data &&
|
||||
'url' in data &&
|
||||
'filename' in data
|
||||
);
|
||||
}
|
||||
|
||||
// Type guard for photo arrays
|
||||
export function isPhotoItemArray(data: unknown): data is PhotoItem[] {
|
||||
return Array.isArray(data) && (data.length === 0 || isPhotoItem(data[0]));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user