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:
@@ -33,6 +33,7 @@ import { useTranslation, type TranslateFn } from '../i18n/useTranslation';
|
||||
import { buildLimitSummaries, type LimitSummaryCard } from '../lib/limitSummaries';
|
||||
import { resolveUploadErrorDialog, type UploadErrorDialog } from '../lib/uploadErrorDialog';
|
||||
import { useEventStats } from '../context/EventStatsContext';
|
||||
import { useEventBranding } from '../context/EventBrandingContext';
|
||||
import { compressPhoto, formatBytes } from '../lib/image';
|
||||
|
||||
interface Task {
|
||||
@@ -113,6 +114,11 @@ export default function UploadPage() {
|
||||
const { markCompleted } = useGuestTaskProgress(token);
|
||||
const { t, locale } = useTranslation();
|
||||
const stats = useEventStats();
|
||||
const { branding } = useEventBranding();
|
||||
const radius = branding.buttons?.radius ?? 12;
|
||||
const buttonStyle = branding.buttons?.style ?? 'filled';
|
||||
const linkColor = branding.buttons?.linkColor ?? branding.secondaryColor;
|
||||
const bodyFont = branding.typography?.body ?? branding.fontFamily ?? undefined;
|
||||
|
||||
const taskIdParam = searchParams.get('task');
|
||||
const emotionSlug = searchParams.get('emotion') || '';
|
||||
@@ -936,7 +942,10 @@ const renderWithDialog = (content: ReactNode, wrapperClassName = 'space-y-6 pb-[
|
||||
const canRetryCamera = permissionState !== 'unsupported';
|
||||
|
||||
return (
|
||||
<div className="rounded-[32px] border border-white/15 bg-white/85 p-5 text-slate-900 shadow-lg dark:border-white/10 dark:bg-white/5 dark:text-white">
|
||||
<div
|
||||
className="rounded-[32px] border border-white/15 bg-white/85 p-5 text-slate-900 shadow-lg dark:border-white/10 dark:bg-white/5 dark:text-white"
|
||||
style={{ borderRadius: radius, fontFamily: bodyFont }}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-2xl bg-slate-900/10 dark:bg-white/10">
|
||||
<Camera className="h-6 w-6" />
|
||||
@@ -948,11 +957,20 @@ const renderWithDialog = (content: ReactNode, wrapperClassName = 'space-y-6 pb-[
|
||||
</div>
|
||||
<div className="mt-4 flex flex-wrap gap-3">
|
||||
{canRetryCamera && (
|
||||
<Button onClick={startCamera} size="sm">
|
||||
<Button
|
||||
onClick={startCamera}
|
||||
size="sm"
|
||||
style={buttonStyle === 'outline' ? { borderRadius: radius, background: 'transparent', color: linkColor, border: `1px solid ${linkColor}` } : { borderRadius: radius }}
|
||||
>
|
||||
{t('upload.buttons.startCamera')}
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="secondary" size="sm" onClick={() => fileInputRef.current?.click()}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
style={buttonStyle === 'outline' ? { borderRadius: radius, background: 'transparent', color: linkColor, border: `1px solid ${linkColor}` } : { borderRadius: radius }}
|
||||
>
|
||||
{t('upload.galleryButton')}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -962,9 +980,12 @@ const renderWithDialog = (content: ReactNode, wrapperClassName = 'space-y-6 pb-[
|
||||
|
||||
return renderWithDialog(
|
||||
<>
|
||||
<div className="relative pt-8">
|
||||
<div className="relative pt-8" style={bodyFont ? { fontFamily: bodyFont } : undefined}>
|
||||
{taskFloatingCard}
|
||||
<section className="relative overflow-hidden rounded-[32px] border border-white/10 bg-black text-white shadow-2xl">
|
||||
<section
|
||||
className="relative overflow-hidden border border-white/10 bg-black text-white shadow-2xl"
|
||||
style={{ borderRadius: radius }}
|
||||
>
|
||||
<div className="relative aspect-[3/4] sm:aspect-video">
|
||||
<video
|
||||
ref={videoRef}
|
||||
@@ -1028,7 +1049,10 @@ const renderWithDialog = (content: ReactNode, wrapperClassName = 'space-y-6 pb-[
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="relative z-30 flex flex-col gap-4 bg-gradient-to-t from-black via-black/80 to-transparent p-4">
|
||||
<div
|
||||
className="relative z-30 flex flex-col gap-4 bg-gradient-to-t from-black via-black/80 to-transparent p-4"
|
||||
style={{ fontFamily: bodyFont }}
|
||||
>
|
||||
{uploadWarning && (
|
||||
<Alert className="border-yellow-400/20 bg-yellow-500/10 text-white">
|
||||
<AlertDescription className="text-xs">
|
||||
|
||||
Reference in New Issue
Block a user