Selaa lähdekoodia

Aggiunti contatori per i caratteri rimanenti nei vari box di testo, factory reset visibile in interfaccia

main
Lorenzo Pollutri 3 viikkoa sitten
vanhempi
commit
9550530e50
3 muutettua tiedostoa jossa 46 lisäystä ja 39 poistoa
  1. +3
    -3
      README.md
  2. +35
    -32
      app/admin/page.tsx
  3. +8
    -4
      lib/config.ts

+ 3
- 3
README.md Näytä tiedosto

@@ -60,7 +60,7 @@ Tutte le impostazioni globali sono **flag build-time** nel file [`lib/config.ts`
| Variabile | Default | Descrizione |
|---|---|---|
| `EXTERNAL_LINK_ENABLED` | `true` | Mostra il tipo di card "External Link" nel menu dell'admin. Le card di quel tipo già esistenti restano comunque visibili e cliccabili anche se messo a `false`. |
| `FACTORY_RESET_ENABLED` | `false` | Mostra la sezione "Factory Preset" nell'admin (funzione developer). Vedi [Factory Preset](#factory-preset-developer). Gli endpoint API restano attivi a prescindere. |
| `FACTORY_PRESET_SAVE_ENABLED` | `false` | Mostra il bottone "💾 Salva come Factory Preset" nell'admin (funzione developer per ricreare/aggiornare il preset). Il bottone "🏭 Factory Reset" e lo stato del preset sono **sempre visibili** indipendentemente da questo flag — vedi [Factory Preset](#factory-preset-developer). L'endpoint API `POST /api/admin/factory-preset` resta attivo a prescindere. |
| `DEFAULT_FONT` | `''` | Font di default se il portale non ne ha impostato uno. Stringa vuota = font di sistema (Arial). Altrimenti il nome esatto di un file in `data/fonts/` (es. `"Geist-Variable.woff2"`). |
| `TEXT_LIMITS` | vedi sotto | Limiti caratteri di tutti i campi testuali. |
| `UPLOAD_LIMITS` | vedi sotto | Dimensioni massime upload per famiglia di file. |
@@ -230,7 +230,7 @@ Lo zip così prodotto è caricabile direttamente dal pulsante "Ripristina da ZIP

Stato "di fabbrica" ripristinabile con un click. Pensato per preparare preset standard (es. banner + icona + una card di redirect) da distribuire a più macchine MajorNet.

**Visibile nell'admin solo se** `FACTORY_RESET_ENABLED = true` in `lib/config.ts`. Gli endpoint API restano comunque attivi anche con il flag a `false`.
**La sezione è sempre visibile in admin.** Il bottone "🏭 Factory Reset" e lo stato del preset corrente sono mostrati sempre (è un'operazione lecita per qualunque amministratore). Il bottone "💾 Salva come Factory Preset" — che riscrive il preset di fabbrica — è invece **gated** da `FACTORY_PRESET_SAVE_ENABLED = true` in `lib/config.ts` (default `false`): tienilo attivo solo sulla macchina di sviluppo dove prepari/aggiorni il preset. Gli endpoint API restano comunque attivi a prescindere dal flag.

Il preset è un file fisso: **`factory/preset.zip`** alla radice del progetto (fuori da `data/`, quindi non viene toccato dai reset; fuori da `public/`, quindi non scaricabile via web).

@@ -510,4 +510,4 @@ rm -rf .next && npm run build

**Recupero dopo un ripristino sbagliato**: lo stato precedente è in `data.bak-<timestamp>/`. Ferma il server, rinomina quella cartella in `data/` e riavvia.

**La sezione Factory Preset non compare**: è dietro il flag `FACTORY_RESET_ENABLED` in `lib/config.ts`. Mettilo a `true` e ricostruisci.
**Il bottone "Salva come Factory Preset" non compare**: è dietro il flag `FACTORY_PRESET_SAVE_ENABLED` in `lib/config.ts`. Mettilo a `true` e ricostruisci (di norma lo si tiene `false` in produzione). Il bottone "🏭 Factory Reset" e lo stato del preset restano comunque sempre visibili.

+ 35
- 32
app/admin/page.tsx Näytä tiedosto

@@ -2,18 +2,20 @@
import { useState, useEffect, useRef } from 'react';
import { Card, Portal, MediaItem, CardType } from '@/types';
import { EXTERNAL_LINK_ENABLED as EXTERNAL_LINK_DEFAULT, FACTORY_RESET_ENABLED } from '@/lib/config';
import { EXTERNAL_LINK_ENABLED as EXTERNAL_LINK_DEFAULT, FACTORY_PRESET_SAVE_ENABLED } from '@/lib/config';
import { CARD_LIMITS, PORTAL_LIMITS } from '@/lib/validation';
import { withBasePath } from '@/lib/url';
type CharCounterProps = { value: string | undefined; limit: number };
function CharCounter({ value, limit }: CharCounterProps) {
const len = (value ?? '').length;
if (len < limit * 0.8) return null;
const remaining = limit - len;
const overflow = len > limit;
const near = !overflow && len >= limit * 0.8;
const color = overflow ? 'text-red-600 font-semibold' : near ? 'text-amber-600' : 'text-gray-400';
return (
<p className={`text-xs mt-1 text-right ${overflow ? 'text-red-600 font-semibold' : 'text-gray-500'}`}>
{len} / {limit}
<p className={`text-xs mt-1 text-right ${color}`}>
{len} / {limit} · {remaining < 0 ? `${Math.abs(remaining)} oltre il limite` : `${remaining} rimanenti`}
</p>
);
}
@@ -614,7 +616,8 @@ export default function AdminDashboard() {
}
};
// Factory preset (developer): UI visibile solo se FACTORY_RESET_ENABLED.
// Factory preset: la sezione è sempre visibile (per chi accede a /admin); solo
// il bottone "Salva come preset" è gated da FACTORY_PRESET_SAVE_ENABLED.
const [factoryPreset, setFactoryPreset] = useState<{ exists: boolean; sizeBytes?: number; modifiedAt?: string } | null>(null);
const [savingPreset, setSavingPreset] = useState(false);
const [factoryResetting, setFactoryResetting] = useState(false);
@@ -626,7 +629,7 @@ export default function AdminDashboard() {
} catch { /* ignore */ }
};
useEffect(() => {
if (FACTORY_RESET_ENABLED) void refreshFactoryPreset();
void refreshFactoryPreset();
}, []);
const handleSaveFactoryPreset = async () => {
@@ -904,40 +907,40 @@ export default function AdminDashboard() {
</div>
</div>
{FACTORY_RESET_ENABLED && (
<div className="mt-8 pt-6 border-t border-gray-200">
<h3 className="text-sm font-bold uppercase tracking-wider text-gray-600 mb-3">Factory Preset <span className="text-[10px] bg-gray-200 text-gray-600 px-1.5 py-0.5 rounded ml-1 tracking-normal">developer</span></h3>
<p className="text-xs text-gray-500 mb-2">
Stato &ldquo;di fabbrica&rdquo; sempre ripristinabile con un click. Utile per preparare preset standard da distribuire alle macchine MajorNet:
configura il portale come vuoi, salvalo come preset, poi copia <code>factory/preset.zip</code> sulle altre macchine.
</p>
<p className="text-xs text-gray-700 mb-4">
Preset attuale: {factoryPreset === null ? '…'
: factoryPreset.exists
? <span className="text-green-700 font-medium">presente · {((factoryPreset.sizeBytes ?? 0) / (1024 * 1024)).toFixed(1)} MB · {factoryPreset.modifiedAt ? new Date(factoryPreset.modifiedAt).toLocaleString('it-IT') : '?'}</span>
: <span className="text-gray-400 italic">nessun preset configurato</span>}
</p>
<div className="flex flex-wrap gap-3">
<div className="mt-8 pt-6 border-t border-gray-200">
<h3 className="text-sm font-bold uppercase tracking-wider text-gray-600 mb-3">Factory Preset</h3>
<p className="text-xs text-gray-500 mb-2">
Stato &ldquo;di fabbrica&rdquo; ripristinabile con un click. Il preset (<code>factory/preset.zip</code>) viene preparato sulla macchina di sviluppo e distribuito alle macchine MajorNet.
</p>
<p className="text-xs text-gray-700 mb-4">
Preset attuale: {factoryPreset === null ? '…'
: factoryPreset.exists
? <span className="text-green-700 font-medium">presente · {((factoryPreset.sizeBytes ?? 0) / (1024 * 1024)).toFixed(1)} MB · {factoryPreset.modifiedAt ? new Date(factoryPreset.modifiedAt).toLocaleString('it-IT') : '?'}</span>
: <span className="text-gray-400 italic">nessun preset configurato</span>}
</p>
<div className="flex flex-wrap gap-3">
<button
type="button"
onClick={handleFactoryReset}
disabled={factoryResetting || !factoryPreset?.exists}
title={factoryPreset?.exists ? undefined : 'Nessun preset configurato'}
className="bg-red-700 text-white px-5 py-2.5 rounded-lg hover:bg-red-800 font-medium shadow-sm disabled:opacity-60 disabled:cursor-not-allowed"
>
{factoryResetting ? 'Reset in corso…' : '🏭 Factory Reset'}
</button>
{FACTORY_PRESET_SAVE_ENABLED && (
<button
type="button"
onClick={handleSaveFactoryPreset}
disabled={savingPreset}
title="Funzione developer: salva lo stato corrente come nuovo preset di fabbrica"
className="bg-emerald-700 text-white px-5 py-2.5 rounded-lg hover:bg-emerald-800 font-medium shadow-sm disabled:opacity-60"
>
{savingPreset ? 'Salvataggio…' : '💾 Salva stato attuale come Factory Preset'}
{savingPreset ? 'Salvataggio…' : '💾 Salva come Factory Preset (dev)'}
</button>
<button
type="button"
onClick={handleFactoryReset}
disabled={factoryResetting || !factoryPreset?.exists}
title={factoryPreset?.exists ? undefined : 'Nessun preset configurato'}
className="bg-red-700 text-white px-5 py-2.5 rounded-lg hover:bg-red-800 font-medium shadow-sm disabled:opacity-60 disabled:cursor-not-allowed"
>
{factoryResetting ? 'Reset in corso…' : '🏭 Factory Reset'}
</button>
</div>
)}
</div>
)}
</div>
<div className="mt-10 pt-6 border-t border-gray-200 flex justify-end">
<button onClick={handleSavePortal} disabled={savingPortal} className="bg-blue-600 text-white px-10 py-3 rounded-lg hover:bg-blue-700 font-bold shadow disabled:opacity-50 transition-colors">


+ 8
- 4
lib/config.ts Näytä tiedosto

@@ -7,10 +7,14 @@ export const BASE_PATH = '/cards';

export const EXTERNAL_LINK_ENABLED = true;

// Mostra la sezione "Factory Preset" (salva preset + factory reset) nell'admin.
// Funzione developer: tienila a false in produzione, true solo quando serve
// preparare/distribuire un preset standard. Gli endpoint API restano comunque attivi.
export const FACTORY_RESET_ENABLED = false;
// Mostra il bottone "Salva stato attuale come Factory Preset" nell'admin.
// È una funzione developer: tienila a false in produzione, true solo sulla macchina
// dove serve preparare/aggiornare il preset di fabbrica.
// Il bottone "Factory Reset" e lo stato del preset restano comunque sempre visibili
// nell'admin (operazione ammessa per chiunque abbia accesso a /cards/admin).
// L'endpoint POST /api/admin/factory-preset resta sempre attivo: un developer
// può creare il preset anche via curl, indipendentemente da questo flag.
export const FACTORY_PRESET_SAVE_ENABLED = false;

// Font di default se il portale non ne ha impostato uno.
// Lascia stringa vuota per usare il font di sistema (Arial).


Ladataan…
Peruuta
Tallenna