Neue Branding-Page und Gäste-PWA reagiert nun auf Branding-Einstellungen vom event-admin. Implemented local Google Fonts pipeline and admin UI selects for branding and invites.
- Added fonts:sync-google command (uses GOOGLE_FONTS_API_KEY, generates /public/fonts/google files, manifest, CSS, cache flush) and
exposed manifest via new GET /api/v1/tenant/fonts endpoint with fallbacks for existing local fonts.
- Imported generated fonts CSS, added API client + font loader hook, and wired branding page font fields to searchable selects (with
custom override) that auto-load selected fonts.
- Invites layout editor now offers font selection per element with runtime font loading for previews/export alignment.
- New tests cover font sync command and font manifest API.
Tests run: php artisan test --filter=Fonts --testsuite=Feature.
Note: repository already has other modified files (e.g., EventPublicController, SettingsStoreRequest, guest components, etc.); left
untouched. Run php artisan fonts:sync-google after setting the API key to populate /public/fonts/google.
This commit is contained in:
@@ -8,6 +8,7 @@ import { fetchGalleryMeta, fetchGalleryPhotos, type GalleryMetaResponse, type Ga
|
||||
import { useTranslation } from '../i18n/useTranslation';
|
||||
import { DEFAULT_LOCALE, isLocaleCode } from '../i18n/messages';
|
||||
import { AlertTriangle, Download, Loader2, X } from 'lucide-react';
|
||||
import { getContrastingTextColor } from '../lib/color';
|
||||
|
||||
interface GalleryState {
|
||||
meta: GalleryMetaResponse | null;
|
||||
@@ -90,6 +91,29 @@ export default function PublicGalleryPage(): React.ReactElement | null {
|
||||
loadInitial();
|
||||
}, [loadInitial]);
|
||||
|
||||
useEffect(() => {
|
||||
const mode = state.meta?.branding.mode;
|
||||
if (!mode || typeof document === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const wasDark = document.documentElement.classList.contains('dark');
|
||||
|
||||
if (mode === 'dark') {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else if (mode === 'light') {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (wasDark) {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
};
|
||||
}, [state.meta?.branding.mode]);
|
||||
|
||||
const loadMore = useCallback(async () => {
|
||||
if (!token || !state.cursor || state.loadingMore) {
|
||||
return;
|
||||
@@ -140,10 +164,17 @@ export default function PublicGalleryPage(): React.ReactElement | null {
|
||||
return {} as React.CSSProperties;
|
||||
}
|
||||
|
||||
const palette = state.meta.branding.palette ?? {};
|
||||
const primary = palette.primary ?? state.meta.branding.primary_color;
|
||||
const secondary = palette.secondary ?? state.meta.branding.secondary_color;
|
||||
const background = palette.background ?? state.meta.branding.background_color;
|
||||
const surface = palette.surface ?? state.meta.branding.surface_color ?? background;
|
||||
|
||||
return {
|
||||
'--gallery-primary': state.meta.branding.primary_color,
|
||||
'--gallery-secondary': state.meta.branding.secondary_color,
|
||||
'--gallery-background': state.meta.branding.background_color,
|
||||
'--gallery-primary': primary,
|
||||
'--gallery-secondary': secondary,
|
||||
'--gallery-background': background,
|
||||
'--gallery-surface': surface,
|
||||
} as React.CSSProperties & Record<string, string>;
|
||||
}, [state.meta]);
|
||||
|
||||
@@ -151,9 +182,13 @@ export default function PublicGalleryPage(): React.ReactElement | null {
|
||||
if (!state.meta) {
|
||||
return {};
|
||||
}
|
||||
const palette = state.meta.branding.palette ?? {};
|
||||
const primary = palette.primary ?? state.meta.branding.primary_color;
|
||||
const secondary = palette.secondary ?? state.meta.branding.secondary_color ?? primary;
|
||||
const textColor = getContrastingTextColor(primary ?? '#f43f5e', '#0f172a', '#ffffff');
|
||||
return {
|
||||
background: state.meta.branding.primary_color,
|
||||
color: '#ffffff',
|
||||
background: `linear-gradient(135deg, ${primary}, ${secondary})`,
|
||||
color: textColor,
|
||||
} satisfies React.CSSProperties;
|
||||
}, [state.meta]);
|
||||
|
||||
@@ -162,7 +197,7 @@ export default function PublicGalleryPage(): React.ReactElement | null {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
color: state.meta.branding.primary_color,
|
||||
color: (state.meta.branding.palette?.primary ?? state.meta.branding.primary_color),
|
||||
} satisfies React.CSSProperties;
|
||||
}, [state.meta]);
|
||||
|
||||
@@ -171,7 +206,7 @@ export default function PublicGalleryPage(): React.ReactElement | null {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
backgroundColor: state.meta.branding.background_color,
|
||||
backgroundColor: state.meta.branding.palette?.background ?? state.meta.branding.background_color,
|
||||
} satisfies React.CSSProperties;
|
||||
}, [state.meta]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user