Fix: Enable TypeScript strict mode

This commit is contained in:
gpt-engineer-app[bot]
2025-10-20 00:26:49 +00:00
parent 84188b94f2
commit d9a912f443
11 changed files with 174 additions and 29 deletions

View File

@@ -20,12 +20,19 @@ export default tseslint.config(
rules: {
...reactHooks.configs.recommended.rules,
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error",
"@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",
"@typescript-eslint/no-unsafe-argument": "error",
"@typescript-eslint/explicit-function-return-type": ["error", {
allowExpressions: true,
allowTypedFunctionExpressions: true,
allowHigherOrderFunctions: true,
allowDirectConstAssertionInArrowFunctions: true,
}],
},
},
);

View File

@@ -1,6 +1,7 @@
import { useState, useEffect } from 'react';
import { supabase } from '@/integrations/supabase/client';
import { toast } from 'sonner';
import { getSessionAAL } from '@/types/supabase-session';
import { getErrorMessage } from '@/lib/errorHandler';
import { useRequireMFA } from '@/hooks/useRequireMFA';
import {
@@ -36,9 +37,9 @@ export function MFARemovalDialog({ open, onOpenChange, factorId, onSuccess }: MF
// Phase 1: Check AAL2 requirement on dialog open
useEffect(() => {
if (open) {
const checkAalLevel = async () => {
const checkAalLevel = async (): Promise<void> => {
const { data: { session } } = await supabase.auth.getSession();
const currentAal = (session as any)?.aal || 'aal1';
const currentAal = getSessionAAL(session);
if (currentAal !== 'aal2') {
toast.error('Please verify your identity with MFA before making security changes');
@@ -114,7 +115,7 @@ export function MFARemovalDialog({ open, onOpenChange, factorId, onSuccess }: MF
// Phase 1: Verify session is at AAL2 after TOTP verification
const { data: { session } } = await supabase.auth.getSession();
const currentAal = (session as any)?.aal || 'aal1';
const currentAal = getSessionAAL(session);
if (currentAal !== 'aal2') {
throw new Error('Session must be at AAL2 to remove MFA');
@@ -122,7 +123,7 @@ export function MFARemovalDialog({ open, onOpenChange, factorId, onSuccess }: MF
toast.success('TOTP code verified');
setStep('confirm');
} catch (error) {
} catch (error: unknown) {
console.error('TOTP verification failed:', error);
toast.error(getErrorMessage(error));
} finally {

View File

@@ -220,7 +220,7 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig):
// Show error toast when query fails
useEffect(() => {
if (queueQuery.error) {
logger.error('❌ Queue query error:', queueQuery.error);
logger.error('❌ Queue query error:', { error: getErrorMessage(queueQuery.error) });
toast({
variant: 'destructive',
title: 'Failed to Load Queue',

View File

@@ -1,6 +1,7 @@
import { useRef, useCallback } from 'react';
import { supabase } from '@/integrations/supabase/client';
import { logger } from '@/lib/logger';
import { getErrorMessage } from '@/lib/errorHandler';
import { MODERATION_CONSTANTS } from '@/lib/moderation/constants';
/**
@@ -105,7 +106,7 @@ export function useProfileCache() {
.in('user_id', uncachedIds);
if (error) {
logger.error('Error fetching profiles:', error);
logger.error('Error fetching profiles:', { error: getErrorMessage(error) });
return [];
}

View File

@@ -9,6 +9,7 @@ import { useQuery, useQueryClient } from '@tanstack/react-query';
import { fetchSubmissions, type QueryConfig } from '@/lib/moderation/queries';
import { supabase } from '@/integrations/supabase/client';
import { logger } from '@/lib/logger';
import { getErrorMessage } from '@/lib/errorHandler';
import { MODERATION_CONSTANTS } from '@/lib/moderation/constants';
import type {
ModerationItem,
@@ -126,7 +127,7 @@ export function useQueueQuery(config: UseQueueQueryConfig): UseQueueQueryReturn
const result = await fetchSubmissions(supabase, queryConfig);
if (result.error) {
logger.error('❌ [TanStack Query] Error:', result.error);
logger.error('❌ [TanStack Query] Error:', { error: getErrorMessage(result.error) });
throw result.error;
}

View File

@@ -9,6 +9,7 @@ import { useEffect, useRef, useState, useCallback } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import { supabase } from '@/integrations/supabase/client';
import { logger } from '@/lib/logger';
import { getErrorMessage } from '@/lib/errorHandler';
import { MODERATION_CONSTANTS } from '@/lib/moderation/constants';
import type { RealtimeChannel, RealtimePostgresChangesPayload } from '@supabase/supabase-js';
import type { ModerationItem, EntityFilter, StatusFilter } from '@/types/moderation';
@@ -165,7 +166,7 @@ export function useRealtimeSubscriptions(
.single();
if (error || !submission) {
logger.error('Error fetching submission details:', error);
logger.error('Error fetching submission details:', { error: getErrorMessage(error) });
return null;
}

View File

@@ -4,6 +4,7 @@ import { ImageAssignments } from '@/components/upload/EntityMultiImageUploader';
import { uploadPendingImages } from './imageUploadHelper';
import type { ProcessedImage } from './supabaseHelpers';
import { extractChangedFields } from './submissionChangeDetection';
import type { CompanyDatabaseRecord, TimelineEventDatabaseRecord } from '@/types/company-data';
/**
* ═══════════════════════════════════════════════════════════════════
@@ -723,7 +724,7 @@ export async function submitManufacturerUpdate(
item_type: 'manufacturer',
action_type: 'edit',
item_data: {
...extractChangedFields(data, existingCompany as any),
...extractChangedFields(data, existingCompany as Partial<CompanyDatabaseRecord>),
company_id: companyId, // Always include for relational integrity
company_type: 'manufacturer', // Always include for entity type discrimination
images: processedImages as unknown as Json
@@ -831,7 +832,7 @@ export async function submitDesignerUpdate(
item_type: 'designer',
action_type: 'edit',
item_data: {
...extractChangedFields(data, existingCompany as any),
...extractChangedFields(data, existingCompany as Partial<CompanyDatabaseRecord>),
company_id: companyId, // Always include for relational integrity
company_type: 'designer', // Always include for entity type discrimination
images: processedImages as unknown as Json
@@ -939,7 +940,7 @@ export async function submitOperatorUpdate(
item_type: 'operator',
action_type: 'edit',
item_data: {
...extractChangedFields(data, existingCompany as any),
...extractChangedFields(data, existingCompany as Partial<CompanyDatabaseRecord>),
company_id: companyId, // Always include for relational integrity
company_type: 'operator', // Always include for entity type discrimination
images: processedImages as unknown as Json
@@ -1047,7 +1048,7 @@ export async function submitPropertyOwnerUpdate(
item_type: 'property_owner',
action_type: 'edit',
item_data: {
...extractChangedFields(data, existingCompany as any),
...extractChangedFields(data, existingCompany as Partial<CompanyDatabaseRecord>),
company_id: companyId, // Always include for relational integrity
company_type: 'property_owner', // Always include for entity type discrimination
images: processedImages as unknown as Json
@@ -1128,7 +1129,7 @@ export async function submitTimelineEvent(
p_user_id: userId,
p_submission_type: 'milestone',
p_content: content,
p_items: items as any,
p_items: items as unknown as Json[],
});
if (error || !submissionId) {
@@ -1169,9 +1170,9 @@ export async function submitTimelineEventUpdate(
}
// Extract only changed fields from form data
const changedFields = extractChangedFields(data, originalEvent as any);
const changedFields = extractChangedFields(data, originalEvent as Partial<Record<string, unknown>>);
const itemData: Record<string, any> = {
const itemData: Record<string, unknown> = {
...changedFields,
// Always include entity reference (for FK integrity)
entity_type: originalEvent.entity_type,

View File

@@ -4,7 +4,7 @@ import { logger } from './logger';
export type ErrorContext = {
action: string;
userId?: string;
metadata?: Record<string, any>;
metadata?: Record<string, unknown>;
};
export class AppError extends Error {
@@ -66,11 +66,27 @@ export const handleInfo = (
* 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;
export function 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';
};
}
/**
* Type guard to check if error has a code property
*/
export function hasErrorCode(error: unknown): error is { code: string } {
return (
error !== null &&
typeof error === 'object' &&
'code' in error &&
typeof (error as { code: unknown }).code === 'string'
);
}

View File

@@ -7,24 +7,22 @@
const isDev = import.meta.env.DEV;
type LogContext = {
[key: string]: any;
};
type LogContext = Record<string, unknown>;
export const logger = {
log: (...args: any[]) => {
log: (...args: unknown[]): void => {
if (isDev) console.log(...args);
},
error: (message: string, context?: LogContext) => {
error: (message: string, context?: LogContext): void => {
console.error(message, context); // Always log errors
},
warn: (...args: any[]) => {
warn: (...args: unknown[]): void => {
if (isDev) console.warn(...args);
},
info: (...args: any[]) => {
info: (...args: unknown[]): void => {
if (isDev) console.info(...args);
},
debug: (...args: any[]) => {
debug: (...args: unknown[]): void => {
if (isDev) console.debug(...args);
}
};

84
src/types/company-data.ts Normal file
View File

@@ -0,0 +1,84 @@
/**
* Company Data Types
*
* Type-safe interfaces for company database records and form data.
* These types prevent `as any` casts when working with company entities.
*/
export interface CompanyDatabaseRecord {
id: string;
name: string;
slug: string;
description?: string | null;
company_type: 'manufacturer' | 'designer' | 'operator' | 'property_owner';
person_type: 'company' | 'individual' | 'firm' | 'organization';
website_url?: string | null;
founded_year?: number | null;
headquarters_location?: string | null;
logo_url?: string | null;
banner_image_url?: string | null;
card_image_url?: string | null;
created_at: string;
updated_at: string;
// Stats fields
total_rides?: number;
average_rating?: number;
review_count?: number;
}
export interface TimelineEventDatabaseRecord {
id: string;
entity_id: string;
entity_type: 'park' | 'ride' | 'company' | 'ride_model';
event_type: string;
event_date: string;
event_date_precision: 'day' | 'month' | 'year';
title: string;
description?: string | null;
from_value?: string | null;
to_value?: string | null;
from_entity_id?: string | null;
to_entity_id?: string | null;
from_location_id?: string | null;
to_location_id?: string | null;
display_order: number;
created_by?: string | null;
approved_by?: string | null;
submission_id?: string | null;
created_at: string;
updated_at: string;
}
export interface LocationData {
country?: string;
state_province?: string;
city?: string;
latitude?: number;
longitude?: number;
}
/**
* Type guard for company database records
*/
export function isCompanyRecord(data: unknown): data is CompanyDatabaseRecord {
return (
typeof data === 'object' &&
data !== null &&
'id' in data &&
'name' in data &&
'company_type' in data
);
}
/**
* Type guard for timeline event records
*/
export function isTimelineEventRecord(data: unknown): data is TimelineEventDatabaseRecord {
return (
typeof data === 'object' &&
data !== null &&
'id' in data &&
'entity_type' in data &&
'event_type' in data
);
}

View File

@@ -0,0 +1,35 @@
/**
* Extended Supabase Session Types
*
* Supabase session objects contain additional properties not in the official types.
* These extended types provide type safety for AAL (Authentication Assurance Level) and other fields.
*/
import type { Session } from '@supabase/supabase-js';
export type AALLevel = 'aal1' | 'aal2';
/**
* Extended session type with AAL property
* Supabase auth sessions include this property but it's not in the official types
*/
export interface ExtendedSession extends Session {
aal?: AALLevel;
}
/**
* Type guard to check if session has AAL property
*/
export function hasAAL(session: Session | null): session is ExtendedSession {
return session !== null && 'aal' in session;
}
/**
* Safely extract AAL level from session
*/
export function getSessionAAL(session: Session | null): AALLevel {
if (hasAAL(session) && session.aal) {
return session.aal;
}
return 'aal1';
}