diff --git a/app/admin/page.tsx b/app/admin/page.tsx index 1362184..bd7c413 100644 --- a/app/admin/page.tsx +++ b/app/admin/page.tsx @@ -317,7 +317,7 @@ export default function AdminDashboard() { // Auto-seleziona il font appena caricato if (data.name) setPortal(p => ({ ...p, fontFamily: data.name })); } catch (err) { - showToast(`Network error: ${(err as Error).message}`, 'error'); + showToast(`Could not reach the server. Check your network connection and try again. Details: ${(err as Error).message}`, 'error'); } finally { setUploadingFont(false); } @@ -336,7 +336,7 @@ export default function AdminDashboard() { await refreshFonts(); if (portal.fontFamily === name) setPortal(p => ({ ...p, fontFamily: '' })); } catch (err) { - showToast(`Network error: ${(err as Error).message}`, 'error'); + showToast(`Could not reach the server. Check your network connection and try again. Details: ${(err as Error).message}`, 'error'); } }; @@ -669,7 +669,7 @@ export default function AdminDashboard() { showToast(`Restore completed: ${data.restored?.cards ?? 0} cards, ${data.restored?.portals ?? 0} portals. Reloading…`); setTimeout(() => window.location.reload(), 1200); } catch (err) { - showToast(`Network error: ${(err as Error).message}`, 'error'); + showToast(`Could not reach the server. Check your network connection and try again. Details: ${(err as Error).message}`, 'error'); } finally { setRestoring(false); } @@ -707,7 +707,7 @@ export default function AdminDashboard() { showToast('Factory preset updated.'); await refreshFactoryPreset(); } catch (err) { - showToast(`Network error: ${(err as Error).message}`, 'error'); + showToast(`Could not reach the server. Check your network connection and try again. Details: ${(err as Error).message}`, 'error'); } finally { setSavingPreset(false); } @@ -726,7 +726,7 @@ export default function AdminDashboard() { showToast(`Factory reset completed: ${data.restored?.cards ?? 0} cards, ${data.restored?.portals ?? 0} portals. Reloading…`); setTimeout(() => window.location.reload(), 1200); } catch (err) { - showToast(`Network error: ${(err as Error).message}`, 'error'); + showToast(`Could not reach the server. Check your network connection and try again. Details: ${(err as Error).message}`, 'error'); } finally { setFactoryResetting(false); } diff --git a/app/api/admin/backup/route.ts b/app/api/admin/backup/route.ts index 1deed59..276e438 100644 --- a/app/api/admin/backup/route.ts +++ b/app/api/admin/backup/route.ts @@ -16,7 +16,10 @@ function timestamp(): string { export async function GET() { if (!(await checkSystemBin('zip', '-v'))) { - return NextResponse.json({ error: "Binario 'zip' non disponibile sul server." }, { status: 503 }); + return NextResponse.json( + { error: "Cannot create the backup: the 'zip' command is not installed on the server. Ask the administrator to install it." }, + { status: 503 }, + ); } // -r ricorsivo, - stdout, -x esclude pattern relativi al cwd. diff --git a/app/api/admin/factory-preset/route.ts b/app/api/admin/factory-preset/route.ts index 9969ef2..61b3636 100644 --- a/app/api/admin/factory-preset/route.ts +++ b/app/api/admin/factory-preset/route.ts @@ -31,7 +31,10 @@ export async function GET() { // POST — crea il preset zippando lo stato corrente di data/. export async function POST() { if (!(await checkSystemBin('zip', '-v'))) { - return NextResponse.json({ error: "Binario 'zip' non disponibile sul server." }, { status: 503 }); + return NextResponse.json( + { error: "Cannot create the preset: the 'zip' command is not installed on the server. Ask the administrator to install it." }, + { status: 503 }, + ); } await mkdir(FACTORY_DIR, { recursive: true }); @@ -58,7 +61,10 @@ export async function POST() { // 0 = ok, 12 = "nothing to do" (data/ vuoto: accettiamo, sarà uno zip minimale) if (exit !== 0 && exit !== 12) { try { await unlink(tmpPath); } catch { /* ignore */ } - return NextResponse.json({ error: `zip terminato con codice ${exit}` }, { status: 500 }); + return NextResponse.json( + { error: `Failed to create the preset archive: the 'zip' command exited with code ${exit}. Check the server logs for details. The previous preset (if any) has not been modified.` }, + { status: 500 }, + ); } await rename(tmpPath, PRESET_PATH); diff --git a/app/api/admin/factory-reset/route.ts b/app/api/admin/factory-reset/route.ts index b7af1b5..3585715 100644 --- a/app/api/admin/factory-reset/route.ts +++ b/app/api/admin/factory-reset/route.ts @@ -11,14 +11,17 @@ const PRESET_PATH = path.join(process.cwd(), 'factory', 'preset.zip'); export async function POST() { if (!(await checkSystemBin('unzip', '-v'))) { - return NextResponse.json({ error: "Binario 'unzip' non disponibile sul server." }, { status: 503 }); + return NextResponse.json( + { error: "Cannot perform Factory Reset: the 'unzip' command is not installed on the server. Ask the administrator to install it." }, + { status: 503 }, + ); } try { await stat(PRESET_PATH); } catch { return NextResponse.json( - { error: 'Nessun factory preset configurato. Imposta prima lo stato attuale come preset.' }, + { error: 'No factory preset is configured on this server: factory/preset.zip is missing. Ask a developer to create a preset first (either via "Save as Factory Preset" in admin or by uploading factory/preset.zip manually).' }, { status: 404 }, ); } diff --git a/app/api/admin/fonts/route.ts b/app/api/admin/fonts/route.ts index 57f257b..2821ad9 100644 --- a/app/api/admin/fonts/route.ts +++ b/app/api/admin/fonts/route.ts @@ -37,24 +37,31 @@ export async function POST(request: Request) { const formData = await request.formData(); const file = formData.get('file') as File | null; if (!file) { - return NextResponse.json({ error: 'No file received.' }, { status: 400 }); + return NextResponse.json( + { error: 'No font file was received. Choose a font file before clicking Upload.' }, + { status: 400 }, + ); } const safeName = sanitizeFontName(file.name); const ext = path.extname(safeName).toLowerCase(); if (!ALLOWED_EXT.has(ext)) { return NextResponse.json( - { error: `Unsupported font extension. Allowed: ${[...ALLOWED_EXT].join(', ')}` }, + { error: `Font extension "${ext || '(none)'}" is not supported. Allowed formats: ${[...ALLOWED_EXT].join(', ')}.` }, { status: 400 }, ); } if (!safeName || safeName === ext) { - return NextResponse.json({ error: 'Invalid font filename.' }, { status: 400 }); + return NextResponse.json( + { error: 'Invalid font filename. The file name must contain at least one letter, digit, hyphen or underscore before the extension.' }, + { status: 400 }, + ); } if (file.size > UPLOAD_LIMITS.font) { - const mb = (UPLOAD_LIMITS.font / (1024 * 1024)).toFixed(0); + const limitMB = (UPLOAD_LIMITS.font / (1024 * 1024)).toFixed(0); + const actualMB = (file.size / (1024 * 1024)).toFixed(1); return NextResponse.json( - { error: `Font too large (max ${mb} MB).` }, + { error: `Font is too large: ${actualMB} MB (limit: ${limitMB} MB). Web fonts are typically under 500 KB — convert the file to WOFF2 (e.g. with a font-tools subsetter) before uploading.` }, { status: 413 }, ); } @@ -65,14 +72,14 @@ export async function POST(request: Request) { const detected = await fileTypeFromBuffer(buffer); if (!detected) { return NextResponse.json( - { error: 'Font content not recognized (unknown format).' }, + { error: 'Could not identify the font content. The file may be corrupted or not actually a font. Try re-exporting from your font tool.' }, { status: 400 }, ); } const allowed = ALLOWED_DETECTED[ext] ?? []; if (!allowed.includes(detected.ext)) { return NextResponse.json( - { error: `Font content does not match extension (${ext} declared, detected ${detected.ext}).` }, + { error: `Font content does not match its extension: the file looks like ${detected.ext.toUpperCase()} but the extension is "${ext}". Rename the file or re-export it in the declared format.` }, { status: 400 }, ); } @@ -83,7 +90,10 @@ export async function POST(request: Request) { return NextResponse.json({ ok: true, name: safeName }, { status: 201 }); } catch (error) { console.error('Font upload error:', error); - return NextResponse.json({ error: 'Failed to upload font.' }, { status: 500 }); + return NextResponse.json( + { error: 'Unexpected error while saving the font. The upload was aborted. Check the server logs for details and try again.' }, + { status: 500 }, + ); } } @@ -93,25 +103,37 @@ export async function DELETE(request: Request) { const { searchParams } = new URL(request.url); const rawName = searchParams.get('name'); if (!rawName) { - return NextResponse.json({ error: 'Missing name parameter.' }, { status: 400 }); + return NextResponse.json( + { error: 'Missing "name" parameter. Specify which font file to delete.' }, + { status: 400 }, + ); } const safeName = sanitizeFontName(rawName); const ext = path.extname(safeName).toLowerCase(); if (!ALLOWED_EXT.has(ext) || !safeName) { - return NextResponse.json({ error: 'Invalid font name.' }, { status: 400 }); + return NextResponse.json( + { error: `Invalid font name "${rawName}". Expected a basename ending in ${[...ALLOWED_EXT].join(' / ')}.` }, + { status: 400 }, + ); } try { await unlink(path.join(FONTS_DIR, safeName)); } catch (err) { const e = err as NodeJS.ErrnoException; if (e.code === 'ENOENT') { - return NextResponse.json({ error: 'Font not found.' }, { status: 404 }); + return NextResponse.json( + { error: `Font "${safeName}" was not found on the server. It may have already been deleted — refresh the page.` }, + { status: 404 }, + ); } throw err; } return NextResponse.json({ ok: true }); } catch (error) { console.error('Font delete error:', error); - return NextResponse.json({ error: 'Failed to delete font.' }, { status: 500 }); + return NextResponse.json( + { error: 'Unexpected error while deleting the font. Check the server logs and try again.' }, + { status: 500 }, + ); } } diff --git a/app/api/admin/restore/route.ts b/app/api/admin/restore/route.ts index 73c6283..9db78ff 100644 --- a/app/api/admin/restore/route.ts +++ b/app/api/admin/restore/route.ts @@ -13,7 +13,10 @@ const UPLOAD_STAGING = path.join(PROJECT_ROOT, '.restore-staging'); export async function POST(request: Request) { if (!(await checkSystemBin('unzip', '-v'))) { - return NextResponse.json({ error: "Binario 'unzip' non disponibile sul server." }, { status: 503 }); + return NextResponse.json( + { error: "Restore is unavailable: the 'unzip' command is not installed on the server. Ask the administrator to install it." }, + { status: 503 }, + ); } const sessionId = crypto.randomUUID(); @@ -24,7 +27,10 @@ export async function POST(request: Request) { const formData = await request.formData(); const file = formData.get('file') as File | null; if (!file) { - return NextResponse.json({ error: 'Nessun file ricevuto.' }, { status: 400 }); + return NextResponse.json( + { error: 'No backup file was received. Choose a .zip backup file before clicking Restore.' }, + { status: 400 }, + ); } await mkdir(sessionDir, { recursive: true }); @@ -41,7 +47,10 @@ export async function POST(request: Request) { }); } catch (error) { console.error('Restore error:', error); - return NextResponse.json({ error: 'Errore durante il ripristino.' }, { status: 500 }); + return NextResponse.json( + { error: 'Unexpected error during restore. Existing data was not changed. Check the server logs for details and try again.' }, + { status: 500 }, + ); } finally { try { await rm(sessionDir, { recursive: true, force: true }); } catch { /* ignore */ } } diff --git a/app/api/cards/route.ts b/app/api/cards/route.ts index a6902ab..3059911 100644 --- a/app/api/cards/route.ts +++ b/app/api/cards/route.ts @@ -20,7 +20,10 @@ export async function POST(request: Request) { const validation = validateCard(incomingCard); if (!validation.valid) { - return NextResponse.json({ error: 'Validation failed', errors: validation.errors }, { status: 400 }); + return NextResponse.json( + { error: 'The card could not be saved because some fields are invalid. See the highlighted errors and fix them, then try again.', errors: validation.errors }, + { status: 400 }, + ); } if (typeof incomingCard.fullContent === 'string') { @@ -40,7 +43,10 @@ export async function POST(request: Request) { revalidatePath('/'); // Force public portal to update instantly return NextResponse.json(incomingCard, { status: 200 }); } catch { - return NextResponse.json({ error: 'Failed to save card' }, { status: 500 }); + return NextResponse.json( + { error: 'Unexpected error while saving the card. The card was not saved. Check the server logs for details and try again.' }, + { status: 500 }, + ); } } @@ -51,8 +57,11 @@ export async function PUT(request: Request) { await saveCards(updatedCards); revalidatePath('/'); // Force public portal to update instantly return NextResponse.json({ success: true }, { status: 200 }); - } catch (error) { - return NextResponse.json({ error: 'Failed to reorder cards' }, { status: 500 }); + } catch { + return NextResponse.json( + { error: 'Unexpected error while saving the new card order. The order was not saved. Reload the page and try again.' }, + { status: 500 }, + ); } } @@ -61,7 +70,10 @@ export async function DELETE(request: Request) { const { searchParams } = new URL(request.url); const id = searchParams.get('id'); - if (!id) return NextResponse.json({ error: 'Card ID required' }, { status: 400 }); + if (!id) return NextResponse.json( + { error: 'Missing "id" parameter: cannot tell which card to delete. Reload the page and retry.' }, + { status: 400 }, + ); const cards = await getCards(); const filteredCards = cards.filter(c => c.id !== id); @@ -69,7 +81,10 @@ export async function DELETE(request: Request) { await saveCards(filteredCards); revalidatePath('/'); // Force public portal to update instantly return NextResponse.json({ success: true }, { status: 200 }); - } catch (error) { - return NextResponse.json({ error: 'Failed to delete card' }, { status: 500 }); + } catch { + return NextResponse.json( + { error: 'Unexpected error while deleting the card. The card was not deleted. Check the server logs and try again.' }, + { status: 500 }, + ); } } \ No newline at end of file diff --git a/app/api/portals/route.ts b/app/api/portals/route.ts index 133234f..309f982 100644 --- a/app/api/portals/route.ts +++ b/app/api/portals/route.ts @@ -24,15 +24,21 @@ export async function POST(request: Request) { const { valid, errors } = validatePortal(incomingPortal); if (!valid) { - return NextResponse.json({ error: 'Validation failed', errors }, { status: 400 }); + return NextResponse.json( + { error: 'The portal settings could not be saved because some fields are invalid. See the highlighted errors and fix them, then try again.', errors }, + { status: 400 }, + ); } // themeColor goes into a