diff --git a/frontend/src/components/admin/GalleryPreview.tsx b/frontend/src/components/admin/GalleryPreview.tsx new file mode 100644 index 0000000..538e87d --- /dev/null +++ b/frontend/src/components/admin/GalleryPreview.tsx @@ -0,0 +1,184 @@ +import React, { useMemo } from 'react'; +import { Card } from '../common'; +import { Camera } from 'lucide-react'; +import { ThemeConfig, GalleryLayoutType } from '../../types/theme.types'; + +interface GalleryPreviewProps { + theme: ThemeConfig; + layoutType?: GalleryLayoutType; + className?: string; +} + +// Mock photo data for preview +const generateMockPhotos = (count: number) => { + return Array.from({ length: count }, (_, i) => ({ + id: i + 1, + filename: `photo-${i + 1}.jpg`, + url: '', + thumbnail_url: '', + type: i % 3 === 0 ? 'collage' : 'individual', + category_id: (i % 4) + 1, + category_name: ['Ceremony', 'Reception', 'Portraits', 'Party'][i % 4], + category_slug: ['ceremony', 'reception', 'portraits', 'party'][i % 4], + size: Math.floor(Math.random() * 5000000) + 1000000, + uploaded_at: new Date().toISOString(), + })); +}; + +// Preview photo component +const PreviewPhoto: React.FC<{ + photo: any; + className?: string; + aspectRatio?: string; +}> = ({ + photo, + className = '', + aspectRatio = 'aspect-square' +}) => ( +
+
+ +
+
+

{photo.filename}

