Ver código fonte

variabilizzati parametri per il limite di testo

main
Lorenzo Pollutri 1 mês atrás
pai
commit
405cc9ae69
4 arquivos alterados com 55 adições e 11 exclusões
  1. +6
    -4
      app/admin/page.tsx
  2. +6
    -0
      app/api/portals/route.ts
  3. +16
    -0
      lib/config.ts
  4. +27
    -7
      lib/validation.ts

+ 6
- 4
app/admin/page.tsx Ver arquivo

@@ -3,7 +3,7 @@
import { useState, useEffect, useRef } from 'react';
import { Card, Portal, MediaItem, CardType } from '@/types';
import { EXTERNAL_LINK_ENABLED as EXTERNAL_LINK_DEFAULT } from '@/lib/config';
import { CARD_LIMITS } from '@/lib/validation';
import { CARD_LIMITS, PORTAL_LIMITS } from '@/lib/validation';
type CharCounterProps = { value: string | undefined; limit: number };
function CharCounter({ value, limit }: CharCounterProps) {
@@ -609,12 +609,14 @@ export default function AdminDashboard() {
<div className="space-y-6">
<div>
<label className="block text-sm font-semibold text-gray-700 mb-1">Portal Title</label>
<input type="text" value={portal.title || ''} onChange={e => setPortal({...portal, title: e.target.value})} className={inputClasses} />
<input type="text" maxLength={PORTAL_LIMITS.title} value={portal.title || ''} onChange={e => setPortal({...portal, title: e.target.value})} className={inputClasses} />
<CharCounter value={portal.title} limit={PORTAL_LIMITS.title} />
</div>
<div>
<label className="block text-sm font-semibold text-gray-700 mb-1">Welcome Text</label>
<textarea value={portal.welcomeText || ''} onChange={e => setPortal({...portal, welcomeText: e.target.value})} className={`${inputClasses} h-32 resize-none`} />
<textarea maxLength={PORTAL_LIMITS.welcomeText} value={portal.welcomeText || ''} onChange={e => setPortal({...portal, welcomeText: e.target.value})} className={`${inputClasses} h-32 resize-none`} />
<CharCounter value={portal.welcomeText} limit={PORTAL_LIMITS.welcomeText} />
</div>
<div className="flex gap-8">


+ 6
- 0
app/api/portals/route.ts Ver arquivo

@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server';
import { revalidatePath } from 'next/cache'; // ADD THIS
import { getPortals, savePortals } from '@/lib/db';
import { isValidHexColor } from '@/lib/sanitize';
import { validatePortal } from '@/lib/validation';
import { Portal } from '@/types';
export const dynamic = 'force-dynamic';
@@ -15,6 +16,11 @@ export async function POST(request: Request) {
try {
const incomingPortal: Portal = await request.json();
const { valid, errors } = validatePortal(incomingPortal);
if (!valid) {
return NextResponse.json({ error: 'Validation failed', errors }, { status: 400 });
}
// themeColor goes into a <style dangerouslySetInnerHTML> in PublicGrid,
// so reject anything that is not a strict #RRGGBB.
if (incomingPortal.themeColor !== undefined && !isValidHexColor(incomingPortal.themeColor)) {


+ 16
- 0
lib/config.ts Ver arquivo

@@ -6,3 +6,19 @@ export const EXTERNAL_LINK_ENABLED = true;
// Lascia stringa vuota per usare il font di sistema (Arial).
// Per usare un font, scrivi il nome esatto del file presente in data/fonts/ (es. "Geist-Variable.woff2").
export const DEFAULT_FONT = '';

// Limiti caratteri per tutti i campi testuali compilabili dall'admin.
// Validati lato server (app/api/cards/route.ts, app/api/portals/route.ts) e
// usati lato client come maxLength + contatore (app/admin/page.tsx).
export const TEXT_LIMITS = {
card: {
title: 200,
shortDescription: 500,
fullContent: 20_000,
actionUrl: 2000,
},
portal: {
title: 200,
welcomeText: 1000,
},
} as const;

+ 27
- 7
lib/validation.ts Ver arquivo

@@ -1,11 +1,9 @@
import type { Card, CardType } from '@/types';
import type { Card, CardType, Portal } from '@/types';
import { TEXT_LIMITS } from '@/lib/config';

export const CARD_LIMITS = {
title: 200,
shortDescription: 500,
fullContent: 20000,
actionUrl: 2000,
} as const;
// 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',
@@ -77,3 +75,25 @@ export function validateCard(card: Partial<Card>): ValidationResult {

return { valid: errors.length === 0, errors };
}

export function validatePortal(portal: Partial<Portal>): 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 };
}

Carregando…
Cancelar
Salvar