fix: resolve external media dimensions, gallery theme race condition, and add email color customization
- Fix dimension repair for external media by using photoResolver instead of hardcoded paths - Extract photo dimensions via Sharp during external media import - Remove duplicate theme useEffect from GalleryView to prevent flash/revert race condition - Pass event welcome_message to Story layout footer for per-event customization - Add email_primary_color/email_secondary_color settings with admin UI color pickers - Add i18n keys for email branding in all 4 locales (en, de, ru, pt)
This commit is contained in:
@@ -16,7 +16,6 @@ import { UserPhotoUpload } from './UserPhotoUpload';
|
||||
import type { FilterType } from './GalleryFilter';
|
||||
import { analyticsService } from '../../services/analytics.service';
|
||||
import { useDevToolsProtection } from '../../hooks/useDevToolsProtection';
|
||||
import { GALLERY_THEME_PRESETS } from '../../types/theme.types';
|
||||
import { api } from '../../config/api';
|
||||
import { Upload, Menu } from 'lucide-react';
|
||||
import { galleryService } from '../../services/gallery.service';
|
||||
@@ -44,7 +43,7 @@ interface GalleryViewProps {
|
||||
export const GalleryView: React.FC<GalleryViewProps> = ({ slug, event }) => {
|
||||
const { t } = useTranslation();
|
||||
const { logout } = useGalleryAuth();
|
||||
const { setTheme, theme } = useTheme();
|
||||
const { theme } = useTheme();
|
||||
const [selectedCategoryId, setSelectedCategoryId] = useState<number | string | null>(null);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [sortBy, setSortBy] = useState<'date' | 'name' | 'size' | 'rating' | 'capture_date'>('date');
|
||||
@@ -275,62 +274,6 @@ export const GalleryView: React.FC<GalleryViewProps> = ({ slug, event }) => {
|
||||
setStaticHeroPhoto(defaultHeroPhoto);
|
||||
}, [selectedCategoryId, data?.categories, data?.photos, defaultHeroPhoto]);
|
||||
|
||||
// Apply theme when settings are loaded
|
||||
useEffect(() => {
|
||||
if (settingsData && data?.event) {
|
||||
let themeToApply = null;
|
||||
const fullEvent = data.event; // Use the full event data from API
|
||||
|
||||
if (fullEvent.color_theme) {
|
||||
try {
|
||||
// Check if it's a valid JSON string
|
||||
if (fullEvent.color_theme.startsWith('{')) {
|
||||
const eventTheme = JSON.parse(fullEvent.color_theme);
|
||||
themeToApply = eventTheme;
|
||||
} else {
|
||||
// Handle legacy theme names - check if it's a preset
|
||||
const preset = GALLERY_THEME_PRESETS[fullEvent.color_theme];
|
||||
if (preset) {
|
||||
themeToApply = preset.config;
|
||||
} else {
|
||||
// Unknown theme name, fall back to global theme
|
||||
if (settingsData.theme_config) {
|
||||
themeToApply = settingsData.theme_config;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Invalid theme format - use default
|
||||
// Fall back to global theme
|
||||
if (settingsData.theme_config) {
|
||||
themeToApply = settingsData.theme_config;
|
||||
}
|
||||
}
|
||||
} else if (settingsData.theme_config) {
|
||||
// No event theme, use global theme
|
||||
themeToApply = settingsData.theme_config;
|
||||
}
|
||||
|
||||
// Apply theme with a small delay to ensure it overrides any global theme
|
||||
if (themeToApply) {
|
||||
// Use setTimeout to ensure this runs after any global theme application
|
||||
const timer = setTimeout(() => {
|
||||
// If there's a hero photo, add it to gallery settings
|
||||
if (fullEvent.hero_photo_id && themeToApply.gallerySettings) {
|
||||
themeToApply.gallerySettings.heroImageId = fullEvent.hero_photo_id;
|
||||
// Apply hero photo ID to existing gallery settings
|
||||
} else if (fullEvent.hero_photo_id) {
|
||||
themeToApply.gallerySettings = { heroImageId: fullEvent.hero_photo_id };
|
||||
// Create gallery settings with hero photo ID
|
||||
}
|
||||
setTheme(themeToApply);
|
||||
}, 0);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
}, [settingsData, data, setTheme]); // Use data instead of event prop
|
||||
|
||||
// Calculate days until expiration (null means never expires)
|
||||
const daysUntilExpiration = event.expires_at
|
||||
? differenceInDays(parseISO(event.expires_at), new Date())
|
||||
@@ -619,6 +562,7 @@ export const GalleryView: React.FC<GalleryViewProps> = ({ slug, event }) => {
|
||||
headerStyle={data?.event?.header_style || theme.headerStyle}
|
||||
heroDividerStyle={data?.event?.hero_divider_style || theme.heroDividerStyle || 'wave'}
|
||||
heroImageAnchor={data?.event?.hero_image_anchor || 'center'}
|
||||
welcomeMessage={event.welcome_message}
|
||||
onLogout={logout}
|
||||
/>
|
||||
|
||||
@@ -794,6 +738,7 @@ export const GalleryView: React.FC<GalleryViewProps> = ({ slug, event }) => {
|
||||
headerStyle={data?.event?.header_style || theme.headerStyle}
|
||||
heroDividerStyle={data?.event?.hero_divider_style || theme.heroDividerStyle || 'wave'}
|
||||
heroImageAnchor={data?.event?.hero_image_anchor || 'center'}
|
||||
welcomeMessage={event.welcome_message}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -64,6 +64,8 @@ interface PhotoGridWithLayoutsProps {
|
||||
heroDividerStyle?: HeroDividerStyle;
|
||||
// Hero image anchor position (#162) – keyword or "X% Y%" focal point
|
||||
heroImageAnchor?: string;
|
||||
// Welcome message (per-event) for layouts that display it
|
||||
welcomeMessage?: string;
|
||||
// Logout callback for full-page layouts
|
||||
onLogout?: () => void;
|
||||
}
|
||||
@@ -97,6 +99,7 @@ export const PhotoGridWithLayouts: React.FC<PhotoGridWithLayoutsProps> = ({
|
||||
headerStyle,
|
||||
heroDividerStyle = 'wave',
|
||||
heroImageAnchor = 'center',
|
||||
welcomeMessage,
|
||||
onLogout
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -226,6 +229,7 @@ export const PhotoGridWithLayouts: React.FC<PhotoGridWithLayoutsProps> = ({
|
||||
heroLogoVisible,
|
||||
heroLogoSize,
|
||||
heroLogoPosition,
|
||||
welcomeMessage,
|
||||
onLogout,
|
||||
};
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ interface CategoryScene {
|
||||
|
||||
interface GalleryStoryLayoutProps extends BaseGalleryLayoutProps {
|
||||
heroPhotoOverride?: Photo | null;
|
||||
welcomeMessage?: string;
|
||||
}
|
||||
|
||||
export const GalleryStoryLayout: React.FC<GalleryStoryLayoutProps> = ({
|
||||
@@ -55,6 +56,7 @@ export const GalleryStoryLayout: React.FC<GalleryStoryLayoutProps> = ({
|
||||
feedbackEnabled = false,
|
||||
feedbackOptions,
|
||||
heroPhotoOverride,
|
||||
welcomeMessage,
|
||||
onLogout
|
||||
}) => {
|
||||
// These props are passed by parent but we use our own feedback system, so mark as intentionally unused
|
||||
@@ -348,7 +350,7 @@ export const GalleryStoryLayout: React.FC<GalleryStoryLayoutProps> = ({
|
||||
<footer className="story-footer">
|
||||
<h2 className="story-footer-title">{t('gallery.thankYou', 'Thank You')}</h2>
|
||||
<p className="story-footer-text">
|
||||
{t('gallery.thankYouMessage', 'For being part of our story and making our special day unforgettable.')}
|
||||
{welcomeMessage || t('gallery.thankYouMessage', 'For being part of our story and making our special day unforgettable.')}
|
||||
</p>
|
||||
{allowDownloads && (
|
||||
<button className="story-footer-btn" onClick={handleDownloadAll}>
|
||||
|
||||
Reference in New Issue
Block a user