feat(pages): add page opening, page deletion API, edit mode toggle and custom 404 page
All checks were successful
Production Build & Deploy / build-and-deploy (push) Successful in 1m10s

This commit is contained in:
Daniel S
2026-08-09 22:30:13 +02:00
parent 334c6723e9
commit e8f332da43
4 changed files with 117 additions and 4 deletions

25
src/pages/404.astro Normal file
View File

@@ -0,0 +1,25 @@
---
// src/pages/404.astro
import Layout from '../layouts/Layout.astro';
---
<Layout title="404 - Seite nicht gefunden | N&D IT Solutions">
<main class="min-h-screen bg-[#0B0F19] text-white flex items-center justify-center p-4">
<div class="preset-card p-10 bg-slate-900/80 border border-white/10 rounded-2xl max-w-lg w-full text-center space-y-6 shadow-2xl backdrop-blur-md">
<span class="text-6xl font-extrabold text-sky-400 font-mono tracking-widest block">404</span>
<h1 class="text-2xl font-bold text-white">Seite nicht gefunden</h1>
<p class="text-slate-400 text-sm leading-relaxed">
Die von Ihnen angeforderte Seite existiert nicht oder wurde verschoben.
</p>
<div class="pt-4">
<a
href="/"
class="inline-block bg-sky-500 hover:bg-sky-400 text-slate-950 font-bold px-6 py-3 rounded-xl text-sm transition-all shadow-lg shadow-sky-500/20 active:scale-95"
>
← Zurück zur Startseite
</a>
</div>
</div>
</main>
</Layout>

View File

@@ -19,13 +19,17 @@ const presets = Object.values(THEME_PRESETS);
<h1 class="text-3xl font-extrabold tracking-tight mt-1">System & Admin Dashboard</h1> <h1 class="text-3xl font-extrabold tracking-tight mt-1">System & Admin Dashboard</h1>
</div> </div>
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<a id="toggle-preview-mode" href="/?preview=draft" target="_blank" class="px-4 py-2 rounded-xl bg-amber-500/10 text-amber-400 border border-amber-500/20 text-xs font-semibold hover:bg-amber-500 hover:text-slate-950 transition-all flex items-center gap-2">
🛠️ Bearbeitungsmodus (Entwurf-Vorschau)
</a>
<a href="/" target="_blank" class="px-4 py-2 rounded-xl glass-panel text-xs font-semibold hover:border-sky-400 transition-all flex items-center gap-2"> <a href="/" target="_blank" class="px-4 py-2 rounded-xl glass-panel text-xs font-semibold hover:border-sky-400 transition-all flex items-center gap-2">
🌐 Live Seite Ansehen 🌐 Live-Seite Ansehen
</a> </a>
<button id="save-all-btn" class="px-6 py-2.5 rounded-xl bg-sky-400 text-slate-950 font-bold text-xs uppercase tracking-wider hover:bg-white transition-all shadow-lg shadow-sky-400/20 active:scale-95"> <button id="save-all-btn" class="px-6 py-2.5 rounded-xl bg-sky-400 text-slate-950 font-bold text-xs uppercase tracking-wider hover:bg-white transition-all shadow-lg shadow-sky-400/20 active:scale-95">
Einstellungen Speichern Einstellungen Speichern
</button> </button>
</div> </div>
</div> </div>
<!-- Quick Admin Nav Cards / Links --> <!-- Quick Admin Nav Cards / Links -->

View File

@@ -66,15 +66,35 @@ const currentHomepageId = (siteConfig as any).site_info?.homepage_id;
</td> </td>
<td class="p-4 text-right space-x-2"> <td class="p-4 text-right space-x-2">
<a <a
href={`/admin/editor/?page_id=${page.id}`} href={page.slug}
class="bg-slate-800 hover:bg-slate-700 text-white px-3 py-1.5 rounded text-xs font-medium inline-block transition-colors" target="_blank"
class="bg-slate-800 hover:bg-slate-700 text-sky-400 px-2.5 py-1.5 rounded text-xs font-medium inline-block transition-colors"
title="Seite im Browser öffnen"
> >
✏️ Im Editor öffnen 🔗 Öffnen
</a> </a>
<a
href={`/admin/editor/?page_id=${page.id}`}
class="bg-sky-500/20 hover:bg-sky-500 text-sky-300 hover:text-slate-950 px-2.5 py-1.5 rounded text-xs font-medium inline-block transition-colors border border-sky-500/30"
>
✏️ Editor
</a>
{!isHomepage && (
<button
type="button"
data-page-id={page.id}
data-page-title={page.title}
class="btn-delete-page bg-red-500/10 hover:bg-red-500 text-red-400 hover:text-white px-2.5 py-1.5 rounded text-xs font-medium inline-block transition-colors border border-red-500/20"
title="Seite löschen"
>
🗑️ Löschen
</button>
)}
</td> </td>
</tr> </tr>
); );
})} })}
</tbody> </tbody>
</table> </table>
</div> </div>
@@ -138,4 +158,28 @@ const currentHomepageId = (siteConfig as any).site_info?.homepage_id;
window.location.reload(); window.location.reload();
} }
}); });
// Event: Seite löschen
document.querySelectorAll('.btn-delete-page').forEach(btn => {
btn.addEventListener('click', async (e: any) => {
const pageId = e.target.getAttribute('data-page-id');
const pageTitle = e.target.getAttribute('data-page-title');
if (!confirm(`Möchten Sie die Seite "${pageTitle}" wirklich unwiderruflich löschen?`)) return;
const res = await fetch('/api/admin/pages/delete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ page_id: pageId })
});
if (res.ok) {
window.location.reload();
} else {
const data = await res.json();
alert(data.error || 'Fehler beim Löschen der Seite.');
}
});
});
</script> </script>

View File

@@ -0,0 +1,40 @@
import type { APIRoute } from 'astro';
import fs from 'node:fs/promises';
import path from 'node:path';
export const prerender = false;
const DATA_DIR = process.env.DATA_DIR || path.join(process.cwd(), 'app', 'data');
export const POST: APIRoute = async ({ request }) => {
try {
const { page_id } = await request.json();
if (!page_id) {
return new Response(JSON.stringify({ error: 'page_id erforderlich' }), { status: 400 });
}
const configPath = path.join(DATA_DIR, 'site.config.json');
let config: any = {};
try {
const rawData = await fs.readFile(configPath, 'utf-8');
config = JSON.parse(rawData);
} catch {
return new Response(JSON.stringify({ error: 'Konfigurationsdatei nicht gefunden' }), { status: 404 });
}
// Verhindere Löschen der aktiven Homepage
if (config.site_info?.homepage_id === page_id) {
return new Response(JSON.stringify({ error: 'Die aktive Startseite kann nicht gelöscht werden.' }), { status: 400 });
}
config.pages = (config.pages || []).filter((p: any) => p.id !== page_id);
await fs.mkdir(DATA_DIR, { recursive: true });
await fs.writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8');
return new Response(JSON.stringify({ success: true, message: 'Seite erfolgreich gelöscht' }), { status: 200 });
} catch (error) {
return new Response(JSON.stringify({ error: 'Fehler beim Löschen der Seite' }), { status: 500 });
}
};