From 2ccbb51fb1d99eb66dbfcb020a79b52b0181c184 Mon Sep 17 00:00:00 2001
From: "gpt-engineer-app[bot]"
<159125892+gpt-engineer-app[bot]@users.noreply.github.com>
Date: Fri, 10 Oct 2025 00:38:04 +0000
Subject: [PATCH] Fix validation errors and display
---
src/components/moderation/QueueItem.tsx | 4 +-
.../moderation/ValidationSummary.tsx | 160 +++++++-----------
src/lib/entityValidationSchemas.ts | 54 ++++--
3 files changed, 101 insertions(+), 117 deletions(-)
diff --git a/src/components/moderation/QueueItem.tsx b/src/components/moderation/QueueItem.tsx
index 94ec3499..8caa906e 100644
--- a/src/components/moderation/QueueItem.tsx
+++ b/src/components/moderation/QueueItem.tsx
@@ -158,15 +158,17 @@ export const QueueItem = memo(({
Claimed by You
)}
+ {item.submission_type && (
+ )}
diff --git a/src/components/moderation/ValidationSummary.tsx b/src/components/moderation/ValidationSummary.tsx
index 361b18c4..ba1a6917 100644
--- a/src/components/moderation/ValidationSummary.tsx
+++ b/src/components/moderation/ValidationSummary.tsx
@@ -3,7 +3,6 @@ import { AlertCircle, CheckCircle, Info, AlertTriangle } from 'lucide-react';
import { Badge } from '@/components/ui/badge';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
-import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { validateEntityData, ValidationResult } from '@/lib/entityValidationSchemas';
interface ValidationSummaryProps {
@@ -25,10 +24,18 @@ export function ValidationSummary({ item, onValidationChange, compact = false }:
async function validate() {
setIsLoading(true);
try {
+ console.log('Validating submission:', {
+ type: item.item_type,
+ data: item.item_data,
+ id: item.id
+ });
+
const result = await validateEntityData(
item.item_type as any,
{ ...item.item_data, id: item.id }
);
+
+ console.log('Validation result:', result);
setValidationResult(result);
onValidationChange?.(result);
} catch (error) {
@@ -73,107 +80,62 @@ export function ValidationSummary({ item, onValidationChange, compact = false }:
const hasSuggestions = validationResult.suggestions.length > 0;
const hasAnyIssues = hasBlockingErrors || hasWarnings || hasSuggestions;
- // Compact view (for queue items)
+ // Compact view (for queue items) - NO HOVER, ALWAYS VISIBLE
if (compact) {
return (
-
-
-
-
- {validationResult.isValid && !hasWarnings && !hasSuggestions && (
-
-
- Valid
-
- )}
- {hasBlockingErrors && (
-
-
- {validationResult.blockingErrors.length} Error{validationResult.blockingErrors.length !== 1 ? 's' : ''}
-
- )}
- {hasWarnings && !hasBlockingErrors && (
-
-
- {validationResult.warnings.length} Warning{validationResult.warnings.length !== 1 ? 's' : ''}
-
- )}
- {hasSuggestions && !hasBlockingErrors && !hasWarnings && (
-
-
- {validationResult.suggestions.length} Suggestion{validationResult.suggestions.length !== 1 ? 's' : ''}
-
- )}
-
-
-
- {hasAnyIssues && (
-
-
- {hasBlockingErrors && (
-
-
- Blocking Errors:
-
-
- {validationResult.blockingErrors.slice(0, 3).map((error, i) => (
- -
- • {error.field}: {error.message}
-
- ))}
- {validationResult.blockingErrors.length > 3 && (
- -
- ... and {validationResult.blockingErrors.length - 3} more
-
- )}
-
-
- )}
-
- {hasWarnings && (
-
-
- Warnings:
-
-
- {validationResult.warnings.slice(0, 3).map((warning, i) => (
- -
- • {warning.field}: {warning.message}
-
- ))}
- {validationResult.warnings.length > 3 && (
- -
- ... and {validationResult.warnings.length - 3} more
-
- )}
-
-
- )}
-
- {hasSuggestions && !hasBlockingErrors && !hasWarnings && (
-
-
- Suggestions:
-
-
- {validationResult.suggestions.slice(0, 3).map((suggestion, i) => (
- -
- • {suggestion.field}: {suggestion.message}
-
- ))}
- {validationResult.suggestions.length > 3 && (
- -
- ... and {validationResult.suggestions.length - 3} more
-
- )}
-
-
- )}
-
-
+
+ {/* Status Badges */}
+
+ {validationResult.isValid && !hasWarnings && !hasSuggestions && (
+
+
+ Valid
+
)}
-
-
+ {hasBlockingErrors && (
+
+
+ {validationResult.blockingErrors.length} Error{validationResult.blockingErrors.length !== 1 ? 's' : ''}
+
+ )}
+ {hasWarnings && (
+
+
+ {validationResult.warnings.length} Warning{validationResult.warnings.length !== 1 ? 's' : ''}
+
+ )}
+
+
+ {/* ALWAYS SHOW ERROR DETAILS - NO HOVER NEEDED */}
+ {hasBlockingErrors && (
+
+
Blocking Errors:
+
+ {validationResult.blockingErrors.map((error, i) => (
+ -
+ • {error.field}: {error.message}
+
+ ))}
+
+
+ )}
+
+ {hasWarnings && !hasBlockingErrors && (
+
+
Warnings:
+
+ {validationResult.warnings.slice(0, 3).map((warning, i) => (
+ -
+ • {warning.field}: {warning.message}
+
+ ))}
+ {validationResult.warnings.length > 3 && (
+ - ... and {validationResult.warnings.length - 3} more
+ )}
+
+
+ )}
+
);
}
diff --git a/src/lib/entityValidationSchemas.ts b/src/lib/entityValidationSchemas.ts
index cf0bf40c..f641e2a8 100644
--- a/src/lib/entityValidationSchemas.ts
+++ b/src/lib/entityValidationSchemas.ts
@@ -21,12 +21,18 @@ export const parkValidationSchema = z.object({
return date <= new Date();
}, 'Opening date cannot be in the future'),
closing_date: z.string().optional(),
- location_id: z.string().uuid().optional(),
- website_url: z.string().url('Invalid URL format').optional().or(z.literal('')),
+ location_id: z.string().uuid().optional().nullable(),
+ website_url: z.string().optional().refine((val) => {
+ if (!val || val === '') return true;
+ return z.string().url().safeParse(val).success;
+ }, 'Invalid URL format'),
phone: z.string().max(50, 'Phone must be less than 50 characters').optional(),
- email: z.string().email('Invalid email format').optional().or(z.literal('')),
- operator_id: z.string().uuid().optional(),
- property_owner_id: z.string().uuid().optional(),
+ email: z.string().optional().refine((val) => {
+ if (!val || val === '') return true;
+ return z.string().email().safeParse(val).success;
+ }, 'Invalid email format'),
+ operator_id: z.string().uuid().optional().nullable(),
+ property_owner_id: z.string().uuid().optional().nullable(),
banner_image_id: z.string().optional(),
banner_image_url: z.string().optional(),
card_image_id: z.string().optional(),
@@ -54,8 +60,8 @@ export const rideValidationSchema = z.object({
category: z.string().min(1, 'Category is required'),
ride_sub_type: z.string().max(100, 'Sub type must be less than 100 characters').optional(),
status: z.string().min(1, 'Status is required'),
- park_id: z.string().uuid().optional(),
- designer_id: z.string().uuid().optional(),
+ park_id: z.string().uuid().optional().nullable(),
+ designer_id: z.string().uuid().optional().nullable(),
opening_date: z.string().optional(),
closing_date: z.string().optional(),
height_requirement: z.number().min(0, 'Height requirement must be positive').max(300, 'Height requirement must be less than 300cm').optional(),
@@ -66,8 +72,8 @@ export const rideValidationSchema = z.object({
max_height_meters: z.number().min(0, 'Height must be positive').max(200, 'Height must be less than 200 meters').optional(),
length_meters: z.number().min(0, 'Length must be positive').optional(),
inversions: z.number().min(0, 'Inversions must be positive').optional(),
- manufacturer_id: z.string().uuid().optional(),
- ride_model_id: z.string().uuid().optional(),
+ manufacturer_id: z.string().uuid().optional().nullable(),
+ ride_model_id: z.string().uuid().optional().nullable(),
coaster_type: z.string().optional(),
seating_type: z.string().optional(),
intensity_level: z.string().optional(),
@@ -93,7 +99,10 @@ export const companyValidationSchema = z.object({
person_type: z.enum(['company', 'individual', 'firm', 'organization']).optional(),
founded_year: z.number().min(1800, 'Founded year must be after 1800').max(currentYear, `Founded year cannot be in the future`).optional(),
headquarters_location: z.string().max(200, 'Location must be less than 200 characters').optional(),
- website_url: z.string().url('Invalid URL format').optional().or(z.literal('')),
+ website_url: z.string().optional().refine((val) => {
+ if (!val || val === '') return true;
+ return z.string().url().safeParse(val).success;
+ }, 'Invalid URL format'),
banner_image_id: z.string().optional(),
banner_image_url: z.string().optional(),
card_image_id: z.string().optional(),
@@ -254,6 +263,8 @@ async function checkSlugUniqueness(
const tableName = getTableNameFromEntityType(entityType);
try {
+ console.log(`Checking slug uniqueness for "${slug}" in ${tableName}, excludeId: ${excludeId}`);
+
const { data, error } = await supabase
.from(tableName as any)
.select('id')
@@ -261,19 +272,28 @@ async function checkSlugUniqueness(
.limit(1);
if (error) {
- console.error('Error checking slug uniqueness:', error);
+ console.error(`Slug uniqueness check failed for ${entityType}:`, error);
return true; // Assume unique on error to avoid blocking
}
- // Check if excludeId matches
- if (excludeId && data && data.length > 0 && data[0]) {
- return (data[0] as any).id === excludeId;
+ // If no data, slug is unique
+ if (!data || data.length === 0) {
+ console.log(`Slug "${slug}" is unique in ${tableName}`);
+ return true;
}
- return !data || data.length === 0;
+ // If excludeId provided and matches, it's the same entity (editing)
+ if (excludeId && data[0] && (data[0] as any).id === excludeId) {
+ console.log(`Slug "${slug}" matches current entity (editing mode)`);
+ return true;
+ }
+
+ // Slug is in use by a different entity
+ console.log(`Slug "${slug}" already exists in ${tableName}`);
+ return false;
} catch (error) {
- console.error('Error checking slug uniqueness:', error);
- return true; // Assume unique on error
+ console.error(`Exception during slug uniqueness check:`, error);
+ return true; // Assume unique on error to avoid false positives
}
}