export type ThemeFieldType = 'text' | 'textarea' | 'url' | 'number' | 'select' | 'color' | 'image' export type ThemeFieldSchema = { type: ThemeFieldType name: string label: string required?: boolean options?: string[] } export type ThemeSectionSchema = { id: string title: string fields: ThemeFieldSchema[] } const HEX_COLOR_PATTERN = /^#[0-9a-fA-F]{6}$/ const UNSAFE_TOKENS = [' = { primaryColor: '#4f46e5', backgroundColor: '#ffffff', textColor: '#0f172a', fontFamily: 'Inter', buttonRadius: 8, loginHeading: 'Welcome back', registrationHeading: 'Create your account', footerText: 'Powered by CMS Platform', securityText: 'Never share your password with anyone.', } export type ThemeFormValidationResult = { valid: boolean errors: Record } function hasUnsafeToken(value: string): boolean { const normalized = value.toLowerCase() return UNSAFE_TOKENS.some(token => normalized.includes(token)) } function isValidUrl(value: string): boolean { try { const parsed = new URL(value) return parsed.protocol === 'http:' || parsed.protocol === 'https:' } catch { return false } } export function validateThemeFormValues( schema: ThemeSectionSchema[], values: Record, ): ThemeFormValidationResult { const errors: Record = {} for (const section of schema) { for (const field of section.fields) { const value = values[field.name] const textValue = typeof value === 'string' ? value.trim() : '' if (field.required && (value === undefined || value === null || textValue === '')) { errors[field.name] = `${field.label} is required` continue } if (value === undefined || value === null || textValue === '') { continue } if (typeof value === 'string' && hasUnsafeToken(value)) { errors[field.name] = `${field.label} contains unsafe content` continue } if (field.type === 'color' && typeof value === 'string' && !HEX_COLOR_PATTERN.test(value.trim())) { errors[field.name] = `${field.label} must be a hex color` continue } if (field.type === 'url' && typeof value === 'string' && !isValidUrl(value.trim())) { errors[field.name] = `${field.label} must be a valid URL` continue } if (field.type === 'number') { const numeric = typeof value === 'number' ? value : Number(value) if (Number.isNaN(numeric)) { errors[field.name] = `${field.label} must be a number` continue } } if (field.type === 'select' && field.options && !field.options.includes(String(value).trim())) { errors[field.name] = `${field.label} has an unsupported value` continue } if (field.type === 'image' && typeof value === 'string' && value.includes(' ')) { errors[field.name] = `${field.label} must not contain spaces` } } } return { valid: Object.keys(errors).length === 0, errors, } }