Compare commits

...

3 Commits

Author SHA1 Message Date
gpt-engineer-app[bot]
2ce837f376 Fix type errors in admin components 2025-11-03 01:24:54 +00:00
gpt-engineer-app[bot]
8281fb9852 Code edited in Lovable Code Editor 2025-11-03 01:22:13 +00:00
gpt-engineer-app[bot]
8ce7775324 Fix remaining any types and enable strict mode 2025-11-03 01:20:50 +00:00
14 changed files with 84 additions and 48 deletions

View File

@@ -163,7 +163,11 @@ export function ManufacturerForm({ onSubmit, onCancel, initialData }: Manufactur
{/* Additional Details */} {/* Additional Details */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<FlexibleDateInput <FlexibleDateInput
value={watch('founded_date') ? parseDateOnly(watch('founded_date')) : undefined} value={(() => {
const dateValue = watch('founded_date');
if (!dateValue) return undefined;
return parseDateOnly(dateValue);
})()}
precision={(watch('founded_date_precision') as DatePrecision) || 'year'} precision={(watch('founded_date_precision') as DatePrecision) || 'year'}
onChange={(date, precision) => { onChange={(date, precision) => {
setValue('founded_date', date ? toDateOnly(date) : undefined); setValue('founded_date', date ? toDateOnly(date) : undefined);

View File

@@ -9,10 +9,10 @@ import { supabase } from '@/integrations/supabase/client';
import { format } from 'date-fns'; import { format } from 'date-fns';
interface DuplicateStats { interface DuplicateStats {
date: string; date: string | null;
total_attempts: number; total_attempts: number | null;
duplicates_prevented: number; duplicates_prevented: number | null;
prevention_rate: number; prevention_rate: number | null;
health_status: 'healthy' | 'warning' | 'critical'; health_status: 'healthy' | 'warning' | 'critical';
} }
@@ -20,11 +20,11 @@ interface RecentDuplicate {
id: string; id: string;
user_id: string; user_id: string;
channel: string; channel: string;
idempotency_key: string; idempotency_key: string | null;
created_at: string; created_at: string;
profiles?: { profiles?: {
username: string; username: string;
display_name: string; display_name: string | null;
}; };
} }
@@ -165,11 +165,11 @@ export function NotificationDebugPanel() {
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{stats.map((stat) => ( {stats.map((stat) => (
<TableRow key={stat.date}> <TableRow key={stat.date || 'unknown'}>
<TableCell>{format(new Date(stat.date), 'MMM d, yyyy')}</TableCell> <TableCell>{stat.date ? format(new Date(stat.date), 'MMM d, yyyy') : 'N/A'}</TableCell>
<TableCell className="text-right">{stat.total_attempts}</TableCell> <TableCell className="text-right">{stat.total_attempts ?? 0}</TableCell>
<TableCell className="text-right">{stat.duplicates_prevented}</TableCell> <TableCell className="text-right">{stat.duplicates_prevented ?? 0}</TableCell>
<TableCell className="text-right">{stat.prevention_rate.toFixed(1)}%</TableCell> <TableCell className="text-right">{stat.prevention_rate !== null ? stat.prevention_rate.toFixed(1) : 'N/A'}%</TableCell>
<TableCell>{getHealthBadge(stat.health_status)}</TableCell> <TableCell>{getHealthBadge(stat.health_status)}</TableCell>
</TableRow> </TableRow>
))} ))}

View File

@@ -375,7 +375,7 @@ export function ParkForm({ onSubmit, onCancel, initialData, isEditing = false }:
{/* Dates */} {/* Dates */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<FlexibleDateInput <FlexibleDateInput
value={watch('opening_date') ? parseDateOnly(watch('opening_date')) : undefined} value={watch('opening_date') ? parseDateOnly(watch('opening_date')!) : undefined}
precision={(watch('opening_date_precision') as DatePrecision) || 'day'} precision={(watch('opening_date_precision') as DatePrecision) || 'day'}
onChange={(date, precision) => { onChange={(date, precision) => {
setValue('opening_date', date ? toDateOnly(date) : undefined); setValue('opening_date', date ? toDateOnly(date) : undefined);
@@ -388,7 +388,7 @@ export function ParkForm({ onSubmit, onCancel, initialData, isEditing = false }:
/> />
<FlexibleDateInput <FlexibleDateInput
value={watch('closing_date') ? parseDateOnly(watch('closing_date')) : undefined} value={watch('closing_date') ? parseDateOnly(watch('closing_date')!) : undefined}
precision={(watch('closing_date_precision') as DatePrecision) || 'day'} precision={(watch('closing_date_precision') as DatePrecision) || 'day'}
onChange={(date, precision) => { onChange={(date, precision) => {
setValue('closing_date', date ? toDateOnly(date) : undefined); setValue('closing_date', date ? toDateOnly(date) : undefined);
@@ -632,7 +632,7 @@ export function ParkForm({ onSubmit, onCancel, initialData, isEditing = false }:
{/* Images */} {/* Images */}
<EntityMultiImageUploader <EntityMultiImageUploader
mode={isEditing ? 'edit' : 'create'} mode={isEditing ? 'edit' : 'create'}
value={watch('images')} value={watch('images') as ImageAssignments}
onChange={(images: ImageAssignments) => setValue('images', images)} onChange={(images: ImageAssignments) => setValue('images', images)}
entityType="park" entityType="park"
entityId={isEditing ? initialData?.id : undefined} entityId={isEditing ? initialData?.id : undefined}
@@ -663,7 +663,7 @@ export function ParkForm({ onSubmit, onCancel, initialData, isEditing = false }:
<Dialog open={isOperatorModalOpen} onOpenChange={setIsOperatorModalOpen}> <Dialog open={isOperatorModalOpen} onOpenChange={setIsOperatorModalOpen}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto"> <DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<OperatorForm <OperatorForm
initialData={tempNewOperator} initialData={tempNewOperator || undefined}
onSubmit={(data) => { onSubmit={(data) => {
setTempNewOperator(data); setTempNewOperator(data);
setIsOperatorModalOpen(false); setIsOperatorModalOpen(false);
@@ -678,7 +678,7 @@ export function ParkForm({ onSubmit, onCancel, initialData, isEditing = false }:
<Dialog open={isPropertyOwnerModalOpen} onOpenChange={setIsPropertyOwnerModalOpen}> <Dialog open={isPropertyOwnerModalOpen} onOpenChange={setIsPropertyOwnerModalOpen}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto"> <DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<PropertyOwnerForm <PropertyOwnerForm
initialData={tempNewPropertyOwner} initialData={tempNewPropertyOwner || undefined}
onSubmit={(data) => { onSubmit={(data) => {
setTempNewPropertyOwner(data); setTempNewPropertyOwner(data);
setIsPropertyOwnerModalOpen(false); setIsPropertyOwnerModalOpen(false);

View File

@@ -326,10 +326,10 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }:
_technical_specifications: technicalSpecs, _technical_specifications: technicalSpecs,
_coaster_statistics: coasterStats, _coaster_statistics: coasterStats,
_name_history: formerNames, _name_history: formerNames,
_tempNewPark: tempNewPark, _tempNewPark: tempNewPark || undefined,
_tempNewManufacturer: tempNewManufacturer, _tempNewManufacturer: tempNewManufacturer || undefined,
_tempNewDesigner: tempNewDesigner, _tempNewDesigner: tempNewDesigner || undefined,
_tempNewRideModel: tempNewRideModel _tempNewRideModel: tempNewRideModel || undefined
}; };
// Pass clean data to parent with extended fields // Pass clean data to parent with extended fields
@@ -492,7 +492,7 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }:
// Show combobox for existing manufacturers // Show combobox for existing manufacturers
<Combobox <Combobox
options={manufacturers} options={manufacturers}
value={watch('manufacturer_id')} value={watch('manufacturer_id') || undefined}
onValueChange={(value) => { onValueChange={(value) => {
setValue('manufacturer_id', value); setValue('manufacturer_id', value);
setSelectedManufacturerId(value); setSelectedManufacturerId(value);
@@ -557,7 +557,7 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }:
<> <>
<Combobox <Combobox
options={rideModels} options={rideModels}
value={watch('ride_model_id')} value={watch('ride_model_id') || undefined}
onValueChange={(value) => setValue('ride_model_id', value)} onValueChange={(value) => setValue('ride_model_id', value)}
placeholder="Select model" placeholder="Select model"
searchPlaceholder="Search models..." searchPlaceholder="Search models..."
@@ -595,7 +595,7 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }:
{/* Dates */} {/* Dates */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<FlexibleDateInput <FlexibleDateInput
value={watch('opening_date') ? parseDateOnly(watch('opening_date')) : undefined} value={watch('opening_date') ? parseDateOnly(watch('opening_date')!) : undefined}
precision={(watch('opening_date_precision') as DatePrecision) || 'day'} precision={(watch('opening_date_precision') as DatePrecision) || 'day'}
onChange={(date, precision) => { onChange={(date, precision) => {
setValue('opening_date', date ? toDateOnly(date) : undefined); setValue('opening_date', date ? toDateOnly(date) : undefined);
@@ -608,7 +608,7 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }:
/> />
<FlexibleDateInput <FlexibleDateInput
value={watch('closing_date') ? parseDateOnly(watch('closing_date')) : undefined} value={watch('closing_date') ? parseDateOnly(watch('closing_date')!) : undefined}
precision={(watch('closing_date_precision') as DatePrecision) || 'day'} precision={(watch('closing_date_precision') as DatePrecision) || 'day'}
onChange={(date, precision) => { onChange={(date, precision) => {
setValue('closing_date', date ? toDateOnly(date) : undefined); setValue('closing_date', date ? toDateOnly(date) : undefined);
@@ -1390,7 +1390,7 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }:
<DialogTitle>Create New Designer</DialogTitle> <DialogTitle>Create New Designer</DialogTitle>
</DialogHeader> </DialogHeader>
<ManufacturerForm <ManufacturerForm
initialData={tempNewDesigner} initialData={tempNewDesigner || undefined}
onSubmit={(data) => { onSubmit={(data) => {
setTempNewDesigner(data); setTempNewDesigner(data);
setIsDesignerModalOpen(false); setIsDesignerModalOpen(false);
@@ -1413,7 +1413,7 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }:
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<ManufacturerForm <ManufacturerForm
initialData={tempNewManufacturer} initialData={tempNewManufacturer || undefined}
onSubmit={(data) => { onSubmit={(data) => {
setTempNewManufacturer(data); setTempNewManufacturer(data);
setSelectedManufacturerName(data.name); setSelectedManufacturerName(data.name);
@@ -1442,9 +1442,9 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }:
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<RideModelForm <RideModelForm
manufacturerName={selectedManufacturerName || tempNewManufacturer?.name} manufacturerName={selectedManufacturerName || tempNewManufacturer?.name || ''}
manufacturerId={selectedManufacturerId} manufacturerId={selectedManufacturerId}
initialData={tempNewRideModel} initialData={tempNewRideModel || undefined}
onSubmit={(data) => { onSubmit={(data) => {
setTempNewRideModel(data); setTempNewRideModel(data);
setIsModelModalOpen(false); setIsModelModalOpen(false);

View File

@@ -730,7 +730,7 @@ export const SystemActivityLog = forwardRef<SystemActivityLogRef, SystemActivity
} }
default: default:
return null; return <span>Unknown activity type</span>;
} }
}; };

View File

@@ -18,6 +18,7 @@ import { setAuthMethod } from '@/lib/sessionFlags';
import { validateEmailNotDisposable } from '@/lib/emailValidation'; import { validateEmailNotDisposable } from '@/lib/emailValidation';
import { getErrorMessage } from '@/lib/errorHandler'; import { getErrorMessage } from '@/lib/errorHandler';
import { logger } from '@/lib/logger'; import { logger } from '@/lib/logger';
import type { SignInOptions } from '@/types/supabase-auth';
interface AuthModalProps { interface AuthModalProps {
open: boolean; open: boolean;
@@ -70,7 +71,7 @@ export function AuthModal({ open, onOpenChange, defaultTab = 'signin' }: AuthMod
setSignInCaptchaToken(null); setSignInCaptchaToken(null);
try { try {
const signInOptions: any = { const signInOptions: SignInOptions = {
email: formData.email, email: formData.email,
password: formData.password, password: formData.password,
}; };
@@ -125,7 +126,7 @@ export function AuthModal({ open, onOpenChange, defaultTab = 'signin' }: AuthMod
const { handlePostAuthFlow } = await import('@/lib/authService'); const { handlePostAuthFlow } = await import('@/lib/authService');
const postAuthResult = await handlePostAuthFlow(data.session, 'password'); const postAuthResult = await handlePostAuthFlow(data.session, 'password');
if (postAuthResult.success && postAuthResult.data.shouldRedirect) { if (postAuthResult.success && postAuthResult.data?.shouldRedirect) {
// Get the TOTP factor ID // Get the TOTP factor ID
const { data: factors } = await supabase.auth.mfa.listFactors(); const { data: factors } = await supabase.auth.mfa.listFactors();
const totpFactor = factors?.totp?.find(f => f.status === 'verified'); const totpFactor = factors?.totp?.find(f => f.status === 'verified');

View File

@@ -297,8 +297,9 @@ export function SubmissionReviewManager({
description: `Successfully approved ${selectedItemIds.size} item(s)${requestId ? ` (Request: ${requestId.substring(0, 8)})` : ''}`, description: `Successfully approved ${selectedItemIds.size} item(s)${requestId ? ` (Request: ${requestId.substring(0, 8)})` : ''}`,
}); });
const successCount = data.results.filter((r: any) => r.success).length; interface ApprovalResult { success: boolean; item_id: string; error?: string }
const failCount = data.results.filter((r: any) => !r.success).length; const successCount = data.results.filter((r: ApprovalResult) => r.success).length;
const failCount = data.results.filter((r: ApprovalResult) => !r.success).length;
const allFailed = failCount > 0 && successCount === 0; const allFailed = failCount > 0 && successCount === 0;
const someFailed = failCount > 0 && successCount > 0; const someFailed = failCount > 0 && successCount > 0;

View File

@@ -3,6 +3,7 @@ import { supabase } from '@/integrations/supabase/client';
import { logger } from '@/lib/logger'; import { logger } from '@/lib/logger';
import { getErrorMessage } from '@/lib/errorHandler'; import { getErrorMessage } from '@/lib/errorHandler';
import { MODERATION_CONSTANTS } from '@/lib/moderation/constants'; import { MODERATION_CONSTANTS } from '@/lib/moderation/constants';
import type { ModerationItem } from '@/types/moderation';
/** /**
* Profile data structure returned from the database * Profile data structure returned from the database
@@ -143,7 +144,7 @@ export function useProfileCache() {
* @param submissions - Array of submissions with user_id and reviewer_id * @param submissions - Array of submissions with user_id and reviewer_id
* @returns Map of userId -> profile for all users involved * @returns Map of userId -> profile for all users involved
*/ */
const fetchForSubmissions = useCallback(async (submissions: any[]): Promise<Map<string, CachedProfile>> => { const fetchForSubmissions = useCallback(async (submissions: ModerationItem[]): Promise<Map<string, CachedProfile>> => {
const userIds = submissions.map(s => s.user_id).filter(Boolean); const userIds = submissions.map(s => s.user_id).filter(Boolean);
const reviewerIds = submissions.map(s => s.reviewer_id).filter((id): id is string => !!id); const reviewerIds = submissions.map(s => s.reviewer_id).filter((id): id is string => !!id);
const allUserIds = [...new Set([...userIds, ...reviewerIds])]; const allUserIds = [...new Set([...userIds, ...reviewerIds])];

View File

@@ -12,6 +12,7 @@ import { logger } from '@/lib/logger';
import { getErrorMessage } from '@/lib/errorHandler'; import { getErrorMessage } from '@/lib/errorHandler';
import { MODERATION_CONSTANTS } from '@/lib/moderation/constants'; import { MODERATION_CONSTANTS } from '@/lib/moderation/constants';
import type { RealtimeChannel, RealtimePostgresChangesPayload } from '@supabase/supabase-js'; import type { RealtimeChannel, RealtimePostgresChangesPayload } from '@supabase/supabase-js';
import type { Json } from '@/integrations/supabase/types';
import type { ModerationItem, EntityFilter, StatusFilter } from '@/types/moderation'; import type { ModerationItem, EntityFilter, StatusFilter } from '@/types/moderation';
import type { useEntityCache } from './useEntityCache'; import type { useEntityCache } from './useEntityCache';
import type { useProfileCache } from './useProfileCache'; import type { useProfileCache } from './useProfileCache';
@@ -176,7 +177,7 @@ export function useRealtimeSubscriptions(
/** /**
* Resolve entity names for a submission * Resolve entity names for a submission
*/ */
const resolveEntityNames = useCallback(async (submission: any) => { const resolveEntityNames = useCallback(async (submission: { submission_type: string; content: Json }) => {
const content = submission.content as SubmissionContent; const content = submission.content as SubmissionContent;
let entityName = content?.name || 'Unknown'; let entityName = content?.name || 'Unknown';
let parkName: string | undefined; let parkName: string | undefined;

View File

@@ -1,5 +1,6 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import type { User } from '@supabase/supabase-js';
import { useAuth } from './useAuth'; import { useAuth } from './useAuth';
import { useUserRole } from './useUserRole'; import { useUserRole } from './useUserRole';
import { useRequireMFA } from './useRequireMFA'; import { useRequireMFA } from './useRequireMFA';
@@ -15,7 +16,7 @@ export interface AdminGuardState {
needsMFA: boolean; needsMFA: boolean;
/** Current authenticated user */ /** Current authenticated user */
user: any; user: User | null;
} }
/** /**

View File

@@ -35,13 +35,13 @@ export interface Park {
id: string; id: string;
name: string; name: string;
slug: string; slug: string;
description?: string; description?: string | null;
status: string; // Allow any string from database status: string; // Allow any string from database
park_type: string; // Allow any string from database park_type: string; // Allow any string from database
opening_date?: string; opening_date?: string | null;
opening_date_precision?: string; opening_date_precision?: string | null;
closing_date?: string; closing_date?: string | null;
closing_date_precision?: string; closing_date_precision?: string | null;
website_url?: string; website_url?: string;
phone?: string; phone?: string;
email?: string; email?: string;
@@ -132,7 +132,7 @@ export interface Ride {
id: string; id: string;
name: string; name: string;
slug: string; slug: string;
description?: string; description?: string | null;
park?: Park | { name: string; slug: string; location?: Location }; // Allow partial park data park?: Park | { name: string; slug: string; location?: Location }; // Allow partial park data
ride_model?: RideModel; ride_model?: RideModel;
manufacturer?: Company; manufacturer?: Company;
@@ -140,10 +140,10 @@ export interface Ride {
category: string; // Allow any string from database category: string; // Allow any string from database
ride_sub_type?: string; // Sub-category like "Flying Coaster", "Inverted Coaster", etc. ride_sub_type?: string; // Sub-category like "Flying Coaster", "Inverted Coaster", etc.
status: string; // Allow any string from database status: string; // Allow any string from database
opening_date?: string; opening_date?: string | null;
opening_date_precision?: string; opening_date_precision?: string | null;
closing_date?: string; closing_date?: string | null;
closing_date_precision?: string; closing_date_precision?: string | null;
height_requirement?: number; height_requirement?: number;
age_requirement?: number; age_requirement?: number;
capacity_per_hour?: number; capacity_per_hour?: number;

View File

@@ -164,6 +164,9 @@ export interface ModerationItem {
reviewed_at?: string; reviewed_at?: string;
/** ID of the moderator who reviewed this item */ /** ID of the moderator who reviewed this item */
reviewer_id?: string;
/** Username of the moderator who reviewed this item */
reviewed_by?: string; reviewed_by?: string;
/** Notes left by the reviewing moderator */ /** Notes left by the reviewing moderator */

View File

@@ -0,0 +1,24 @@
/**
* Extended Supabase Auth Types
* Supabase auth methods accept options objects not fully typed in official SDK
*/
export interface SignInOptions {
email: string;
password: string;
options?: {
captchaToken?: string;
};
}
export interface SignUpOptions {
email: string;
password: string;
options?: {
data?: {
username?: string;
display_name?: string;
};
captchaToken?: string;
};
}

View File

@@ -15,7 +15,7 @@
"jsx": "react-jsx", "jsx": "react-jsx",
/* Linting */ /* Linting */
"strict": false, "strict": true,
"noUnusedLocals": false, "noUnusedLocals": false,
"noUnusedParameters": false, "noUnusedParameters": false,
"noImplicitAny": false, "noImplicitAny": false,