Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 

106 lignes
4.4 KiB

  1. import type { Card, CardType, Portal } from '@/types';
  2. import { TEXT_LIMITS } from '@/lib/config';
  3. // Re-export per retro-compatibilità con il codice che importa CARD_LIMITS.
  4. export const CARD_LIMITS = TEXT_LIMITS.card;
  5. export const PORTAL_LIMITS = TEXT_LIMITS.portal;
  6. const VALID_CARD_TYPES: readonly CardType[] = [
  7. 'INFO_PAGE',
  8. 'EXTERNAL_LINK',
  9. 'IMAGE_GALLERY',
  10. 'SERVICE_REQUEST', // non esposto in admin né effettivamente utilizzato (vedi nota in types/index.ts)
  11. 'BOOK',
  12. 'FULLSCREEN_LOCK',
  13. ] as const;
  14. const ALLOWED_URL_SCHEMES = new Set(['http:', 'https:', 'mailto:', 'tel:']);
  15. export type ValidationError = {
  16. field: string;
  17. message: string;
  18. limit?: number;
  19. actual?: number;
  20. };
  21. export type ValidationResult = {
  22. valid: boolean;
  23. errors: ValidationError[];
  24. };
  25. function strLen(v: unknown): number {
  26. return typeof v === 'string' ? v.length : 0;
  27. }
  28. export function validateCard(card: Partial<Card>): ValidationResult {
  29. const errors: ValidationError[] = [];
  30. if (typeof card.title !== 'string' || card.title.trim().length === 0) {
  31. errors.push({ field: 'title', message: 'Title is required. Enter a title before saving.' });
  32. } else if (card.title.length > CARD_LIMITS.title) {
  33. errors.push({ field: 'title', message: 'Title is too long', limit: CARD_LIMITS.title, actual: card.title.length });
  34. }
  35. if (card.shortDescription !== undefined && typeof card.shortDescription !== 'string') {
  36. errors.push({ field: 'shortDescription', message: 'Invalid type (expected text)' });
  37. } else if (strLen(card.shortDescription) > CARD_LIMITS.shortDescription) {
  38. errors.push({ field: 'shortDescription', message: 'Short description is too long', limit: CARD_LIMITS.shortDescription, actual: strLen(card.shortDescription) });
  39. }
  40. if (card.fullContent !== undefined && typeof card.fullContent !== 'string') {
  41. errors.push({ field: 'fullContent', message: 'Invalid type (expected text)' });
  42. } else if (strLen(card.fullContent) > CARD_LIMITS.fullContent) {
  43. errors.push({ field: 'fullContent', message: 'Content is too long', limit: CARD_LIMITS.fullContent, actual: strLen(card.fullContent) });
  44. }
  45. // Le card External Link richiedono obbligatoriamente l'URL.
  46. if (card.cardType === 'EXTERNAL_LINK' && (typeof card.actionUrl !== 'string' || card.actionUrl.trim() === '')) {
  47. errors.push({ field: 'actionUrl', message: 'URL is required for External Link cards. Enter the destination URL before saving.' });
  48. }
  49. if (card.actionUrl !== undefined && card.actionUrl !== '') {
  50. if (typeof card.actionUrl !== 'string') {
  51. errors.push({ field: 'actionUrl', message: 'Invalid type (expected text)' });
  52. } else if (card.actionUrl.length > CARD_LIMITS.actionUrl) {
  53. errors.push({ field: 'actionUrl', message: 'URL is too long', limit: CARD_LIMITS.actionUrl, actual: card.actionUrl.length });
  54. } else {
  55. try {
  56. const parsed = new URL(card.actionUrl);
  57. if (!ALLOWED_URL_SCHEMES.has(parsed.protocol)) {
  58. errors.push({ field: 'actionUrl', message: `URL scheme not allowed (${parsed.protocol}). Allowed schemes: http, https, mailto, tel.` });
  59. }
  60. } catch {
  61. errors.push({ field: 'actionUrl', message: 'Invalid URL format. Use a complete URL such as https://example.com.' });
  62. }
  63. }
  64. }
  65. if (card.cardType !== undefined && !VALID_CARD_TYPES.includes(card.cardType as CardType)) {
  66. errors.push({ field: 'cardType', message: `Invalid card type "${String(card.cardType)}". Pick a type from the dropdown.` });
  67. }
  68. return { valid: errors.length === 0, errors };
  69. }
  70. export function validatePortal(portal: Partial<Portal>): ValidationResult {
  71. const errors: ValidationError[] = [];
  72. if (portal.title !== undefined) {
  73. if (typeof portal.title !== 'string') {
  74. errors.push({ field: 'title', message: 'Invalid type (expected text)' });
  75. } else if (portal.title.length > PORTAL_LIMITS.title) {
  76. errors.push({ field: 'title', message: 'Portal title is too long', limit: PORTAL_LIMITS.title, actual: portal.title.length });
  77. }
  78. }
  79. if (portal.welcomeText !== undefined) {
  80. if (typeof portal.welcomeText !== 'string') {
  81. errors.push({ field: 'welcomeText', message: 'Invalid type (expected text)' });
  82. } else if (portal.welcomeText.length > PORTAL_LIMITS.welcomeText) {
  83. errors.push({ field: 'welcomeText', message: 'Welcome text is too long', limit: PORTAL_LIMITS.welcomeText, actual: portal.welcomeText.length });
  84. }
  85. }
  86. return { valid: errors.length === 0, errors };
  87. }