Fix ESLint errors

This commit is contained in:
gpt-engineer-app[bot]
2025-10-29 23:27:37 +00:00
parent 017879ba21
commit 41f4e3b920
16 changed files with 80 additions and 184 deletions

View File

@@ -66,7 +66,7 @@ export function analyzeHeuristics(userAgent: string, headers: Record<string, str
} }
// Version number patterns typical of bots (e.g., "v1.0", "version/2.3") // Version number patterns typical of bots (e.g., "v1.0", "version/2.3")
if (userAgent.match(/\b(v|version)[\/\s]?\d+\.\d+/i)) { if (userAgent.match(/\b(v|version)[/\s]?\d+\.\d+/i)) {
signals.push('version-pattern'); signals.push('version-pattern');
confidence += 10; confidence += 10;
} }

View File

@@ -5,12 +5,12 @@ import { join } from 'path';
type VercelRequest = IncomingMessage & { type VercelRequest = IncomingMessage & {
query: { [key: string]: string | string[] }; query: { [key: string]: string | string[] };
cookies: { [key: string]: string }; cookies: { [key: string]: string };
body: any; body: unknown;
}; };
type VercelResponse = ServerResponse & { type VercelResponse = ServerResponse & {
status: (code: number) => VercelResponse; status: (code: number) => VercelResponse;
json: (data: any) => VercelResponse; json: (data: unknown) => VercelResponse;
send: (body: string) => VercelResponse; send: (body: string) => VercelResponse;
}; };
@@ -66,7 +66,7 @@ async function getPageData(pathname: string, fullUrl: string): Promise<PageData>
} }
// Individual ride page: /parks/{park-slug}/rides/{ride-slug} // Individual ride page: /parks/{park-slug}/rides/{ride-slug}
if (normalizedPath.match(/^\/parks\/[^\/]+\/rides\/[^\/]+$/)) { if (normalizedPath.match(/^\/parks\/[^/]+\/rides\/[^/]+$/)) {
const parts = normalizedPath.split('/'); const parts = normalizedPath.split('/');
const rideSlug = parts[4]; const rideSlug = parts[4];
@@ -178,7 +178,7 @@ function injectOGTags(html: string, ogTags: string): string {
return html; return html;
} }
export default async function handler(req: VercelRequest, res: VercelResponse) { export default async function handler(req: VercelRequest, res: VercelResponse): Promise<void> {
try { try {
const userAgent = req.headers['user-agent'] || ''; const userAgent = req.headers['user-agent'] || '';
const fullUrl = `https://${req.headers.host}${req.url}`; const fullUrl = `https://${req.headers.host}${req.url}`;
@@ -239,7 +239,7 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
const html = readFileSync(htmlPath, 'utf-8'); const html = readFileSync(htmlPath, 'utf-8');
res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.status(200).send(html); res.status(200).send(html);
} catch (fallbackError) { } catch {
res.status(500).send('Internal Server Error'); res.status(500).send('Internal Server Error');
} }
} }

View File

@@ -79,7 +79,7 @@ const queryClient = new QueryClient({
}, },
}); });
function AppContent() { function AppContent(): React.JSX.Element {
return ( return (
<TooltipProvider> <TooltipProvider>
<BrowserRouter> <BrowserRouter>
@@ -154,7 +154,7 @@ function AppContent() {
); );
} }
const App = () => ( const App = (): React.JSX.Element => (
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<AuthProvider> <AuthProvider>
<AuthModalProvider> <AuthModalProvider>

View File

@@ -66,8 +66,8 @@ export function AdminPageLayout({
getAdminPanelPollInterval, getAdminPanelPollInterval,
} = useAdminSettings(); } = useAdminSettings();
const refreshMode = getAdminPanelRefreshMode(); const refreshMode = getAdminPanelRefreshMode() as 'auto' | 'manual';
const pollInterval = getAdminPanelPollInterval(); const pollInterval = getAdminPanelPollInterval() as number;
const { lastUpdated } = useModerationStats({ const { lastUpdated } = useModerationStats({
enabled: isAuthorized && showRefreshControls, enabled: isAuthorized && showRefreshControls,
@@ -84,9 +84,9 @@ export function AdminPageLayout({
return ( return (
<AdminLayout <AdminLayout
onRefresh={showRefreshControls ? handleRefreshClick : undefined} onRefresh={showRefreshControls ? handleRefreshClick : undefined}
refreshMode={showRefreshControls ? refreshMode : undefined} refreshMode={showRefreshControls ? (refreshMode as 'auto' | 'manual') : undefined}
pollInterval={showRefreshControls ? pollInterval : undefined} pollInterval={showRefreshControls ? pollInterval : undefined}
lastUpdated={showRefreshControls ? lastUpdated : undefined} lastUpdated={showRefreshControls ? (lastUpdated as Date) : undefined}
> >
<div className="space-y-6"> <div className="space-y-6">
<div> <div>
@@ -117,9 +117,9 @@ export function AdminPageLayout({
return ( return (
<AdminLayout <AdminLayout
onRefresh={showRefreshControls ? handleRefreshClick : undefined} onRefresh={showRefreshControls ? handleRefreshClick : undefined}
refreshMode={showRefreshControls ? refreshMode : undefined} refreshMode={showRefreshControls ? (refreshMode as 'auto' | 'manual') : undefined}
pollInterval={showRefreshControls ? pollInterval : undefined} pollInterval={showRefreshControls ? pollInterval : undefined}
lastUpdated={showRefreshControls ? lastUpdated : undefined} lastUpdated={showRefreshControls ? (lastUpdated as Date) : undefined}
> >
<div className="space-y-6"> <div className="space-y-6">
<div> <div>

View File

@@ -1,9 +1,7 @@
import { useState } from 'react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod'; import * as z from 'zod';
import { entitySchemas } from '@/lib/entityValidationSchemas'; import { entitySchemas } from '@/lib/entityValidationSchemas';
import { getErrorMessage } from '@/lib/errorHandler';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea'; import { Textarea } from '@/components/ui/textarea';
@@ -15,36 +13,12 @@ import { SlugField } from '@/components/ui/slug-field';
import { Ruler, Save, X } from 'lucide-react'; import { Ruler, Save, X } from 'lucide-react';
import { useUserRole } from '@/hooks/useUserRole'; import { useUserRole } from '@/hooks/useUserRole';
import { HeadquartersLocationInput } from './HeadquartersLocationInput'; import { HeadquartersLocationInput } from './HeadquartersLocationInput';
import { EntityMultiImageUploader, ImageAssignments } from '@/components/upload/EntityMultiImageUploader'; import { EntityMultiImageUploader } from '@/components/upload/EntityMultiImageUploader';
import { FlexibleDateInput, type DatePrecision } from '@/components/ui/flexible-date-input';
import { submitDesignerCreation, submitDesignerUpdate } from '@/lib/entitySubmissionHelpers';
import { useAuth } from '@/hooks/useAuth'; import { useAuth } from '@/hooks/useAuth';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { handleError } from '@/lib/errorHandler'; import { handleError } from '@/lib/errorHandler';
import { useNavigate } from 'react-router-dom';
import type { UploadedImage } from '@/types/company'; import type { UploadedImage } from '@/types/company';
// Raw form input state (before Zod transformation)
interface DesignerFormInput {
name: string;
slug: string;
company_type: 'designer' | 'manufacturer' | 'operator' | 'property_owner';
description?: string;
person_type: 'company' | 'individual' | 'firm' | 'organization';
founded_year?: string;
founded_date?: string;
founded_date_precision?: 'day' | 'month' | 'year';
headquarters_location?: string;
website_url?: string;
source_url?: string;
submission_notes?: string;
images?: {
uploaded: UploadedImage[];
banner_assignment?: number | null;
card_assignment?: number | null;
};
}
// Zod output type (after transformation) // Zod output type (after transformation)
type DesignerFormData = z.infer<typeof entitySchemas.designer>; type DesignerFormData = z.infer<typeof entitySchemas.designer>;
@@ -58,10 +32,9 @@ interface DesignerFormProps {
}>; }>;
} }
export function DesignerForm({ onSubmit, onCancel, initialData }: DesignerFormProps) { export function DesignerForm({ onSubmit, onCancel, initialData }: DesignerFormProps): React.JSX.Element {
const { isModerator } = useUserRole(); const { isModerator } = useUserRole();
const { user } = useAuth(); const { user } = useAuth();
const navigate = useNavigate();
const { const {
register, register,

View File

@@ -29,7 +29,7 @@ export function HeadquartersLocationInput({
onChange, onChange,
disabled = false, disabled = false,
className className
}: HeadquartersLocationInputProps) { }: HeadquartersLocationInputProps): React.JSX.Element {
const [mode, setMode] = useState<'search' | 'manual'>('search'); const [mode, setMode] = useState<'search' | 'manual'>('search');
const [searchQuery, setSearchQuery] = useState(''); const [searchQuery, setSearchQuery] = useState('');
const [results, setResults] = useState<LocationResult[]>([]); const [results, setResults] = useState<LocationResult[]>([]);
@@ -44,7 +44,7 @@ export function HeadquartersLocationInput({
return; return;
} }
const timeoutId = setTimeout(async () => { const timeoutId = setTimeout(async (): Promise<void> => {
setIsSearching(true); setIsSearching(true);
try { try {
const response = await fetch( const response = await fetch(
@@ -59,7 +59,7 @@ export function HeadquartersLocationInput({
); );
if (response.ok) { if (response.ok) {
const data = await response.json(); const data = await response.json() as LocationResult[];
setResults(data); setResults(data);
setShowResults(true); setShowResults(true);
} }
@@ -87,7 +87,7 @@ export function HeadquartersLocationInput({
return result.display_name; return result.display_name;
}; };
const handleSelectLocation = (result: LocationResult) => { const handleSelectLocation = (result: LocationResult): void => {
const formatted = formatLocation(result); const formatted = formatLocation(result);
onChange(formatted); onChange(formatted);
setSearchQuery(''); setSearchQuery('');
@@ -95,7 +95,7 @@ export function HeadquartersLocationInput({
setResults([]); setResults([]);
}; };
const handleClear = () => { const handleClear = (): void => {
onChange(''); onChange('');
setSearchQuery(''); setSearchQuery('');
setResults([]); setResults([]);

View File

@@ -41,7 +41,7 @@ interface LocationSearchProps {
className?: string; className?: string;
} }
export function LocationSearch({ onLocationSelect, initialLocationId, className }: LocationSearchProps) { export function LocationSearch({ onLocationSelect, initialLocationId, className }: LocationSearchProps): React.JSX.Element {
const [searchQuery, setSearchQuery] = useState(''); const [searchQuery, setSearchQuery] = useState('');
const [results, setResults] = useState<LocationResult[]>([]); const [results, setResults] = useState<LocationResult[]>([]);
const [isSearching, setIsSearching] = useState(false); const [isSearching, setIsSearching] = useState(false);
@@ -54,11 +54,11 @@ export function LocationSearch({ onLocationSelect, initialLocationId, className
// Load initial location if editing // Load initial location if editing
useEffect(() => { useEffect(() => {
if (initialLocationId) { if (initialLocationId) {
loadInitialLocation(initialLocationId); void loadInitialLocation(initialLocationId);
} }
}, [initialLocationId]); }, [initialLocationId]);
const loadInitialLocation = async (locationId: string) => { const loadInitialLocation = async (locationId: string): Promise<void> => {
const { data, error } = await supabase const { data, error } = await supabase
.from('locations') .from('locations')
.select('id, name, city, state_province, country, postal_code, latitude, longitude, timezone') .select('id, name, city, state_province, country, postal_code, latitude, longitude, timezone')
@@ -119,11 +119,11 @@ export function LocationSearch({ onLocationSelect, initialLocationId, className
return; return;
} }
const data = await response.json(); const data = await response.json() as LocationResult[];
setResults(data); setResults(data);
setShowResults(true); setShowResults(true);
setSearchError(null); setSearchError(null);
} catch (error: unknown) { } catch {
logger.error('Location search failed', { query: searchQuery }); logger.error('Location search failed', { query: searchQuery });
setSearchError('Failed to search locations. Please check your connection.'); setSearchError('Failed to search locations. Please check your connection.');
setResults([]); setResults([]);
@@ -135,14 +135,15 @@ export function LocationSearch({ onLocationSelect, initialLocationId, className
useEffect(() => { useEffect(() => {
if (debouncedSearch) { if (debouncedSearch) {
searchLocations(debouncedSearch); void searchLocations(debouncedSearch);
} else { } else {
setResults([]); setResults([]);
setShowResults(false); setShowResults(false);
} }
}, [debouncedSearch, searchLocations]); // eslint-disable-next-line react-hooks/exhaustive-deps
}, [debouncedSearch]);
const handleSelectResult = async (result: LocationResult) => { const handleSelectResult = (result: LocationResult): void => {
const latitude = parseFloat(result.lat); const latitude = parseFloat(result.lat);
const longitude = parseFloat(result.lon); const longitude = parseFloat(result.lon);
@@ -176,7 +177,7 @@ export function LocationSearch({ onLocationSelect, initialLocationId, className
onLocationSelect(locationData); onLocationSelect(locationData);
}; };
const handleClear = () => { const handleClear = (): void => {
setSelectedLocation(null); setSelectedLocation(null);
setSearchQuery(''); setSearchQuery('');
setResults([]); setResults([]);
@@ -214,7 +215,7 @@ export function LocationSearch({ onLocationSelect, initialLocationId, className
<button <button
type="button" type="button"
key={result.place_id} key={result.place_id}
onClick={() => handleSelectResult(result)} onClick={() => void handleSelectResult(result)}
className="w-full text-left p-3 hover:bg-accent transition-colors" className="w-full text-left p-3 hover:bg-accent transition-colors"
> >
<div className="flex items-start gap-2"> <div className="flex items-start gap-2">

View File

@@ -1,9 +1,7 @@
import { useState } from 'react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod'; import * as z from 'zod';
import { entitySchemas } from '@/lib/entityValidationSchemas'; import { entitySchemas } from '@/lib/entityValidationSchemas';
import { getErrorMessage } from '@/lib/errorHandler';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea'; import { Textarea } from '@/components/ui/textarea';
@@ -15,37 +13,14 @@ import { SlugField } from '@/components/ui/slug-field';
import { Building2, Save, X } from 'lucide-react'; import { Building2, Save, X } from 'lucide-react';
import { useUserRole } from '@/hooks/useUserRole'; import { useUserRole } from '@/hooks/useUserRole';
import { HeadquartersLocationInput } from './HeadquartersLocationInput'; import { HeadquartersLocationInput } from './HeadquartersLocationInput';
import { EntityMultiImageUploader, ImageAssignments } from '@/components/upload/EntityMultiImageUploader'; import { EntityMultiImageUploader } from '@/components/upload/EntityMultiImageUploader';
import { FlexibleDateInput, type DatePrecision } from '@/components/ui/flexible-date-input'; import { FlexibleDateInput, type DatePrecision } from '@/components/ui/flexible-date-input';
import { submitManufacturerCreation, submitManufacturerUpdate } from '@/lib/entitySubmissionHelpers';
import { useAuth } from '@/hooks/useAuth'; import { useAuth } from '@/hooks/useAuth';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { handleError } from '@/lib/errorHandler'; import { handleError } from '@/lib/errorHandler';
import { useNavigate } from 'react-router-dom';
import { toDateOnly } from '@/lib/dateUtils'; import { toDateOnly } from '@/lib/dateUtils';
import type { UploadedImage } from '@/types/company'; import type { UploadedImage } from '@/types/company';
// Raw form input state (before Zod transformation)
interface ManufacturerFormInput {
name: string;
slug: string;
company_type: 'designer' | 'manufacturer' | 'operator' | 'property_owner';
description?: string;
person_type: 'company' | 'individual' | 'firm' | 'organization';
founded_year?: string;
founded_date?: string;
founded_date_precision?: 'day' | 'month' | 'year';
headquarters_location?: string;
website_url?: string;
source_url?: string;
submission_notes?: string;
images?: {
uploaded: UploadedImage[];
banner_assignment?: number | null;
card_assignment?: number | null;
};
}
// Zod output type (after transformation) // Zod output type (after transformation)
type ManufacturerFormData = z.infer<typeof entitySchemas.manufacturer>; type ManufacturerFormData = z.infer<typeof entitySchemas.manufacturer>;
@@ -59,10 +34,9 @@ interface ManufacturerFormProps {
}>; }>;
} }
export function ManufacturerForm({ onSubmit, onCancel, initialData }: ManufacturerFormProps) { export function ManufacturerForm({ onSubmit, onCancel, initialData }: ManufacturerFormProps): React.JSX.Element {
const { isModerator } = useUserRole(); const { isModerator } = useUserRole();
const { user } = useAuth(); const { user } = useAuth();
const navigate = useNavigate();
const { const {
register, register,
@@ -112,7 +86,7 @@ export function ManufacturerForm({ onSubmit, onCancel, initialData }: Manufactur
founded_year: data.founded_year ? parseInt(String(data.founded_year)) : undefined, founded_year: data.founded_year ? parseInt(String(data.founded_year)) : undefined,
}; };
await onSubmit(formData); onSubmit(formData);
// Only show success toast and close if not editing through moderation queue // Only show success toast and close if not editing through moderation queue
if (!initialData?.id) { if (!initialData?.id) {

View File

@@ -54,7 +54,7 @@ export function MarkdownEditor({
autoSave = false, autoSave = false,
height = 600, height = 600,
placeholder = 'Write your content in markdown...' placeholder = 'Write your content in markdown...'
}: MarkdownEditorProps) { }: MarkdownEditorProps): React.JSX.Element {
const { theme } = useTheme(); const { theme } = useTheme();
const [mounted, setMounted] = useState(false); const [mounted, setMounted] = useState(false);
const [resolvedTheme, setResolvedTheme] = useState<'light' | 'dark'>('light'); const [resolvedTheme, setResolvedTheme] = useState<'light' | 'dark'>('light');
@@ -66,7 +66,7 @@ export function MarkdownEditor({
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches; const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
setResolvedTheme(isDark ? 'dark' : 'light'); setResolvedTheme(isDark ? 'dark' : 'light');
} else { } else {
setResolvedTheme(theme as 'light' | 'dark'); setResolvedTheme(theme);
} }
}, [theme]); }, [theme]);
@@ -75,7 +75,7 @@ export function MarkdownEditor({
if (theme !== 'system') return; if (theme !== 'system') return;
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handler = (e: MediaQueryListEvent) => { const handler = (e: MediaQueryListEvent): void => {
setResolvedTheme(e.matches ? 'dark' : 'light'); setResolvedTheme(e.matches ? 'dark' : 'light');
}; };
@@ -108,7 +108,7 @@ export function MarkdownEditor({
); );
} }
const getLastSavedText = () => { const getLastSavedText = (): string | null => {
if (!lastSaved) return null; if (!lastSaved) return null;
const seconds = Math.floor((Date.now() - lastSaved.getTime()) / 1000); const seconds = Math.floor((Date.now() - lastSaved.getTime()) / 1000);
if (seconds < 60) return `Saved ${seconds}s ago`; if (seconds < 60) return `Saved ${seconds}s ago`;
@@ -138,7 +138,7 @@ export function MarkdownEditor({
linkPlugin(), linkPlugin(),
linkDialogPlugin(), linkDialogPlugin(),
imagePlugin({ imagePlugin({
imageUploadHandler: async (file: File) => { imageUploadHandler: async (file: File): Promise<string> => {
try { try {
const formData = new FormData(); const formData = new FormData();
formData.append('file', file); formData.append('file', file);
@@ -152,7 +152,7 @@ export function MarkdownEditor({
if (error) throw error; if (error) throw error;
// Return CloudFlare imagedelivery.net URL // Return CloudFlare imagedelivery.net URL
const imageUrl = getCloudflareImageUrl(data.id, 'public'); const imageUrl = getCloudflareImageUrl((data as { id: string }).id, 'public');
if (!imageUrl) throw new Error('Failed to generate image URL'); if (!imageUrl) throw new Error('Failed to generate image URL');
return imageUrl; return imageUrl;

View File

@@ -14,7 +14,7 @@ export interface MarkdownEditorProps {
placeholder?: string; placeholder?: string;
} }
export function MarkdownEditorLazy(props: MarkdownEditorProps) { export function MarkdownEditorLazy(props: MarkdownEditorProps): React.JSX.Element {
return ( return (
<Suspense fallback={<EditorSkeleton />}> <Suspense fallback={<EditorSkeleton />}>
<MarkdownEditor {...props} /> <MarkdownEditor {...props} />

View File

@@ -15,14 +15,14 @@ interface MigrationResult {
error?: string; error?: string;
} }
export function NovuMigrationUtility() { export function NovuMigrationUtility(): React.JSX.Element {
const { toast } = useToast(); const { toast } = useToast();
const [isRunning, setIsRunning] = useState(false); const [isRunning, setIsRunning] = useState(false);
const [progress, setProgress] = useState(0); const [progress, setProgress] = useState(0);
const [results, setResults] = useState<MigrationResult[]>([]); const [results, setResults] = useState<MigrationResult[]>([]);
const [totalUsers, setTotalUsers] = useState(0); const [totalUsers, setTotalUsers] = useState(0);
const runMigration = async () => { const runMigration = async (): Promise<void> => {
setIsRunning(true); setIsRunning(true);
setResults([]); setResults([]);
setProgress(0); setProgress(0);
@@ -35,7 +35,7 @@ export function NovuMigrationUtility() {
throw new Error('You must be logged in to run the migration'); throw new Error('You must be logged in to run the migration');
} }
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL || 'https://api.thrillwiki.com'; const supabaseUrl = import.meta.env.VITE_SUPABASE_URL as string || 'https://api.thrillwiki.com';
const response = await fetch( const response = await fetch(
`${supabaseUrl}/functions/v1/migrate-novu-users`, `${supabaseUrl}/functions/v1/migrate-novu-users`,
{ {
@@ -47,7 +47,7 @@ export function NovuMigrationUtility() {
} }
); );
const data = await response.json(); const data = await response.json() as { success: boolean; error?: string; results?: MigrationResult[]; total?: number };
if (!response.ok || !data.success) { if (!response.ok || !data.success) {
throw new Error(data.error || 'Migration failed'); throw new Error(data.error || 'Migration failed');
@@ -62,12 +62,12 @@ export function NovuMigrationUtility() {
return; return;
} }
setTotalUsers(data.total); setTotalUsers(data.total ?? 0);
setResults(data.results); setResults(data.results ?? []);
setProgress(100); setProgress(100);
const successCount = data.results.filter((r: MigrationResult) => r.success).length; const successCount = (data.results ?? []).filter((r: MigrationResult) => r.success).length;
const failureCount = data.results.filter((r: MigrationResult) => !r.success).length; const failureCount = (data.results ?? []).length - successCount;
toast({ toast({
title: "Migration completed", title: "Migration completed",
@@ -106,7 +106,7 @@ export function NovuMigrationUtility() {
</Alert> </Alert>
<Button <Button
onClick={runMigration} onClick={() => void runMigration()}
disabled={isRunning} disabled={isRunning}
className="w-full" className="w-full"
> >

View File

@@ -1,9 +1,7 @@
import { useState } from 'react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod'; import * as z from 'zod';
import { entitySchemas } from '@/lib/entityValidationSchemas'; import { entitySchemas } from '@/lib/entityValidationSchemas';
import { getErrorMessage } from '@/lib/errorHandler';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea'; import { Textarea } from '@/components/ui/textarea';
@@ -15,36 +13,12 @@ import { SlugField } from '@/components/ui/slug-field';
import { FerrisWheel, Save, X } from 'lucide-react'; import { FerrisWheel, Save, X } from 'lucide-react';
import { useUserRole } from '@/hooks/useUserRole'; import { useUserRole } from '@/hooks/useUserRole';
import { HeadquartersLocationInput } from './HeadquartersLocationInput'; import { HeadquartersLocationInput } from './HeadquartersLocationInput';
import { EntityMultiImageUploader, ImageAssignments } from '@/components/upload/EntityMultiImageUploader'; import { EntityMultiImageUploader } from '@/components/upload/EntityMultiImageUploader';
import { FlexibleDateInput, type DatePrecision } from '@/components/ui/flexible-date-input';
import { submitOperatorCreation, submitOperatorUpdate } from '@/lib/entitySubmissionHelpers';
import { useAuth } from '@/hooks/useAuth'; import { useAuth } from '@/hooks/useAuth';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { handleError } from '@/lib/errorHandler'; import { handleError } from '@/lib/errorHandler';
import { useNavigate } from 'react-router-dom';
import type { UploadedImage } from '@/types/company'; import type { UploadedImage } from '@/types/company';
// Raw form input state (before Zod transformation)
interface OperatorFormInput {
name: string;
slug: string;
company_type: 'designer' | 'manufacturer' | 'operator' | 'property_owner';
description?: string;
person_type: 'company' | 'individual' | 'firm' | 'organization';
founded_year?: string;
founded_date?: string;
founded_date_precision?: 'day' | 'month' | 'year';
headquarters_location?: string;
website_url?: string;
source_url?: string;
submission_notes?: string;
images?: {
uploaded: UploadedImage[];
banner_assignment?: number | null;
card_assignment?: number | null;
};
}
// Zod output type (after transformation) // Zod output type (after transformation)
type OperatorFormData = z.infer<typeof entitySchemas.operator>; type OperatorFormData = z.infer<typeof entitySchemas.operator>;
@@ -58,10 +32,9 @@ interface OperatorFormProps {
}>; }>;
} }
export function OperatorForm({ onSubmit, onCancel, initialData }: OperatorFormProps) { export function OperatorForm({ onSubmit, onCancel, initialData }: OperatorFormProps): React.JSX.Element {
const { isModerator } = useUserRole(); const { isModerator } = useUserRole();
const { user } = useAuth(); const { user } = useAuth();
const navigate = useNavigate();
const { const {
register, register,
@@ -109,7 +82,7 @@ export function OperatorForm({ onSubmit, onCancel, initialData }: OperatorFormPr
founded_year: data.founded_year ? parseInt(String(data.founded_year)) : undefined, founded_year: data.founded_year ? parseInt(String(data.founded_year)) : undefined,
}; };
await onSubmit(formData); onSubmit(formData);
// Only show success toast and close if not editing through moderation queue // Only show success toast and close if not editing through moderation queue
if (!initialData?.id) { if (!initialData?.id) {

View File

@@ -8,15 +8,15 @@ import { format } from 'date-fns';
import { handleError } from '@/lib/errorHandler'; import { handleError } from '@/lib/errorHandler';
import { AuditLogEntry } from '@/types/database'; import { AuditLogEntry } from '@/types/database';
export function ProfileAuditLog() { export function ProfileAuditLog(): React.JSX.Element {
const [logs, setLogs] = useState<AuditLogEntry[]>([]); const [logs, setLogs] = useState<AuditLogEntry[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
useEffect(() => { useEffect(() => {
fetchAuditLogs(); void fetchAuditLogs();
}, []); }, []);
const fetchAuditLogs = async () => { const fetchAuditLogs = async (): Promise<void> => {
try { try {
const { data, error } = await supabase const { data, error } = await supabase
.from('profile_audit_log') .from('profile_audit_log')

View File

@@ -1,9 +1,7 @@
import { useState } from 'react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod'; import * as z from 'zod';
import { entitySchemas } from '@/lib/entityValidationSchemas'; import { entitySchemas } from '@/lib/entityValidationSchemas';
import { getErrorMessage } from '@/lib/errorHandler';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea'; import { Textarea } from '@/components/ui/textarea';
@@ -15,36 +13,12 @@ import { SlugField } from '@/components/ui/slug-field';
import { Building2, Save, X } from 'lucide-react'; import { Building2, Save, X } from 'lucide-react';
import { useUserRole } from '@/hooks/useUserRole'; import { useUserRole } from '@/hooks/useUserRole';
import { HeadquartersLocationInput } from './HeadquartersLocationInput'; import { HeadquartersLocationInput } from './HeadquartersLocationInput';
import { EntityMultiImageUploader, ImageAssignments } from '@/components/upload/EntityMultiImageUploader'; import { EntityMultiImageUploader } from '@/components/upload/EntityMultiImageUploader';
import { FlexibleDateInput, type DatePrecision } from '@/components/ui/flexible-date-input';
import { submitPropertyOwnerCreation, submitPropertyOwnerUpdate } from '@/lib/entitySubmissionHelpers';
import { useAuth } from '@/hooks/useAuth'; import { useAuth } from '@/hooks/useAuth';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { handleError } from '@/lib/errorHandler'; import { handleError } from '@/lib/errorHandler';
import { useNavigate } from 'react-router-dom';
import type { UploadedImage } from '@/types/company'; import type { UploadedImage } from '@/types/company';
// Raw form input state (before Zod transformation)
interface PropertyOwnerFormInput {
name: string;
slug: string;
company_type: 'designer' | 'manufacturer' | 'operator' | 'property_owner';
description?: string;
person_type: 'company' | 'individual' | 'firm' | 'organization';
founded_year?: string;
founded_date?: string;
founded_date_precision?: 'day' | 'month' | 'year';
headquarters_location?: string;
website_url?: string;
source_url?: string;
submission_notes?: string;
images?: {
uploaded: UploadedImage[];
banner_assignment?: number | null;
card_assignment?: number | null;
};
}
// Zod output type (after transformation) // Zod output type (after transformation)
type PropertyOwnerFormData = z.infer<typeof entitySchemas.property_owner>; type PropertyOwnerFormData = z.infer<typeof entitySchemas.property_owner>;
@@ -58,10 +32,9 @@ interface PropertyOwnerFormProps {
}>; }>;
} }
export function PropertyOwnerForm({ onSubmit, onCancel, initialData }: PropertyOwnerFormProps) { export function PropertyOwnerForm({ onSubmit, onCancel, initialData }: PropertyOwnerFormProps): React.JSX.Element {
const { isModerator } = useUserRole(); const { isModerator } = useUserRole();
const { user } = useAuth(); const { user } = useAuth();
const navigate = useNavigate();
const { const {
register, register,
@@ -109,7 +82,7 @@ export function PropertyOwnerForm({ onSubmit, onCancel, initialData }: PropertyO
founded_year: data.founded_year ? parseInt(String(data.founded_year)) : undefined, founded_year: data.founded_year ? parseInt(String(data.founded_year)) : undefined,
}; };
await onSubmit(formData); onSubmit(formData);
// Only show success toast and close if not editing through moderation queue // Only show success toast and close if not editing through moderation queue
if (!initialData?.id) { if (!initialData?.id) {

View File

@@ -173,7 +173,7 @@ const activityTypeConfig = {
}; };
export const SystemActivityLog = forwardRef<SystemActivityLogRef, SystemActivityLogProps>( export const SystemActivityLog = forwardRef<SystemActivityLogRef, SystemActivityLogProps>(
({ limit = 50, showFilters = true }, ref) => { ({ limit = 50, showFilters = true }, ref): React.JSX.Element => {
const [activities, setActivities] = useState<SystemActivity[]>([]); const [activities, setActivities] = useState<SystemActivity[]>([]);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [isRefreshing, setIsRefreshing] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false);
@@ -182,7 +182,7 @@ export const SystemActivityLog = forwardRef<SystemActivityLogRef, SystemActivity
const [searchQuery, setSearchQuery] = useState(''); const [searchQuery, setSearchQuery] = useState('');
const [showFiltersPanel, setShowFiltersPanel] = useState(false); const [showFiltersPanel, setShowFiltersPanel] = useState(false);
const loadActivities = async (showLoader = true) => { const loadActivities = async (showLoader = true): Promise<void> => {
if (showLoader) { if (showLoader) {
setIsLoading(true); setIsLoading(true);
} else { } else {
@@ -201,19 +201,20 @@ export const SystemActivityLog = forwardRef<SystemActivityLogRef, SystemActivity
} }
}; };
const handleRefresh = () => { const handleRefresh = (): void => {
loadActivities(false); void loadActivities(false);
}; };
useEffect(() => { useEffect(() => {
loadActivities(); void loadActivities();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [limit, filterType]); }, [limit, filterType]);
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
refresh: loadActivities, refresh: loadActivities,
})); }));
const toggleExpanded = (id: string) => { const toggleExpanded = (id: string): void => {
setExpandedIds(prev => { setExpandedIds(prev => {
const next = new Set(prev); const next = new Set(prev);
if (next.has(id)) { if (next.has(id)) {
@@ -225,7 +226,7 @@ export const SystemActivityLog = forwardRef<SystemActivityLogRef, SystemActivity
}); });
}; };
const clearFilters = () => { const clearFilters = (): void => {
setFilterType('all'); setFilterType('all');
setSearchQuery(''); setSearchQuery('');
}; };
@@ -258,7 +259,7 @@ export const SystemActivityLog = forwardRef<SystemActivityLogRef, SystemActivity
return false; return false;
}); });
const renderActivityDetails = (activity: SystemActivity) => { const renderActivityDetails = (activity: SystemActivity): React.JSX.Element => {
const isExpanded = expandedIds.has(activity.id); const isExpanded = expandedIds.has(activity.id);
switch (activity.type) { switch (activity.type) {

View File

@@ -41,7 +41,7 @@ interface TestDataResults {
time?: string; time?: string;
} }
export function TestDataGenerator() { export function TestDataGenerator(): React.JSX.Element {
const { toast } = useToast(); const { toast } = useToast();
const [preset, setPreset] = useState<'small' | 'medium' | 'large' | 'stress'>('small'); const [preset, setPreset] = useState<'small' | 'medium' | 'large' | 'stress'>('small');
const [fieldDensity, setFieldDensity] = useState<'mixed' | 'minimal' | 'standard' | 'maximum'>('mixed'); const [fieldDensity, setFieldDensity] = useState<'mixed' | 'minimal' | 'standard' | 'maximum'>('mixed');
@@ -78,14 +78,14 @@ export function TestDataGenerator() {
} | null>(null); } | null>(null);
const selectedEntityTypes = Object.entries(entityTypes) const selectedEntityTypes = Object.entries(entityTypes)
.filter(([_, enabled]) => enabled) .filter(([, enabled]) => enabled)
.map(([type]) => type); .map(([type]) => type);
useEffect(() => { useEffect(() => {
loadStats(); void loadStats();
}, []); }, []);
const loadStats = async () => { const loadStats = async (): Promise<void> => {
try { try {
const data = await getTestDataStats(); const data = await getTestDataStats();
setStats(data); setStats(data);
@@ -94,7 +94,7 @@ export function TestDataGenerator() {
} }
}; };
const handleGenerate = async () => { const handleGenerate = async (): Promise<void> => {
setLoading(true); setLoading(true);
setResults(null); setResults(null);
@@ -137,9 +137,10 @@ export function TestDataGenerator() {
if (error) throw error; if (error) throw error;
// Merge results // Merge results
Object.keys(data.summary).forEach(key => { const summary = data.summary as Record<string, number>;
Object.keys(summary).forEach(key => {
if (allResults[key as keyof typeof allResults] !== undefined) { if (allResults[key as keyof typeof allResults] !== undefined) {
allResults[key as keyof typeof allResults] += data.summary[key]; allResults[key as keyof typeof allResults] += summary[key];
} }
}); });
} }
@@ -162,7 +163,7 @@ export function TestDataGenerator() {
} }
}; };
const handleClear = async () => { const handleClear = async (): Promise<void> => {
setLoading(true); setLoading(true);
try { try {
@@ -273,7 +274,7 @@ export function TestDataGenerator() {
<div className="space-y-4"> <div className="space-y-4">
<div> <div>
<Label className="text-base font-semibold">Preset</Label> <Label className="text-base font-semibold">Preset</Label>
<RadioGroup value={preset} onValueChange={(v: any) => setPreset(v)} className="mt-2 space-y-3"> <RadioGroup value={preset} onValueChange={(v: string) => setPreset(v as 'small' | 'medium' | 'large' | 'stress')} className="mt-2 space-y-3">
{Object.entries(PRESETS).map(([key, { label, description, counts }]) => ( {Object.entries(PRESETS).map(([key, { label, description, counts }]) => (
<div key={key} className="flex items-start space-x-2"> <div key={key} className="flex items-start space-x-2">
<RadioGroupItem value={key} id={key} className="mt-1" /> <RadioGroupItem value={key} id={key} className="mt-1" />
@@ -292,7 +293,7 @@ export function TestDataGenerator() {
<p className="text-sm text-muted-foreground mt-1 mb-3"> <p className="text-sm text-muted-foreground mt-1 mb-3">
Controls how many optional fields are populated in generated entities Controls how many optional fields are populated in generated entities
</p> </p>
<RadioGroup value={fieldDensity} onValueChange={(v: any) => setFieldDensity(v)} className="space-y-2"> <RadioGroup value={fieldDensity} onValueChange={(v: string) => setFieldDensity(v as 'mixed' | 'minimal' | 'standard' | 'maximum')} className="space-y-2">
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<RadioGroupItem value="mixed" id="mixed" /> <RadioGroupItem value="mixed" id="mixed" />
<Label htmlFor="mixed" className="cursor-pointer"> <Label htmlFor="mixed" className="cursor-pointer">