added a help system, replaced the words "tenant" and "Pwa" with better alternatives. corrected and implemented cron jobs. prepared going live on a coolify-powered system.
This commit is contained in:
@@ -98,6 +98,7 @@ export type TenantPhoto = {
|
||||
likes_count: number;
|
||||
uploaded_at: string;
|
||||
uploader_name: string | null;
|
||||
ingest_source?: string | null;
|
||||
caption?: string | null;
|
||||
};
|
||||
|
||||
@@ -114,6 +115,40 @@ export type EventStats = {
|
||||
pending_photos?: number;
|
||||
};
|
||||
|
||||
export type PhotoboothStatus = {
|
||||
enabled: boolean;
|
||||
status: string | null;
|
||||
username: string | null;
|
||||
password: string | null;
|
||||
path: string | null;
|
||||
ftp_url: string | null;
|
||||
expires_at: string | null;
|
||||
rate_limit_per_minute: number;
|
||||
ftp: {
|
||||
host: string | null;
|
||||
port: number;
|
||||
require_ftps: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
export type HelpCenterArticleSummary = {
|
||||
slug: string;
|
||||
title: string;
|
||||
summary: string;
|
||||
updated_at?: string;
|
||||
status?: string;
|
||||
translation_state?: string;
|
||||
related?: Array<{ slug: string }>;
|
||||
};
|
||||
|
||||
export type HelpCenterArticle = HelpCenterArticleSummary & {
|
||||
body_html?: string;
|
||||
body_markdown?: string;
|
||||
owner?: string;
|
||||
requires_app_version?: string | null;
|
||||
version_introduced?: string;
|
||||
};
|
||||
|
||||
export type PaginationMeta = {
|
||||
current_page: number;
|
||||
last_page: number;
|
||||
@@ -184,6 +219,60 @@ export async function fetchOnboardingStatus(): Promise<TenantOnboardingStatus |
|
||||
}
|
||||
}
|
||||
|
||||
function resolveHelpLocale(locale?: string): 'de' | 'en' {
|
||||
if (!locale) {
|
||||
return 'de';
|
||||
}
|
||||
const normalized = locale.toLowerCase().split('-')[0];
|
||||
return normalized === 'en' ? 'en' : 'de';
|
||||
}
|
||||
|
||||
export async function fetchHelpCenterArticles(locale?: string): Promise<HelpCenterArticleSummary[]> {
|
||||
const resolvedLocale = resolveHelpLocale(locale);
|
||||
|
||||
try {
|
||||
const params = new URLSearchParams({ audience: 'admin', locale: resolvedLocale });
|
||||
const response = await authorizedFetch(`/api/v1/help?${params.toString()}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch help articles');
|
||||
}
|
||||
|
||||
const payload = (await response.json()) as { data?: HelpCenterArticleSummary[] };
|
||||
return Array.isArray(payload?.data) ? payload.data : [];
|
||||
} catch (error) {
|
||||
const message = i18n.t('common.errors.generic', 'Etwas ist schiefgelaufen.');
|
||||
emitApiErrorEvent({ message, code: 'help.fetch_list_failed' });
|
||||
console.error('[HelpApi] Failed to fetch help articles', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchHelpCenterArticle(slug: string, locale?: string): Promise<HelpCenterArticle> {
|
||||
const resolvedLocale = resolveHelpLocale(locale);
|
||||
|
||||
try {
|
||||
const params = new URLSearchParams({ audience: 'admin', locale: resolvedLocale });
|
||||
const response = await authorizedFetch(`/api/v1/help/${encodeURIComponent(slug)}?${params.toString()}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch help article');
|
||||
}
|
||||
|
||||
const payload = (await response.json()) as { data?: HelpCenterArticle };
|
||||
if (!payload?.data) {
|
||||
throw new Error('Empty help article response');
|
||||
}
|
||||
|
||||
return payload.data;
|
||||
} catch (error) {
|
||||
const message = i18n.t('common.errors.generic', 'Etwas ist schiefgelaufen.');
|
||||
emitApiErrorEvent({ message, code: 'help.fetch_detail_failed' });
|
||||
console.error('[HelpApi] Failed to fetch help article', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export type TenantPackageSummary = {
|
||||
id: number;
|
||||
package_id: number;
|
||||
@@ -883,6 +972,38 @@ function eventEndpoint(slug: string): string {
|
||||
return `/api/v1/tenant/events/${encodeURIComponent(slug)}`;
|
||||
}
|
||||
|
||||
function photoboothEndpoint(slug: string): string {
|
||||
return `${eventEndpoint(slug)}/photobooth`;
|
||||
}
|
||||
|
||||
function normalizePhotoboothStatus(payload: JsonValue): PhotoboothStatus {
|
||||
const ftp = (payload.ftp ?? {}) as JsonValue;
|
||||
|
||||
return {
|
||||
enabled: Boolean(payload.enabled),
|
||||
status: typeof payload.status === 'string' ? payload.status : null,
|
||||
username: typeof payload.username === 'string' ? payload.username : null,
|
||||
password: typeof payload.password === 'string' ? payload.password : null,
|
||||
path: typeof payload.path === 'string' ? payload.path : null,
|
||||
ftp_url: typeof payload.ftp_url === 'string' ? payload.ftp_url : null,
|
||||
expires_at: typeof payload.expires_at === 'string' ? payload.expires_at : null,
|
||||
rate_limit_per_minute: Number(payload.rate_limit_per_minute ?? ftp.rate_limit_per_minute ?? 0),
|
||||
ftp: {
|
||||
host: typeof ftp.host === 'string' ? ftp.host : null,
|
||||
port: Number(ftp.port ?? payload.ftp_port ?? 0) || 0,
|
||||
require_ftps: Boolean(ftp.require_ftps ?? payload.require_ftps),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function requestPhotoboothStatus(slug: string, path = '', init: RequestInit = {}, errorMessage = 'Failed to fetch photobooth status'): Promise<PhotoboothStatus> {
|
||||
const response = await authorizedFetch(`${photoboothEndpoint(slug)}${path}`, init);
|
||||
const payload = await jsonOrThrow<JsonValue | { data: JsonValue }>(response, errorMessage);
|
||||
const body = (payload as { data?: JsonValue }).data ?? (payload as JsonValue);
|
||||
|
||||
return normalizePhotoboothStatus(body ?? {});
|
||||
}
|
||||
|
||||
export async function getEvents(options?: { force?: boolean }): Promise<TenantEvent[]> {
|
||||
return cachedFetch(
|
||||
CacheKeys.events,
|
||||
@@ -1118,6 +1239,22 @@ export async function getEventToolkit(slug: string): Promise<EventToolkit> {
|
||||
return toolkit;
|
||||
}
|
||||
|
||||
export async function getEventPhotoboothStatus(slug: string): Promise<PhotoboothStatus> {
|
||||
return requestPhotoboothStatus(slug, '', {}, 'Failed to load photobooth status');
|
||||
}
|
||||
|
||||
export async function enableEventPhotobooth(slug: string): Promise<PhotoboothStatus> {
|
||||
return requestPhotoboothStatus(slug, '/enable', { method: 'POST' }, 'Failed to enable photobooth access');
|
||||
}
|
||||
|
||||
export async function rotateEventPhotobooth(slug: string): Promise<PhotoboothStatus> {
|
||||
return requestPhotoboothStatus(slug, '/rotate', { method: 'POST' }, 'Failed to rotate credentials');
|
||||
}
|
||||
|
||||
export async function disableEventPhotobooth(slug: string): Promise<PhotoboothStatus> {
|
||||
return requestPhotoboothStatus(slug, '/disable', { method: 'POST' }, 'Failed to disable photobooth access');
|
||||
}
|
||||
|
||||
export async function submitTenantFeedback(payload: {
|
||||
category: string;
|
||||
sentiment?: 'positive' | 'neutral' | 'negative';
|
||||
|
||||
Reference in New Issue
Block a user