feat: implement gallery preview with layout selector
- Add GalleryPreview component that shows simplified gallery layouts - Update ThemeEditorModal with split view: theme customizer on left, preview on right - Add grid style selector above preview to switch between layouts - Update BrandingPage to show live preview alongside theme customizer - Add preview to CreateEventPageEnhanced when customizing themes - Support all 6 gallery layouts: grid, masonry, carousel, timeline, hero, mosaic - Add translation keys for preview layout and live preview The preview accurately reflects different grid layouts and theme settings, helping users visualize how their galleries will look before saving. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -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'
|
||||||
|
}) => (
|
||||||
|
<div className={`relative overflow-hidden rounded-lg bg-gradient-to-br from-neutral-200 to-neutral-300 ${aspectRatio} ${className}`}>
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
|
<Camera className="w-8 h-8 text-neutral-400" />
|
||||||
|
</div>
|
||||||
|
<div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/60 to-transparent p-2">
|
||||||
|
<p className="text-white text-xs truncate">{photo.filename}</p>
|
||||||
|
{photo.category_name && (
|
||||||
|
<p className="text-white/70 text-[10px]">{photo.category_name}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{photo.type === 'collage' && (
|
||||||
|
<div className="absolute top-1 right-1">
|
||||||
|
<span className="px-1.5 py-0.5 bg-black/60 text-white text-[10px] rounded">
|
||||||
|
Collage
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const GalleryPreview: React.FC<GalleryPreviewProps> = ({
|
||||||
|
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 (
|
||||||
|
<div className={`grid grid-cols-3 md:grid-cols-4 ${gapClass}`}>
|
||||||
|
{mockPhotos.slice(0, 8).map((photo) => (
|
||||||
|
<PreviewPhoto key={photo.id} photo={photo} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'masonry':
|
||||||
|
return (
|
||||||
|
<div className={`columns-3 md:columns-4 ${gapClass}`}>
|
||||||
|
{mockPhotos.slice(0, 10).map((photo, idx) => (
|
||||||
|
<div key={photo.id} className={`break-inside-avoid mb-${spacing === 'tight' ? '1' : spacing === 'relaxed' ? '4' : '2'}`}>
|
||||||
|
<PreviewPhoto
|
||||||
|
photo={photo}
|
||||||
|
aspectRatio={idx % 3 === 0 ? 'aspect-[4/5]' : idx % 3 === 1 ? 'aspect-[4/3]' : 'aspect-square'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'carousel':
|
||||||
|
return (
|
||||||
|
<div className="relative">
|
||||||
|
<div className="flex items-center gap-2 overflow-hidden">
|
||||||
|
<PreviewPhoto photo={mockPhotos[0]} className="w-full max-w-md mx-auto" aspectRatio="aspect-[4/3]" />
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-center gap-1 mt-3">
|
||||||
|
{[0, 1, 2, 3].map((idx) => (
|
||||||
|
<div key={idx} className={`w-2 h-2 rounded-full ${idx === 0 ? 'bg-primary-600' : 'bg-neutral-300'}`} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'timeline':
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
{['Today', 'Yesterday'].map((date, dateIdx) => (
|
||||||
|
<div key={date}>
|
||||||
|
<h4 className="text-sm font-medium text-neutral-700 mb-2">{date}</h4>
|
||||||
|
<div className={`grid grid-cols-3 ${gapClass}`}>
|
||||||
|
{mockPhotos.slice(dateIdx * 3, (dateIdx * 3) + 3).map((photo) => (
|
||||||
|
<PreviewPhoto key={photo.id} photo={photo} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'hero':
|
||||||
|
return (
|
||||||
|
<div className="space-y-3">
|
||||||
|
<PreviewPhoto photo={mockPhotos[0]} aspectRatio="aspect-[16/9]" className="w-full" />
|
||||||
|
<div className={`grid grid-cols-4 ${gapClass}`}>
|
||||||
|
{mockPhotos.slice(1, 5).map((photo) => (
|
||||||
|
<PreviewPhoto key={photo.id} photo={photo} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'mosaic':
|
||||||
|
return (
|
||||||
|
<div className={`grid grid-cols-4 grid-rows-3 ${gapClass} h-64`}>
|
||||||
|
<PreviewPhoto photo={mockPhotos[0]} className="col-span-2 row-span-2" aspectRatio="aspect-auto h-full" />
|
||||||
|
<PreviewPhoto photo={mockPhotos[1]} className="col-span-1 row-span-1" aspectRatio="aspect-auto h-full" />
|
||||||
|
<PreviewPhoto photo={mockPhotos[2]} className="col-span-1 row-span-1" aspectRatio="aspect-auto h-full" />
|
||||||
|
<PreviewPhoto photo={mockPhotos[3]} className="col-span-1 row-span-1" aspectRatio="aspect-auto h-full" />
|
||||||
|
<PreviewPhoto photo={mockPhotos[4]} className="col-span-1 row-span-1" aspectRatio="aspect-auto h-full" />
|
||||||
|
<PreviewPhoto photo={mockPhotos[5]} className="col-span-2 row-span-1" aspectRatio="aspect-auto h-full" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`bg-white rounded-lg shadow-sm overflow-hidden ${className}`}
|
||||||
|
style={{
|
||||||
|
backgroundColor: theme.backgroundColor || '#ffffff',
|
||||||
|
color: theme.textColor || '#171717',
|
||||||
|
fontFamily: theme.fontFamily || 'Inter, sans-serif',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Preview Header */}
|
||||||
|
<div
|
||||||
|
className="px-4 py-3 border-b"
|
||||||
|
style={{
|
||||||
|
borderColor: theme.primaryColor ? `${theme.primaryColor}20` : '#e5e7eb',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h3 className="text-sm font-medium capitalize">{activeLayout} Layout</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Preview Content */}
|
||||||
|
<div className="p-4" style={{ maxHeight: '400px', overflowY: 'auto' }}>
|
||||||
|
{renderLayout()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
GalleryPreview.displayName = 'GalleryPreview';
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
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 { Button } from '../common';
|
||||||
import { ThemeCustomizerEnhanced } from './ThemeCustomizerEnhanced';
|
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';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
interface ThemeEditorModalProps {
|
interface ThemeEditorModalProps {
|
||||||
@@ -13,6 +14,15 @@ interface ThemeEditorModalProps {
|
|||||||
eventName: string;
|
eventName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const layoutIcons: Record<GalleryLayoutType, React.ReactNode> = {
|
||||||
|
grid: <Grid3X3 className="w-4 h-4" />,
|
||||||
|
masonry: <Layers className="w-4 h-4" />,
|
||||||
|
carousel: <Play className="w-4 h-4" />,
|
||||||
|
timeline: <Clock className="w-4 h-4" />,
|
||||||
|
hero: <Image className="w-4 h-4" />,
|
||||||
|
mosaic: <LayoutGrid className="w-4 h-4" />
|
||||||
|
};
|
||||||
|
|
||||||
export const ThemeEditorModal: React.FC<ThemeEditorModalProps> = ({
|
export const ThemeEditorModal: React.FC<ThemeEditorModalProps> = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
onClose,
|
onClose,
|
||||||
@@ -23,6 +33,7 @@ export const ThemeEditorModal: React.FC<ThemeEditorModalProps> = ({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [theme, setTheme] = useState<ThemeConfig>(GALLERY_THEME_PRESETS.default.config);
|
const [theme, setTheme] = useState<ThemeConfig>(GALLERY_THEME_PRESETS.default.config);
|
||||||
const [presetName, setPresetName] = useState<string>('default');
|
const [presetName, setPresetName] = useState<string>('default');
|
||||||
|
const [previewLayout, setPreviewLayout] = useState<GalleryLayoutType | undefined>(undefined);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentTheme) {
|
if (currentTheme) {
|
||||||
@@ -105,16 +116,68 @@ export const ThemeEditorModal: React.FC<ThemeEditorModalProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div className="flex-1 overflow-y-auto p-6">
|
<div className="flex-1 overflow-y-auto">
|
||||||
<ThemeCustomizerEnhanced
|
<div className="grid grid-cols-1 lg:grid-cols-2 h-full">
|
||||||
value={theme}
|
{/* Left side - Theme Customizer */}
|
||||||
onChange={handleThemeChange}
|
<div className="p-6 overflow-y-auto border-r border-neutral-200">
|
||||||
presetName={presetName}
|
<ThemeCustomizerEnhanced
|
||||||
onPresetChange={handlePresetChange}
|
value={theme}
|
||||||
isPreviewMode={true}
|
onChange={handleThemeChange}
|
||||||
showGalleryLayouts={true}
|
presetName={presetName}
|
||||||
hideActions={true}
|
onPresetChange={handlePresetChange}
|
||||||
/>
|
isPreviewMode={true}
|
||||||
|
showGalleryLayouts={true}
|
||||||
|
hideActions={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right side - Gallery Preview */}
|
||||||
|
<div className="p-6 bg-neutral-50 overflow-y-auto">
|
||||||
|
<div className="space-y-4">
|
||||||
|
{/* Grid Style Selector */}
|
||||||
|
<div>
|
||||||
|
<h3 className="text-sm font-medium text-neutral-700 mb-3">
|
||||||
|
{t('branding.previewLayout')}
|
||||||
|
</h3>
|
||||||
|
<div className="grid grid-cols-3 gap-2">
|
||||||
|
{(Object.keys(layoutIcons) as GalleryLayoutType[]).map((layout) => (
|
||||||
|
<button
|
||||||
|
key={layout}
|
||||||
|
onClick={() => setPreviewLayout(layout)}
|
||||||
|
className={`relative p-3 rounded-lg border-2 transition-all ${
|
||||||
|
(previewLayout || theme.galleryLayout || 'grid') === layout
|
||||||
|
? 'border-primary-600 bg-primary-50'
|
||||||
|
: 'border-neutral-200 hover:border-neutral-300 bg-white'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col items-center gap-1">
|
||||||
|
<div className="text-neutral-700">
|
||||||
|
{layoutIcons[layout]}
|
||||||
|
</div>
|
||||||
|
<span className="text-xs capitalize">{layout}</span>
|
||||||
|
</div>
|
||||||
|
{(previewLayout || theme.galleryLayout || 'grid') === layout && (
|
||||||
|
<Check className="absolute top-1 right-1 w-3 h-3 text-primary-600" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Gallery Preview */}
|
||||||
|
<div>
|
||||||
|
<h3 className="text-sm font-medium text-neutral-700 mb-3">
|
||||||
|
{t('branding.livePreview')}
|
||||||
|
</h3>
|
||||||
|
<GalleryPreview
|
||||||
|
theme={theme}
|
||||||
|
layoutType={previewLayout}
|
||||||
|
className="shadow-lg"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
|
|||||||
@@ -20,4 +20,5 @@ export { ThemeCustomizerEnhanced } from './ThemeCustomizerEnhanced';
|
|||||||
export { ThemeDisplay } from './ThemeDisplay';
|
export { ThemeDisplay } from './ThemeDisplay';
|
||||||
export { ThemeEditorModal } from './ThemeEditorModal';
|
export { ThemeEditorModal } from './ThemeEditorModal';
|
||||||
export { HeroPhotoSelector } from './HeroPhotoSelector';
|
export { HeroPhotoSelector } from './HeroPhotoSelector';
|
||||||
export { PhotoUploadModal } from './PhotoUploadModal';
|
export { PhotoUploadModal } from './PhotoUploadModal';
|
||||||
|
export { GalleryPreview } from './GalleryPreview';
|
||||||
@@ -375,6 +375,10 @@
|
|||||||
"language": "Sprache",
|
"language": "Sprache",
|
||||||
"defaultLanguage": "Standardsprache",
|
"defaultLanguage": "Standardsprache",
|
||||||
"defaultLanguageHelp": "Sprache, die Gästen vor der Anmeldung angezeigt wird",
|
"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",
|
"saveSettings": "Allgemeine Einstellungen speichern",
|
||||||
"saveGeneralSettings": "Allgemeine Einstellungen speichern",
|
"saveGeneralSettings": "Allgemeine Einstellungen speichern",
|
||||||
"dateTimeFormat": "Datums- & Zeitformat",
|
"dateTimeFormat": "Datums- & Zeitformat",
|
||||||
@@ -579,7 +583,9 @@
|
|||||||
"applyTheme": "Theme anwenden",
|
"applyTheme": "Theme anwenden",
|
||||||
"customTheme": "Benutzerdefiniertes Design",
|
"customTheme": "Benutzerdefiniertes Design",
|
||||||
"customizeTheme": "Design anpassen",
|
"customizeTheme": "Design anpassen",
|
||||||
"saveTheme": "Design speichern"
|
"saveTheme": "Design speichern",
|
||||||
|
"previewLayout": "Vorschau-Layout",
|
||||||
|
"livePreview": "Live-Vorschau"
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
"title": "Admin-Panel",
|
"title": "Admin-Panel",
|
||||||
|
|||||||
@@ -396,6 +396,10 @@
|
|||||||
"language": "Language",
|
"language": "Language",
|
||||||
"defaultLanguage": "Default Language",
|
"defaultLanguage": "Default Language",
|
||||||
"defaultLanguageHelp": "Language shown to guests before login",
|
"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",
|
"saveSettings": "Save General Settings",
|
||||||
"saveGeneralSettings": "Save General Settings",
|
"saveGeneralSettings": "Save General Settings",
|
||||||
"dateTimeFormat": "Date & Time Format",
|
"dateTimeFormat": "Date & Time Format",
|
||||||
@@ -634,7 +638,9 @@
|
|||||||
"applyTheme": "Apply Theme",
|
"applyTheme": "Apply Theme",
|
||||||
"customTheme": "Custom Theme",
|
"customTheme": "Custom Theme",
|
||||||
"customizeTheme": "Customize Theme",
|
"customizeTheme": "Customize Theme",
|
||||||
"saveTheme": "Save Theme"
|
"saveTheme": "Save Theme",
|
||||||
|
"previewLayout": "Preview Layout",
|
||||||
|
"livePreview": "Live Preview"
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
"title": "Admin Panel",
|
"title": "Admin Panel",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef } from 'react';
|
|||||||
import { Save, Eye, Palette, Upload } from 'lucide-react';
|
import { Save, Eye, Palette, Upload } from 'lucide-react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { Button, Card, Input, ErrorBoundary, Loading } from '../../components/common';
|
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 { useTheme, type ThemeConfig, GALLERY_THEME_PRESETS } from '../../contexts/ThemeContext';
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { settingsService, type BrandingSettings } from '../../services/settings.service';
|
import { settingsService, type BrandingSettings } from '../../services/settings.service';
|
||||||
@@ -472,12 +472,33 @@ export const BrandingPage: React.FC = () => {
|
|||||||
<span className="text-sm text-neutral-700">{t('branding.applyLivePreview')}</span>
|
<span className="text-sm text-neutral-700">{t('branding.applyLivePreview')}</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<ThemeCustomizer
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
value={currentTheme}
|
{/* Left side - Theme Customizer */}
|
||||||
onChange={handleThemeChange}
|
<div>
|
||||||
presetName={currentThemeName}
|
<ThemeCustomizerEnhanced
|
||||||
onPresetChange={handlePresetChange}
|
value={currentTheme}
|
||||||
/>
|
onChange={handleThemeChange}
|
||||||
|
presetName={currentThemeName}
|
||||||
|
onPresetChange={handlePresetChange}
|
||||||
|
isPreviewMode={isPreviewMode}
|
||||||
|
showGalleryLayouts={true}
|
||||||
|
hideActions={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right side - Gallery Preview */}
|
||||||
|
<div className="lg:sticky lg:top-4 lg:h-fit">
|
||||||
|
<Card className="p-4">
|
||||||
|
<h3 className="text-sm font-medium text-neutral-700 mb-3">
|
||||||
|
{t('branding.livePreview')}
|
||||||
|
</h3>
|
||||||
|
<GalleryPreview
|
||||||
|
theme={currentTheme}
|
||||||
|
className="shadow-lg"
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Event-Specific Themes Info */}
|
{/* Event-Specific Themes Info */}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { enUS, de } from 'date-fns/locale';
|
|||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import { Button, Input, Card } from '../../components/common';
|
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 { useMutation, useQuery } from '@tanstack/react-query';
|
||||||
import { eventsService } from '../../services/events.service';
|
import { eventsService } from '../../services/events.service';
|
||||||
import { categoriesService } from '../../services/categories.service';
|
import { categoriesService } from '../../services/categories.service';
|
||||||
@@ -375,14 +375,28 @@ export const CreateEventPageEnhanced: React.FC = () => {
|
|||||||
|
|
||||||
{/* Theme Customizer */}
|
{/* Theme Customizer */}
|
||||||
{showThemeCustomizer && (
|
{showThemeCustomizer && (
|
||||||
<ThemeCustomizerEnhanced
|
<div className="space-y-6">
|
||||||
value={formData.theme_config}
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
onChange={handleThemeChange}
|
{/* Theme Customizer */}
|
||||||
presetName={formData.theme_preset}
|
<ThemeCustomizerEnhanced
|
||||||
onPresetChange={handlePresetChange}
|
value={formData.theme_config}
|
||||||
isPreviewMode={false}
|
onChange={handleThemeChange}
|
||||||
showGalleryLayouts={true}
|
presetName={formData.theme_preset}
|
||||||
/>
|
onPresetChange={handlePresetChange}
|
||||||
|
isPreviewMode={false}
|
||||||
|
showGalleryLayouts={true}
|
||||||
|
hideActions={true}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Gallery Preview */}
|
||||||
|
<div className="lg:sticky lg:top-4 lg:h-fit">
|
||||||
|
<GalleryPreview
|
||||||
|
theme={formData.theme_config}
|
||||||
|
className="shadow-lg"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
Reference in New Issue
Block a user