import type { Card, CardType, Portal } from '@/types'; import { TEXT_LIMITS } from '@/lib/config'; // Re-export per retro-compatibilità con il codice che importa CARD_LIMITS. export const CARD_LIMITS = TEXT_LIMITS.card; export const PORTAL_LIMITS = TEXT_LIMITS.portal; const VALID_CARD_TYPES: readonly CardType[] = [ 'INFO_PAGE', 'EXTERNAL_LINK', 'IMAGE_GALLERY', 'SERVICE_REQUEST', // non esposto in admin né effettivamente utilizzato (vedi nota in types/index.ts) 'BOOK', 'FULLSCREEN_LOCK', ] as const; const ALLOWED_URL_SCHEMES = new Set(['http:', 'https:', 'mailto:', 'tel:']); export type ValidationError = { field: string; message: string; limit?: number; actual?: number; }; export type ValidationResult = { valid: boolean; errors: ValidationError[]; }; function strLen(v: unknown): number { return typeof v === 'string' ? v.length : 0; } export function validateCard(card: Partial): ValidationResult { const errors: ValidationError[] = []; if (typeof card.title !== 'string' || card.title.trim().length === 0) { errors.push({ field: 'title', message: 'Il titolo è obbligatorio' }); } else if (card.title.length > CARD_LIMITS.title) { errors.push({ field: 'title', message: 'Titolo troppo lungo', limit: CARD_LIMITS.title, actual: card.title.length }); } if (card.shortDescription !== undefined && typeof card.shortDescription !== 'string') { errors.push({ field: 'shortDescription', message: 'Tipo non valido' }); } else if (strLen(card.shortDescription) > CARD_LIMITS.shortDescription) { errors.push({ field: 'shortDescription', message: 'Descrizione breve troppo lunga', limit: CARD_LIMITS.shortDescription, actual: strLen(card.shortDescription) }); } if (card.fullContent !== undefined && typeof card.fullContent !== 'string') { errors.push({ field: 'fullContent', message: 'Tipo non valido' }); } else if (strLen(card.fullContent) > CARD_LIMITS.fullContent) { errors.push({ field: 'fullContent', message: 'Contenuto troppo lungo', limit: CARD_LIMITS.fullContent, actual: strLen(card.fullContent) }); } // Le card External Link richiedono obbligatoriamente l'URL. if (card.cardType === 'EXTERNAL_LINK' && (typeof card.actionUrl !== 'string' || card.actionUrl.trim() === '')) { errors.push({ field: 'actionUrl', message: "L'URL è obbligatorio per le card External Link" }); } if (card.actionUrl !== undefined && card.actionUrl !== '') { if (typeof card.actionUrl !== 'string') { errors.push({ field: 'actionUrl', message: 'Tipo non valido' }); } else if (card.actionUrl.length > CARD_LIMITS.actionUrl) { errors.push({ field: 'actionUrl', message: 'URL troppo lungo', limit: CARD_LIMITS.actionUrl, actual: card.actionUrl.length }); } else { try { const parsed = new URL(card.actionUrl); if (!ALLOWED_URL_SCHEMES.has(parsed.protocol)) { errors.push({ field: 'actionUrl', message: `Schema URL non ammesso (${parsed.protocol}). Usa http, https, mailto o tel.` }); } } catch { errors.push({ field: 'actionUrl', message: 'URL non valido' }); } } } if (card.cardType !== undefined && !VALID_CARD_TYPES.includes(card.cardType as CardType)) { errors.push({ field: 'cardType', message: `Tipo card non valido: ${String(card.cardType)}` }); } return { valid: errors.length === 0, errors }; } export function validatePortal(portal: Partial): ValidationResult { const errors: ValidationError[] = []; if (portal.title !== undefined) { if (typeof portal.title !== 'string') { errors.push({ field: 'title', message: 'Tipo non valido' }); } else if (portal.title.length > PORTAL_LIMITS.title) { errors.push({ field: 'title', message: 'Titolo portale troppo lungo', limit: PORTAL_LIMITS.title, actual: portal.title.length }); } } if (portal.welcomeText !== undefined) { if (typeof portal.welcomeText !== 'string') { errors.push({ field: 'welcomeText', message: 'Tipo non valido' }); } else if (portal.welcomeText.length > PORTAL_LIMITS.welcomeText) { errors.push({ field: 'welcomeText', message: 'Testo di benvenuto troppo lungo', limit: PORTAL_LIMITS.welcomeText, actual: portal.welcomeText.length }); } } return { valid: errors.length === 0, errors }; }