fix: theme save without Live Preview, Branding default on new events, gallery loading flicker (#323, #321)
#323-A — Branding colour changes weren't persisting unless "Apply changes immediately (Live Preview)" was checked. ThemeCustomizerEnhanced was gating its `onChange` callback on `isPreviewMode`, but the parent BrandingPage already gates global `setTheme()` on its own copy of that flag — so the customizer's gate was double-gating and silently dropped the new values from the parent state that Save reads from. Always propagate `onChange`; let parents decide what's "live". Removed the now no-op `isPreviewMode` prop and dropped the unused passers. #323-B — Default theme set in Branding wasn't applied to new events. CreateEventPage only inherited the event-type's recommended preset, with 'default' falling back to Classic Grid. Now reads `settings.theme_config` on first load and uses it as the form's starting theme; the event-type effect skips the generic 'default' so the Branding default sticks for event types like "Other". #321 — Visitors saw four sequential render states when opening a gallery (full-page "Loading Gallery" → "publicly accessible — loading photos" card → skeleton grid → real gallery). Extracted the skeleton into a shared <GallerySkeleton/> and used it for both GalleryView's photos- loading state and GalleryPage's gallery-info-loading + public-auto-login phases. The "publicly accessible" interstitial is gone. Net: one continuous skeleton from URL open until real photos render.
This commit is contained in:
@@ -12,7 +12,6 @@ interface ThemeCustomizerEnhancedProps {
|
||||
onChange: (theme: ThemeConfig) => void;
|
||||
presetName?: string;
|
||||
onPresetChange?: (presetName: string) => void;
|
||||
isPreviewMode?: boolean;
|
||||
showGalleryLayouts?: boolean;
|
||||
hideActions?: boolean;
|
||||
onApply?: (theme: ThemeConfig, metadata: { presetName: string }) => Promise<void> | void;
|
||||
@@ -76,7 +75,6 @@ export const ThemeCustomizerEnhanced: React.FC<ThemeCustomizerEnhancedProps> = (
|
||||
onChange,
|
||||
presetName = 'default',
|
||||
onPresetChange,
|
||||
isPreviewMode = false,
|
||||
showGalleryLayouts = true,
|
||||
hideActions = false,
|
||||
onApply,
|
||||
@@ -125,10 +123,11 @@ export const ThemeCustomizerEnhanced: React.FC<ThemeCustomizerEnhancedProps> = (
|
||||
onPresetChange('custom');
|
||||
}
|
||||
|
||||
if (isPreviewMode) {
|
||||
// Include customCss in the propagated theme
|
||||
onChange({ ...updated, customCss });
|
||||
}
|
||||
// Always propagate to parent so Save sees the latest values (#323).
|
||||
// The "Apply changes immediately (Live Preview)" toggle controls whether
|
||||
// the parent applies the theme globally — that gating belongs in the
|
||||
// parent, not here.
|
||||
onChange({ ...updated, customCss });
|
||||
};
|
||||
|
||||
const handlePresetSelect = (presetKey: string) => {
|
||||
@@ -140,9 +139,8 @@ export const ThemeCustomizerEnhanced: React.FC<ThemeCustomizerEnhancedProps> = (
|
||||
if (onPresetChange) {
|
||||
onPresetChange(presetKey);
|
||||
}
|
||||
if (isPreviewMode) {
|
||||
onChange(preset.config);
|
||||
}
|
||||
// Always propagate; live-apply gating is the parent's concern (#323).
|
||||
onChange(preset.config);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -795,7 +793,7 @@ export const ThemeCustomizerEnhanced: React.FC<ThemeCustomizerEnhancedProps> = (
|
||||
mutedTextColor: '#a3a3a3',
|
||||
};
|
||||
setLocalTheme(updated);
|
||||
if (isPreviewMode) onChange({ ...updated, customCss });
|
||||
onChange({ ...updated, customCss });
|
||||
} else if (mode === 'light' && localTheme.colorMode === 'dark') {
|
||||
const updated = {
|
||||
...localTheme,
|
||||
@@ -807,7 +805,7 @@ export const ThemeCustomizerEnhanced: React.FC<ThemeCustomizerEnhancedProps> = (
|
||||
mutedTextColor: '#737373',
|
||||
};
|
||||
setLocalTheme(updated);
|
||||
if (isPreviewMode) onChange({ ...updated, customCss });
|
||||
onChange({ ...updated, customCss });
|
||||
}
|
||||
}}
|
||||
className={`px-4 py-2 text-sm font-medium rounded-lg border transition-colors ${
|
||||
@@ -1181,10 +1179,8 @@ export const ThemeCustomizerEnhanced: React.FC<ThemeCustomizerEnhancedProps> = (
|
||||
setSelectedPreset('custom');
|
||||
onPresetChange('custom');
|
||||
}
|
||||
// Propagate customCss changes to parent in preview mode
|
||||
if (isPreviewMode) {
|
||||
onChange({ ...localTheme, customCss: newCss });
|
||||
}
|
||||
// Propagate to parent so Save sees the latest CSS (#323).
|
||||
onChange({ ...localTheme, customCss: newCss });
|
||||
}}
|
||||
placeholder="/* Add custom CSS here */"
|
||||
className="w-full h-40 px-3 py-2 font-mono text-sm border border-neutral-300 dark:border-neutral-600 rounded-lg bg-neutral-50 dark:bg-neutral-900 text-neutral-900 dark:text-neutral-100"
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import { Skeleton, SkeletonGalleryGrid } from '../common';
|
||||
|
||||
/**
|
||||
* Loading placeholder shown while a gallery is resolving (slug → info →
|
||||
* auto-login → photos). Used by GalleryPage during the pre-photos phases and
|
||||
* by GalleryView while the photos query runs, so the visitor sees one
|
||||
* continuous skeleton instead of multiple full-page interstitials (#321).
|
||||
*/
|
||||
export const GallerySkeleton: React.FC = () => (
|
||||
<div className="min-h-screen" style={{ backgroundColor: 'var(--color-background, #fafafa)' }}>
|
||||
<header className="bg-surface border-b border-surface sticky top-0 z-40">
|
||||
<div className="container py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<Skeleton height={32} width={200} className="mb-2" />
|
||||
<Skeleton height={20} width={300} />
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Skeleton height={40} width={120} />
|
||||
<Skeleton height={40} width={100} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div className="container mt-6">
|
||||
<Skeleton height={80} className="mb-6" />
|
||||
<SkeletonGalleryGrid count={12} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -3,7 +3,8 @@ import { differenceInDays, parseISO } from 'date-fns';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Button, SkeletonGalleryGrid, Skeleton } from '../common';
|
||||
import { Button } from '../common';
|
||||
import { GallerySkeleton } from './GallerySkeleton';
|
||||
import { useGalleryAuth, useTheme } from '../../contexts';
|
||||
import { useGalleryPhotos, useDownloadAllPhotos } from '../../hooks/useGallery';
|
||||
import { PhotoGridWithLayouts } from './PhotoGridWithLayouts';
|
||||
@@ -587,31 +588,7 @@ export const GalleryView: React.FC<GalleryViewProps> = ({ slug, event }) => {
|
||||
}, [showUrgentWarning, daysUntilExpiration, slug]);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="min-h-screen" style={{ backgroundColor: 'var(--color-background)' }}>
|
||||
{/* Header Skeleton */}
|
||||
<header className="bg-surface border-b border-surface sticky top-0 z-40">
|
||||
<div className="container py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<Skeleton height={32} width={200} className="mb-2" />
|
||||
<Skeleton height={20} width={300} />
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Skeleton height={40} width={120} />
|
||||
<Skeleton height={40} width={100} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Content Skeleton */}
|
||||
<div className="container mt-6">
|
||||
<Skeleton height={80} className="mb-6" />
|
||||
<SkeletonGalleryGrid count={12} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return <GallerySkeleton />;
|
||||
}
|
||||
|
||||
if (error || !data) {
|
||||
|
||||
Reference in New Issue
Block a user