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:
@@ -181,6 +181,19 @@ export type EventAddonCatalogItem = {
|
||||
increments?: Record<string, number>;
|
||||
};
|
||||
|
||||
export type TenantFontVariant = {
|
||||
variant: string | null;
|
||||
weight: number;
|
||||
style: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
export type TenantFont = {
|
||||
family: string;
|
||||
category?: string | null;
|
||||
variants: TenantFontVariant[];
|
||||
};
|
||||
|
||||
export type EventAddonSummary = {
|
||||
id: number;
|
||||
key: string;
|
||||
@@ -1266,6 +1279,18 @@ export async function getAddonCatalog(): Promise<EventAddonCatalogItem[]> {
|
||||
return data.data ?? [];
|
||||
}
|
||||
|
||||
export async function getTenantFonts(): Promise<TenantFont[]> {
|
||||
return cachedFetch(
|
||||
CacheKeys.fonts,
|
||||
async () => {
|
||||
const response = await authorizedFetch('/api/v1/tenant/fonts');
|
||||
const data = await jsonOrThrow<{ data?: TenantFont[] }>(response, 'Failed to load fonts');
|
||||
return data.data ?? [];
|
||||
},
|
||||
6 * 60 * 60 * 1000,
|
||||
);
|
||||
}
|
||||
|
||||
export async function getEventTypes(): Promise<TenantEventType[]> {
|
||||
const response = await authorizedFetch('/api/v1/tenant/event-types');
|
||||
const data = await jsonOrThrow<{ data?: JsonValue[] }>(response, 'Failed to load event types');
|
||||
@@ -1672,6 +1697,27 @@ export async function getDashboardSummary(options?: { force?: boolean }): Promis
|
||||
);
|
||||
}
|
||||
|
||||
export type TenantSettingsPayload = {
|
||||
id: number;
|
||||
settings: Record<string, unknown>;
|
||||
updated_at: string | null;
|
||||
};
|
||||
|
||||
export async function getTenantSettings(): Promise<TenantSettingsPayload> {
|
||||
const response = await authorizedFetch('/api/v1/tenant/settings');
|
||||
const data = await jsonOrThrow<{ data?: { id?: number; settings?: Record<string, unknown>; updated_at?: string | null } }>(
|
||||
response,
|
||||
'Failed to load tenant settings',
|
||||
);
|
||||
const payload = (data.data ?? {}) as Record<string, unknown>;
|
||||
|
||||
return {
|
||||
id: Number(payload.id ?? 0),
|
||||
settings: (payload.settings ?? {}) as Record<string, unknown>,
|
||||
updated_at: (payload.updated_at ?? null) as string | null,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getTenantPackagesOverview(options?: { force?: boolean }): Promise<{
|
||||
packages: TenantPackageSummary[];
|
||||
activePackage: TenantPackageSummary | null;
|
||||
@@ -2236,6 +2282,7 @@ const CacheKeys = {
|
||||
dashboard: 'tenant:dashboard',
|
||||
events: 'tenant:events',
|
||||
packages: 'tenant:packages',
|
||||
fonts: 'tenant:fonts',
|
||||
} as const;
|
||||
|
||||
function cachedFetch<T>(
|
||||
|
||||
Reference in New Issue
Block a user