+ {photo.category_name && ( +

{photo.category_name}

+ )} +
+ {photo.type === 'collage' && ( +
+ + Collage + +
+ )} +
+); + +export const GalleryPreview: React.FC = ({ + theme, + layoutType, + className = '' +}) => { + const mockPhotos = useMemo(() => generateMockPhotos(12), []); + + // Use the provided layoutType or fallback to theme's gallery layout + const activeLayout = layoutType || theme.galleryLayout || 'grid'; + + const renderLayout = () => { + const spacing = theme.gallerySettings?.spacing || 'normal'; + const gapClass = spacing === 'tight' ? 'gap-1' : spacing === 'relaxed' ? 'gap-4' : 'gap-2'; + + switch (activeLayout) { + case 'grid': { + const cols = theme.gallerySettings?.gridColumns || { mobile: 2, tablet: 3, desktop: 4 }; + return ( +
+ {mockPhotos.slice(0, 8).map((photo) => ( + + ))} +
+ ); + } + + case 'masonry': + return ( +
+ {mockPhotos.slice(0, 10).map((photo, idx) => ( +
+ +
+ ))} +
+ ); + + case 'carousel': + return ( +
+
+ +
+
+ {[0, 1, 2, 3].map((idx) => ( +
+ ))} +
+
+ ); + + case 'timeline': + return ( +
+ {['Today', 'Yesterday'].map((date, dateIdx) => ( +
+

{date}

+
+ {mockPhotos.slice(dateIdx * 3, (dateIdx * 3) + 3).map((photo) => ( + + ))} +
+
+ ))} +
+ ); + + case 'hero': + return ( +
+ +
+ {mockPhotos.slice(1, 5).map((photo) => ( + + ))} +
+
+ ); + + case 'mosaic': + return ( +
+ + + + + + +
+ ); + + default: + return null; + } + }; + + return ( +
+ {/* Preview Header */} +
+

{activeLayout} Layout

+
+ + {/* Preview Content */} +
+ {renderLayout()} +
+
+ ); +}; + +GalleryPreview.displayName = 'GalleryPreview'; \ No newline at end of file diff --git a/frontend/src/components/admin/ThemeEditorModal.tsx b/frontend/src/components/admin/ThemeEditorModal.tsx index 6cd2fb0..85bfe7e 100644 --- a/frontend/src/components/admin/ThemeEditorModal.tsx +++ b/frontend/src/components/admin/ThemeEditorModal.tsx @@ -1,8 +1,9 @@ import React, { useState, useEffect } from 'react'; -import { X, Save, RotateCcw } from 'lucide-react'; +import { X, Save, RotateCcw, Grid3X3, Layers, Play, Clock, Image, LayoutGrid, Check } from 'lucide-react'; import { Button } from '../common'; import { ThemeCustomizerEnhanced } from './ThemeCustomizerEnhanced'; -import { ThemeConfig, GALLERY_THEME_PRESETS } from '../../types/theme.types'; +import { GalleryPreview } from './GalleryPreview'; +import { ThemeConfig, GALLERY_THEME_PRESETS, GalleryLayoutType } from '../../types/theme.types'; import { useTranslation } from 'react-i18next'; interface ThemeEditorModalProps { @@ -13,6 +14,15 @@ interface ThemeEditorModalProps { eventName: string; } +const layoutIcons: Record = { + grid: , + masonry: , + carousel: , + timeline: , + hero: , + mosaic: +}; + export const ThemeEditorModal: React.FC = ({ isOpen, onClose, @@ -23,6 +33,7 @@ export const ThemeEditorModal: React.FC = ({ const { t } = useTranslation(); const [theme, setTheme] = useState(GALLERY_THEME_PRESETS.default.config); const [presetName, setPresetName] = useState('default'); + const [previewLayout, setPreviewLayout] = useState(undefined); useEffect(() => { if (currentTheme) { @@ -105,16 +116,68 @@ export const ThemeEditorModal: React.FC = ({
{/* Content */} -
- +
+
+ {/* Left side - Theme Customizer */} +
+ +
+ + {/* Right side - Gallery Preview */} +
+
+ {/* Grid Style Selector */} +
+

+ {t('branding.previewLayout')} +

+
+ {(Object.keys(layoutIcons) as GalleryLayoutType[]).map((layout) => ( + + ))} +
+
+ + {/* Gallery Preview */} +
+

+ {t('branding.livePreview')} +

+ +
+
+
+
{/* Footer */} diff --git a/frontend/src/components/admin/index.ts b/frontend/src/components/admin/index.ts index a51f9cb..0834b32 100644 --- a/frontend/src/components/admin/index.ts +++ b/frontend/src/components/admin/index.ts @@ -20,4 +20,5 @@ export { ThemeCustomizerEnhanced } from './ThemeCustomizerEnhanced'; export { ThemeDisplay } from './ThemeDisplay'; export { ThemeEditorModal } from './ThemeEditorModal'; export { HeroPhotoSelector } from './HeroPhotoSelector'; -export { PhotoUploadModal } from './PhotoUploadModal'; \ No newline at end of file +export { PhotoUploadModal } from './PhotoUploadModal'; +export { GalleryPreview } from './GalleryPreview'; \ No newline at end of file diff --git a/frontend/src/i18n/locales/de.json b/frontend/src/i18n/locales/de.json index bf8da7a..26f76e2 100644 --- a/frontend/src/i18n/locales/de.json +++ b/frontend/src/i18n/locales/de.json @@ -375,6 +375,10 @@ "language": "Sprache", "defaultLanguage": "Standardsprache", "defaultLanguageHelp": "Sprache, die Gästen vor der Anmeldung angezeigt wird", + "defaultWelcomeMessage": "Standard-Begrüßungsnachricht", + "welcomeMessage": "Begrüßungsnachricht", + "welcomeMessagePlaceholder": "Geben Sie eine Standard-Begrüßungsnachricht ein, die in E-Mails zur Galerieerstellung enthalten sein wird", + "welcomeMessageHelp": "Diese Nachricht wird in allen E-Mails zur Galerieerstellung enthalten sein, sofern sie beim Erstellen einer Veranstaltung nicht überschrieben wird", "saveSettings": "Allgemeine Einstellungen speichern", "saveGeneralSettings": "Allgemeine Einstellungen speichern", "dateTimeFormat": "Datums- & Zeitformat", @@ -579,7 +583,9 @@ "applyTheme": "Theme anwenden", "customTheme": "Benutzerdefiniertes Design", "customizeTheme": "Design anpassen", - "saveTheme": "Design speichern" + "saveTheme": "Design speichern", + "previewLayout": "Vorschau-Layout", + "livePreview": "Live-Vorschau" }, "admin": { "title": "Admin-Panel", diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index ee64daf..466388b 100644 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -396,6 +396,10 @@ "language": "Language", "defaultLanguage": "Default Language", "defaultLanguageHelp": "Language shown to guests before login", + "defaultWelcomeMessage": "Default Welcome Message", + "welcomeMessage": "Welcome Message", + "welcomeMessagePlaceholder": "Enter a default welcome message that will be included in gallery creation emails", + "welcomeMessageHelp": "This message will be included in all gallery creation emails unless overridden when creating an event", "saveSettings": "Save General Settings", "saveGeneralSettings": "Save General Settings", "dateTimeFormat": "Date & Time Format", @@ -634,7 +638,9 @@ "applyTheme": "Apply Theme", "customTheme": "Custom Theme", "customizeTheme": "Customize Theme", - "saveTheme": "Save Theme" + "saveTheme": "Save Theme", + "previewLayout": "Preview Layout", + "livePreview": "Live Preview" }, "admin": { "title": "Admin Panel", diff --git a/frontend/src/pages/admin/BrandingPage.tsx b/frontend/src/pages/admin/BrandingPage.tsx index a8e1194..5418a8c 100644 --- a/frontend/src/pages/admin/BrandingPage.tsx +++ b/frontend/src/pages/admin/BrandingPage.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef } from 'react'; import { Save, Eye, Palette, Upload } from 'lucide-react'; import { toast } from 'react-toastify'; import { Button, Card, Input, ErrorBoundary, Loading } from '../../components/common'; -import { ThemeCustomizer } from '../../components/admin/ThemeCustomizer'; +import { ThemeCustomizerEnhanced, GalleryPreview } from '../../components/admin'; import { useTheme, type ThemeConfig, GALLERY_THEME_PRESETS } from '../../contexts/ThemeContext'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { settingsService, type BrandingSettings } from '../../services/settings.service'; @@ -472,12 +472,33 @@ export const BrandingPage: React.FC = () => { {t('branding.applyLivePreview')}
- +
+ {/* Left side - Theme Customizer */} +
+ +
+ + {/* Right side - Gallery Preview */} +
+ +

+ {t('branding.livePreview')} +

+ +
+
+
{/* Event-Specific Themes Info */} diff --git a/frontend/src/pages/admin/CreateEventPageEnhanced.tsx b/frontend/src/pages/admin/CreateEventPageEnhanced.tsx index 99a1777..463538a 100644 --- a/frontend/src/pages/admin/CreateEventPageEnhanced.tsx +++ b/frontend/src/pages/admin/CreateEventPageEnhanced.tsx @@ -15,7 +15,7 @@ import { enUS, de } from 'date-fns/locale'; import { toast } from 'react-toastify'; import { Button, Input, Card } from '../../components/common'; -import { ThemeCustomizerEnhanced } from '../../components/admin/ThemeCustomizerEnhanced'; +import { ThemeCustomizerEnhanced, GalleryPreview } from '../../components/admin'; import { useMutation, useQuery } from '@tanstack/react-query'; import { eventsService } from '../../services/events.service'; import { categoriesService } from '../../services/categories.service'; @@ -375,14 +375,28 @@ export const CreateEventPageEnhanced: React.FC = () => { {/* Theme Customizer */} {showThemeCustomizer && ( - +
+
+ {/* Theme Customizer */} + + + {/* Gallery Preview */} +
+ +
+
+
)}