feat(cms): improve admin content editing
This commit is contained in:
parent
12d31f5468
commit
a284bbc188
26 changed files with 362 additions and 147 deletions
|
|
@ -20,6 +20,7 @@ export const defaultContent: ContentDocuments = {
|
|||
title: "Funktechnik Schubert",
|
||||
subtitle: "Service, Reparatur und Diagnose für Funkgeräte, Messtechnik und Kommunikationstechnik.",
|
||||
heroText: "Von CB-Funk und Amateurfunk bis zur professionellen Funktechnik: Wir unterstützen bei Fehlersuche, Abgleich, Modulationsproblemen, Frequenzproblemen und technischer Dokumentation.",
|
||||
heroImage: "/workbench-signal.svg",
|
||||
intro: "Von der ersten Fehlerbeschreibung bis zur dokumentierten Prüfung: Funktechnik Schubert verbindet technische Erfahrung, geeignete Messmittel und strukturierte Kommunikation.",
|
||||
paragraphs: [],
|
||||
cta: {
|
||||
|
|
@ -62,6 +63,7 @@ export const defaultContent: ContentDocuments = {
|
|||
title: "Service für Funkgeräte und Kommunikationstechnik",
|
||||
subtitle: "Technische Unterstützung für Geräte, bei denen Diagnose, Messtechnik, Erfahrung und saubere Dokumentation zählen.",
|
||||
heroText: "Leistungen für private und professionelle Funktechnik mit klarem Fokus auf Diagnose, Abgleich und Reparatur.",
|
||||
heroImage: "/workbench-signal.svg",
|
||||
intro: "Die Leistungen können im Adminbereich sortiert, erweitert und ein- oder ausgeblendet werden.",
|
||||
paragraphs: [],
|
||||
cta: { text: "Reparaturfall vorbereiten", primary: { label: "Reparatur anfragen", href: "/reparatur" }, secondary: { label: "Kontakt aufnehmen", href: "/kontakt" } },
|
||||
|
|
@ -83,6 +85,7 @@ export const defaultContent: ContentDocuments = {
|
|||
title: "Prüfung, Diagnose und Abgleich",
|
||||
subtitle: "Funkgeräte zeigen Fehler oft erst im Zusammenspiel von Empfang, Sendeteil, Versorgung, Antennenanpassung, Bedienung und Abgleich.",
|
||||
heroText: "Der Service betrachtet diese Zusammenhänge strukturiert und dokumentiert die technischen Schritte nachvollziehbar.",
|
||||
heroImage: "/workbench-signal.svg",
|
||||
intro: "Eine gute Reparatur beginnt mit vollständigen Angaben zu Gerät, Fehlerbild und Vorgeschichte.",
|
||||
paragraphs: [],
|
||||
cta: { text: "Reparaturprozess starten", primary: { label: "Reparatur anfragen", href: "/reparatur" }, secondary: { label: "Kontakt", href: "/kontakt" } },
|
||||
|
|
@ -105,11 +108,35 @@ export const defaultContent: ContentDocuments = {
|
|||
seo: seo("Funkgeräte-Service", "Werkstattservice für CB-Funk, Amateurfunk und Funkgeräte-Abgleich.", "/funkgeraete-service"),
|
||||
updatedAt: now,
|
||||
},
|
||||
repair: {
|
||||
eyebrow: "Reparaturannahme",
|
||||
title: "Reparatur anfragen",
|
||||
subtitle: "Beschreiben Sie Gerät, Fehlerbild, Zubehör und bisherige Vorarbeiten.",
|
||||
heroText: "Die Anfrage wird strukturiert erfasst und für eine spätere technische Bearbeitung vorbereitet.",
|
||||
heroImage: "/workbench-signal.svg",
|
||||
intro: "Bitte senden Sie Geräte erst nach Rückmeldung ein. Vollständige Gerätedaten und eine präzise Fehlerbeschreibung helfen, den Aufwand besser einzuschätzen.",
|
||||
paragraphs: [
|
||||
{ title: "Vor der Einsendung", text: "Bitte senden Sie Geräte erst nach Rückmeldung ein. Vollständige Gerätedaten und eine präzise Fehlerbeschreibung helfen, den Aufwand besser einzuschätzen." },
|
||||
],
|
||||
cta: { text: "Reparatur vorbereiten", primary: { label: "Formular ausfüllen", href: "/reparatur" }, secondary: { label: "Kontakt", href: "/kontakt" } },
|
||||
listTitle: "Wichtige Angaben",
|
||||
list: [
|
||||
"Hersteller, Modell, Geräteart und Seriennummer bereithalten, soweit vorhanden",
|
||||
"Fehler möglichst konkret beschreiben: Empfang, Sendung, Modulation, Frequenz, Anzeige oder Versorgung",
|
||||
"Zubehör wie Netzteil, Mikrofon, Antennenadapter oder Kabel angeben",
|
||||
"Bereits geöffnete Geräte und durchgeführte Arbeiten ehrlich nennen",
|
||||
"Fotos können später nach Rückmeldung ergänzt werden",
|
||||
],
|
||||
faq: [],
|
||||
seo: seo("Reparaturannahme", "Reparaturanfrage für Funkgeräte strukturiert vorbereiten.", "/reparatur"),
|
||||
updatedAt: now,
|
||||
},
|
||||
about: {
|
||||
eyebrow: "Über uns",
|
||||
title: "Technik verstehen, Fehler nachvollziehbar lösen",
|
||||
subtitle: "Funktechnik Schubert richtet sich an Kunden, die bei Funkgeräten und Kommunikationstechnik eine persönliche, technische Einschätzung suchen.",
|
||||
heroText: "Im Mittelpunkt stehen saubere Diagnose, transparente Kommunikation und respektvoller Umgang mit bestehenden Geräten.",
|
||||
heroImage: "/workbench-signal.svg",
|
||||
intro: "Technischer Service lebt von Genauigkeit, Erfahrung und klarer Rückmeldung.",
|
||||
paragraphs: [
|
||||
{ title: "Arbeitsweise", text: "Im Mittelpunkt stehen saubere Diagnose, transparente Kommunikation und der respektvolle Umgang mit bestehenden Geräten und Serviceunterlagen." },
|
||||
|
|
@ -130,6 +157,7 @@ export const defaultContent: ContentDocuments = {
|
|||
title: "Kontakt aufnehmen",
|
||||
subtitle: "Beschreiben Sie kurz Ihr Anliegen. Für Reparaturen nutzen Sie idealerweise die strukturierte Reparaturannahme.",
|
||||
heroText: "Je genauer Gerät, Anliegen und gewünschte Unterstützung beschrieben werden, desto gezielter kann die Rückmeldung erfolgen.",
|
||||
heroImage: "/workbench-signal.svg",
|
||||
intro: "Direkt und technisch: Nutzen Sie das Kontaktformular oder die Reparaturannahme.",
|
||||
paragraphs: [],
|
||||
cta: { text: "Anfrage senden", primary: { label: "Reparatur anfragen", href: "/reparatur" }, secondary: { label: "Leistungen", href: "/leistungen" } },
|
||||
|
|
@ -170,6 +198,7 @@ export const contentFileNames = {
|
|||
home: "home.json",
|
||||
services: "services.json",
|
||||
"radio-service": "radio-service.json",
|
||||
repair: "repair.json",
|
||||
about: "about.json",
|
||||
contact: "contact.json",
|
||||
settings: "settings.json",
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ import path from "path";
|
|||
import { storageDirectory } from "@/lib/runtime/config";
|
||||
import { listMediaFiles } from "@/lib/admin/media";
|
||||
import { contentFileNames, defaultContent } from "./defaults";
|
||||
import type { ContentDocuments, ContentKey, ContentSummary } from "./types";
|
||||
import type { ContentBackup, ContentDocuments, ContentKey, ContentSummary } from "./types";
|
||||
|
||||
export const contentDirectory = path.join(storageDirectory, "content");
|
||||
export const contentBackupDirectory = path.join(contentDirectory, "backups");
|
||||
export const contentVersion = "0.4.0";
|
||||
export const contentVersion = "0.4.1";
|
||||
|
||||
const contentKeys = Object.keys(contentFileNames) as ContentKey[];
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ function filePathFor(key: ContentKey) {
|
|||
function backupStamp() {
|
||||
const date = new Date();
|
||||
const pad = (value: number) => String(value).padStart(2, "0");
|
||||
return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}-${pad(date.getHours())}${pad(date.getMinutes())}`;
|
||||
return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}-${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`;
|
||||
}
|
||||
|
||||
async function pruneBackups(key: ContentKey) {
|
||||
|
|
@ -144,10 +144,33 @@ export async function getContentSummary(): Promise<ContentSummary> {
|
|||
};
|
||||
}
|
||||
|
||||
export async function listContentBackups(limit = 20): Promise<ContentBackup[]> {
|
||||
await ensureContentStorage();
|
||||
const entries = await readdir(contentBackupDirectory).catch(() => []);
|
||||
const backups = await Promise.all(entries
|
||||
.filter((entry) => entry.endsWith(".json"))
|
||||
.map(async (entry) => {
|
||||
const page = entry.split(".")[0] as ContentKey;
|
||||
const fullPath = path.join(contentBackupDirectory, entry);
|
||||
const fileStat = await stat(fullPath);
|
||||
return {
|
||||
page,
|
||||
fileName: entry,
|
||||
createdAt: fileStat.mtime.toISOString(),
|
||||
};
|
||||
}));
|
||||
|
||||
return backups
|
||||
.filter((backup) => contentKeys.includes(backup.page))
|
||||
.sort((left, right) => right.createdAt.localeCompare(left.createdAt))
|
||||
.slice(0, limit);
|
||||
}
|
||||
|
||||
export async function getContentSystemStatus() {
|
||||
const [summary, media] = await Promise.all([getContentSummary(), listMediaFiles()]);
|
||||
const [summary, media, backups] = await Promise.all([getContentSummary(), listMediaFiles(), listContentBackups()]);
|
||||
return {
|
||||
...summary,
|
||||
mediaCount: media.length,
|
||||
backups,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ export type PageContent = {
|
|||
title: string;
|
||||
subtitle: string;
|
||||
heroText: string;
|
||||
heroImage: string;
|
||||
intro: string;
|
||||
paragraphs: TextBlockContent[];
|
||||
cta: {
|
||||
|
|
@ -92,6 +93,8 @@ export type ContactContent = PageContent & {
|
|||
socialMedia: LinkContent[];
|
||||
};
|
||||
|
||||
export type RepairContent = PageContent;
|
||||
|
||||
export type SettingsContent = {
|
||||
siteName: string;
|
||||
logoUrl: string;
|
||||
|
|
@ -108,6 +111,7 @@ export type ContentDocuments = {
|
|||
home: HomeContent;
|
||||
services: ServicesContent;
|
||||
"radio-service": RadioServiceContent;
|
||||
repair: RepairContent;
|
||||
about: AboutContent;
|
||||
contact: ContactContent;
|
||||
settings: SettingsContent;
|
||||
|
|
@ -126,3 +130,9 @@ export type ContentSummary = {
|
|||
updatedAt: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
export type ContentBackup = {
|
||||
page: ContentKey;
|
||||
fileName: string;
|
||||
createdAt: string;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue