diff --git a/src/components/moderation/ValidationSummary.tsx b/src/components/moderation/ValidationSummary.tsx index 637a2e2c..b3e076d7 100644 --- a/src/components/moderation/ValidationSummary.tsx +++ b/src/components/moderation/ValidationSummary.tsx @@ -31,8 +31,8 @@ export function ValidationSummary({ item, onValidationChange, compact = false }: setIsLoading(true); try { // Type guard for valid entity types - type ValidEntityType = 'park' | 'ride' | 'manufacturer' | 'operator' | 'designer' | 'property_owner' | 'ride_model' | 'photo'; - const validEntityTypes: ValidEntityType[] = ['park', 'ride', 'manufacturer', 'operator', 'designer', 'property_owner', 'ride_model', 'photo']; + type ValidEntityType = 'park' | 'ride' | 'manufacturer' | 'operator' | 'designer' | 'property_owner' | 'ride_model' | 'photo' | 'milestone' | 'timeline_event'; + const validEntityTypes: ValidEntityType[] = ['park', 'ride', 'manufacturer', 'operator', 'designer', 'property_owner', 'ride_model', 'photo', 'milestone', 'timeline_event']; if (!validEntityTypes.includes(item.item_type as ValidEntityType)) { setValidationResult({ diff --git a/src/lib/entityValidationSchemas.ts b/src/lib/entityValidationSchemas.ts index ad241c28..48172a84 100644 --- a/src/lib/entityValidationSchemas.ts +++ b/src/lib/entityValidationSchemas.ts @@ -223,6 +223,7 @@ export const entitySchemas = { ride_model: rideModelValidationSchema, photo: photoValidationSchema, milestone: milestoneValidationSchema, + timeline_event: milestoneValidationSchema, // Alias for milestone }; // ============================================ diff --git a/src/types/submissions.ts b/src/types/submissions.ts index 85c7bcdb..9ff33101 100644 --- a/src/types/submissions.ts +++ b/src/types/submissions.ts @@ -6,7 +6,9 @@ export type EntityType = | 'designer' | 'property_owner' | 'photo_edit' - | 'photo_delete'; + | 'photo_delete' + | 'milestone' + | 'timeline_event'; export interface PhotoSubmission { url: string; diff --git a/supabase/functions/process-selective-approval/validation.ts b/supabase/functions/process-selective-approval/validation.ts index e847402e..2d627a09 100644 --- a/supabase/functions/process-selective-approval/validation.ts +++ b/supabase/functions/process-selective-approval/validation.ts @@ -28,35 +28,45 @@ export function validateEntityDataStrict( warnings: [] }; - // Common validations (blocking) - if (!data.name?.trim()) { - result.blockingErrors.push('Name is required'); - } + // Skip name/slug validations for timeline events (they use title instead) + const isTimelineEvent = entityType === 'milestone' || entityType === 'timeline_event'; - if (!data.slug?.trim()) { - result.blockingErrors.push('Slug is required'); - } - - if (data.slug && !/^[a-z0-9-]+$/.test(data.slug)) { - result.blockingErrors.push('Slug must contain only lowercase letters, numbers, and hyphens'); - } - - if (data.name && data.name.length > 200) { - result.blockingErrors.push('Name must be less than 200 characters'); - } - - if (data.description && data.description.length > 2000) { - result.blockingErrors.push('Description must be less than 2000 characters'); - } - - // URL validation (warning) - if (data.website_url && data.website_url !== '' && !isValidUrl(data.website_url)) { - result.warnings.push('Website URL format may be invalid'); - } - - // Email validation (warning) - if (data.email && data.email !== '' && !isValidEmail(data.email)) { - result.warnings.push('Email format may be invalid'); + // Common validations (blocking) - only for entities with name/slug + if (!isTimelineEvent) { + if (!data.name?.trim()) { + result.blockingErrors.push('Name is required'); + } + + if (!data.slug?.trim()) { + result.blockingErrors.push('Slug is required'); + } + + if (data.slug && !/^[a-z0-9-]+$/.test(data.slug)) { + result.blockingErrors.push('Slug must contain only lowercase letters, numbers, and hyphens'); + } + + if (data.name && data.name.length > 200) { + result.blockingErrors.push('Name must be less than 200 characters'); + } + + if (data.description && data.description.length > 2000) { + result.blockingErrors.push('Description must be less than 2000 characters'); + } + + // URL validation (warning) + if (data.website_url && data.website_url !== '' && !isValidUrl(data.website_url)) { + result.warnings.push('Website URL format may be invalid'); + } + + // Email validation (warning) + if (data.email && data.email !== '' && !isValidEmail(data.email)) { + result.warnings.push('Email format may be invalid'); + } + } else { + // Validations specific to timeline events + if (data.description && data.description.length > 2000) { + result.blockingErrors.push('Description must be less than 2000 characters'); + } } // Entity-specific validations @@ -145,6 +155,28 @@ export function validateEntityDataStrict( result.blockingErrors.push('Caption must be less than 500 characters'); } break; + + case 'milestone': + case 'timeline_event': + if (!data.title?.trim()) { + result.blockingErrors.push('Event title is required'); + } + if (data.title && data.title.length > 200) { + result.blockingErrors.push('Title must be less than 200 characters'); + } + if (!data.event_type) { + result.blockingErrors.push('Event type is required'); + } + if (!data.event_date) { + result.blockingErrors.push('Event date is required'); + } + if (!data.entity_type) { + result.blockingErrors.push('Entity type is required'); + } + if (!data.entity_id) { + result.blockingErrors.push('Entity ID is required'); + } + break; } result.valid = result.blockingErrors.length === 0; @@ -171,27 +203,37 @@ function isValidEmail(email: string): boolean { export function validateEntityData(entityType: string, data: any): ValidationResult { const errors: string[] = []; - // Common validations for all entities - if (!data.name || data.name.trim().length === 0) { - errors.push('Name is required'); - } - if (!data.slug || data.slug.trim().length === 0) { - errors.push('Slug is required'); - } - if (data.slug && !/^[a-z0-9-]+$/.test(data.slug)) { - errors.push('Slug must contain only lowercase letters, numbers, and hyphens'); - } - if (data.name && data.name.length > 200) { - errors.push('Name must be less than 200 characters'); - } - if (data.description && data.description.length > 2000) { - errors.push('Description must be less than 2000 characters'); - } - if (data.website_url && data.website_url !== '' && !data.website_url.startsWith('http')) { - errors.push('Website URL must start with http:// or https://'); - } - if (data.email && data.email !== '' && !data.email.includes('@')) { - errors.push('Invalid email format'); + // Skip name/slug validations for timeline events + const isTimelineEvent = entityType === 'milestone' || entityType === 'timeline_event'; + + // Common validations for entities with name/slug + if (!isTimelineEvent) { + if (!data.name || data.name.trim().length === 0) { + errors.push('Name is required'); + } + if (!data.slug || data.slug.trim().length === 0) { + errors.push('Slug is required'); + } + if (data.slug && !/^[a-z0-9-]+$/.test(data.slug)) { + errors.push('Slug must contain only lowercase letters, numbers, and hyphens'); + } + if (data.name && data.name.length > 200) { + errors.push('Name must be less than 200 characters'); + } + if (data.description && data.description.length > 2000) { + errors.push('Description must be less than 2000 characters'); + } + if (data.website_url && data.website_url !== '' && !data.website_url.startsWith('http')) { + errors.push('Website URL must start with http:// or https://'); + } + if (data.email && data.email !== '' && !data.email.includes('@')) { + errors.push('Invalid email format'); + } + } else { + // Validations for timeline events + if (data.description && data.description.length > 2000) { + errors.push('Description must be less than 2000 characters'); + } } // Entity-specific validations @@ -256,6 +298,20 @@ export function validateEntityData(entityType: string, data: any): ValidationRes errors.push('Caption must be less than 500 characters'); } break; + + case 'milestone': + case 'timeline_event': + if (!data.title || data.title.trim().length === 0) { + errors.push('Event title is required'); + } + if (data.title && data.title.length > 200) { + errors.push('Title must be less than 200 characters'); + } + if (!data.event_type) errors.push('Event type is required'); + if (!data.event_date) errors.push('Event date is required'); + if (!data.entity_type) errors.push('Entity type is required'); + if (!data.entity_id) errors.push('Entity ID is required'); + break; } return {