diff --git a/frontend/src/components/gallery/GalleryLayout.tsx b/frontend/src/components/gallery/GalleryLayout.tsx index db3c769e..d034818a 100644 --- a/frontend/src/components/gallery/GalleryLayout.tsx +++ b/frontend/src/components/gallery/GalleryLayout.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { Link } from 'react-router-dom'; +import { useQuery } from '@tanstack/react-query'; import { Calendar, Clock, Download, LogOut } from 'lucide-react'; import { parseISO } from 'date-fns'; import { useTranslation } from 'react-i18next'; @@ -9,6 +10,7 @@ import { DynamicFavicon } from '../common/DynamicFavicon'; import { useTheme } from '../../contexts/ThemeContext'; import { useGuestIdentityOptional } from '../../contexts/GuestIdentityContext'; import { buildResourceUrl } from '../../utils/url'; +import { cmsService, type PublicCMSPage } from '../../services/cms.service'; import type { HeaderStyleType } from '../../types/theme.types'; interface GalleryLayoutProps { @@ -62,6 +64,22 @@ export const GalleryLayout: React.FC = ({ const { theme } = useTheme(); const guestIdentity = useGuestIdentityOptional(); + // Footer legal-link config. Cached aggressively because the toggle state + // changes rarely and the gallery footer renders on every page view. + // Failures fall back to the internal /impressum and /datenschutz routes. + const { data: impressumPage } = useQuery({ + queryKey: ['public-cms', 'impressum'], + queryFn: () => cmsService.getPublicPage('impressum'), + staleTime: 5 * 60 * 1000, + retry: false, + }); + const { data: datenschutzPage } = useQuery({ + queryKey: ['public-cms', 'datenschutz'], + queryFn: () => cmsService.getPublicPage('datenschutz'), + staleTime: 5 * 60 * 1000, + retry: false, + }); + // Determine header style - use prop first (from event data), then theme, then fall back to 'standard' const headerStyle: HeaderStyleType = headerStyleProp || theme.headerStyle || 'standard'; const isHeroHeader = headerStyle === 'hero'; @@ -592,19 +610,41 @@ export const GalleryLayout: React.FC = ({ )} {/* Legal Links */}
- - {t('legal.impressum')} - + {impressumPage?.use_external_url && impressumPage.external_url ? ( + + {t('legal.impressum')} + + ) : ( + + {t('legal.impressum')} + + )} | - - {t('legal.datenschutz')} - + {datenschutzPage?.use_external_url && datenschutzPage.external_url ? ( + + {t('legal.datenschutz')} + + ) : ( + + {t('legal.datenschutz')} + + )} {guestIdentity?.identity && ( <> | diff --git a/frontend/src/pages/public/LegalPage.tsx b/frontend/src/pages/public/LegalPage.tsx index 688cf028..d2b13706 100644 --- a/frontend/src/pages/public/LegalPage.tsx +++ b/frontend/src/pages/public/LegalPage.tsx @@ -44,7 +44,17 @@ export const LegalPage: React.FC = () => { } }, [page?.title]); - if (isLoading) { + // External-URL override: full-page redirect so the visitor lands on the + // operator's own canonical legal page. Use replace() so the back button + // returns to the gallery instead of looping back through the redirect. + const willRedirect = !!(page?.use_external_url && page?.external_url); + useEffect(() => { + if (willRedirect && page?.external_url) { + window.location.replace(page.external_url); + } + }, [willRedirect, page?.external_url]); + + if (isLoading || willRedirect) { return (