From a4e3d10fb0c97ea07c4b08d16c0947945d8a7576 Mon Sep 17 00:00:00 2001 From: Luca <102960244+Luca-Timo@users.noreply.github.com> Date: Sun, 3 May 2026 23:05:51 +0200 Subject: [PATCH] feat(cms): admin UI for external imprint/privacy URL --- frontend/src/i18n/locales/de.json | 8 +++- frontend/src/i18n/locales/en.json | 8 +++- frontend/src/pages/admin/CMSPage.tsx | 60 ++++++++++++++++++++++++++++ frontend/src/services/cms.service.ts | 4 ++ 4 files changed, 78 insertions(+), 2 deletions(-) diff --git a/frontend/src/i18n/locales/de.json b/frontend/src/i18n/locales/de.json index 2456c406..1e785525 100644 --- a/frontend/src/i18n/locales/de.json +++ b/frontend/src/i18n/locales/de.json @@ -2187,7 +2187,13 @@ "lastUpdated": "Zuletzt aktualisiert:", "impressum": "Impressum", "datenschutz": "Datenschutzerklärung", - "pageUpdated": "Seite erfolgreich aktualisiert" + "pageUpdated": "Seite erfolgreich aktualisiert", + "useExternalUrl": "Externe URL verwenden", + "useExternalUrlHelp": "Besucher werden auf eine externe Seite weitergeleitet, anstatt die internen Inhalte anzuzeigen. Titel und Inhalt bleiben als Fallback gespeichert.", + "externalUrl": "Externe URL", + "externalUrlPlaceholder": "https://example.com/impressum", + "externalUrlInvalid": "Muss eine gültige https://-URL sein", + "externalUrlActive": "Externe URL ist aktiv — interne Inhalte bleiben gespeichert, werden Besuchern aber nicht angezeigt." }, "validation": { "eventNameRequired": "Veranstaltungsname ist erforderlich", diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index 0c571243..0c49ca30 100644 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -1736,7 +1736,13 @@ "lastUpdated": "Last updated:", "impressum": "Legal Notice", "datenschutz": "Privacy Policy", - "pageUpdated": "Page updated successfully" + "pageUpdated": "Page updated successfully", + "useExternalUrl": "Use external URL", + "useExternalUrlHelp": "Redirect visitors to an external page instead of showing the internal content. The internal title and content stay saved as a fallback.", + "externalUrl": "External URL", + "externalUrlPlaceholder": "https://example.com/impressum", + "externalUrlInvalid": "Must be a valid https:// URL", + "externalUrlActive": "External URL is active — internal content is preserved but not shown to visitors." }, "eventTypes": { "title": "Event Types", diff --git a/frontend/src/pages/admin/CMSPage.tsx b/frontend/src/pages/admin/CMSPage.tsx index 40c75fbe..227c7f73 100644 --- a/frontend/src/pages/admin/CMSPage.tsx +++ b/frontend/src/pages/admin/CMSPage.tsx @@ -182,6 +182,31 @@ export const CMSPage: React.FC = () => { setHasUnsavedChanges(true); }; + const handleUseExternalUrlChange = (val: boolean) => { + setEditForm(prev => ({ ...prev, use_external_url: val })); + setHasUnsavedChanges(true); + }; + + const handleExternalUrlChange = (val: string) => { + setEditForm(prev => ({ ...prev, external_url: val })); + setHasUnsavedChanges(true); + }; + + // Inline URL validation. Empty input while typing is not an error + // (avoid flagging mid-edit). Backend re-validates on save regardless. + const externalUrlError = useMemo(() => { + if (!editForm.use_external_url) return null; + const v = (editForm.external_url || '').trim(); + if (!v) return null; + try { + const u = new URL(v); + if (u.protocol !== 'https:') return t('cms.externalUrlInvalid'); + return null; + } catch { + return t('cms.externalUrlInvalid'); + } + }, [editForm.use_external_url, editForm.external_url, t]); + // Per-page logo upload (#324). Only meaningful for the customisable // error pages right now, but harmless if exposed for any slug. const logoInputRef = useRef(null); @@ -607,6 +632,41 @@ export const CMSPage: React.FC = () => {
+ {/* External URL override */} +
+ + {editForm.use_external_url && ( +
+ handleExternalUrlChange(e.target.value)} + placeholder={t('cms.externalUrlPlaceholder')} + error={externalUrlError || undefined} + /> +

+ {t('cms.externalUrlActive')} +

+
+ )} +
+ {/* Title */}