der tenant admin hat eine neue, mobil unterstützende UI, login redirect funktioniert, typescript fehler wurden bereinigt. Neue Blog Posts von ChatGPT eingebaut, übersetzt von Gemini 2.5

This commit is contained in:
Codex Agent
2025-11-05 19:27:10 +01:00
parent adb93b5f9d
commit c6ac04eb15
44 changed files with 1995 additions and 1949 deletions

View File

@@ -3,7 +3,7 @@ import { ApiError, emitApiErrorEvent } from './lib/apiError';
import type { EventLimitSummary } from './lib/limitWarnings';
import i18n from './i18n';
type JsonValue = Record<string, unknown>;
type JsonValue = Record<string, any>;
export type TenantAccountProfile = {
id: number;
@@ -98,6 +98,7 @@ export type TenantPhoto = {
likes_count: number;
uploaded_at: string;
uploader_name: string | null;
caption?: string | null;
};
export type EventStats = {
@@ -107,6 +108,10 @@ export type EventStats = {
recent_uploads: number;
status: string;
is_active: boolean;
uploads_total?: number;
uploads_24h?: number;
likes_total?: number;
pending_photos?: number;
};
export type PaginationMeta = {
@@ -156,7 +161,9 @@ export async function trackOnboarding(step: string, meta?: Record<string, unknow
body: JSON.stringify({ step, meta }),
});
} catch (error) {
emitApiErrorEvent(new ApiError('onboarding.track_failed', i18n.t('common.errors.generic', 'Etwas ist schiefgelaufen.'), error));
const message = i18n.t('common.errors.generic', 'Etwas ist schiefgelaufen.');
emitApiErrorEvent({ message, code: 'onboarding.track_failed' });
console.error('[Onboarding] Failed to track tenant onboarding step', error);
}
}
@@ -165,7 +172,9 @@ export async function fetchOnboardingStatus(): Promise<TenantOnboardingStatus |
const response = await authorizedFetch('/api/v1/tenant/onboarding');
return (await response.json()) as TenantOnboardingStatus;
} catch (error) {
emitApiErrorEvent(new ApiError('onboarding.fetch_failed', i18n.t('common.errors.generic', 'Etwas ist schiefgelaufen.'), error));
const message = i18n.t('common.errors.generic', 'Etwas ist schiefgelaufen.');
emitApiErrorEvent({ message, code: 'onboarding.fetch_failed' });
console.error('[Onboarding] Failed to fetch tenant onboarding status', error);
return null;
}
}
@@ -232,6 +241,7 @@ export type TenantTask = {
difficulty: 'easy' | 'medium' | 'hard' | null;
due_date: string | null;
is_completed: boolean;
tenant_id: number | null;
collection_id: number | null;
source_task_id: number | null;
source_collection_id: number | null;
@@ -401,14 +411,17 @@ async function jsonOrThrow<T>(response: Response, message: string, options: Json
const body = await safeJson(response);
const status = response.status;
const errorPayload = body && typeof body === 'object' ? (body as Record<string, unknown>).error : null;
const errorMessage = (errorPayload && typeof errorPayload === 'object' && 'message' in errorPayload && typeof errorPayload.message === 'string')
? errorPayload.message
const errorRecord = errorPayload && typeof errorPayload === 'object'
? (errorPayload as Record<string, unknown>)
: null;
const errorMessage = errorRecord && typeof errorRecord.message === 'string'
? errorRecord.message
: message;
const errorCode = errorPayload && typeof errorPayload === 'object' && typeof errorPayload.code === 'string'
? errorPayload.code
const errorCode = errorRecord && typeof errorRecord.code === 'string'
? errorRecord.code
: undefined;
let errorMeta = errorPayload && typeof errorPayload === 'object' && typeof errorPayload.meta === 'object'
? errorPayload.meta as Record<string, unknown>
let errorMeta = errorRecord && typeof errorRecord.meta === 'object'
? (errorRecord.meta ?? null) as Record<string, unknown>
: undefined;
if (!errorMeta && body && typeof body === 'object' && 'errors' in body && typeof body.errors === 'object') {
@@ -587,6 +600,7 @@ function normalizePhoto(photo: TenantPhoto): TenantPhoto {
likes_count: Number(photo.likes_count ?? 0),
uploaded_at: photo.uploaded_at,
uploader_name: photo.uploader_name ?? null,
caption: photo.caption ?? null,
};
}
@@ -654,7 +668,6 @@ function normalizeTask(task: JsonValue): TenantTask {
return {
id: Number(task.id ?? 0),
tenant_id: task.tenant_id ?? null,
slug: String(task.slug ?? `task-${task.id ?? ''}`),
title: pickTranslatedText(titleTranslations, 'Ohne Titel'),
title_translations: titleTranslations,
@@ -668,6 +681,7 @@ function normalizeTask(task: JsonValue): TenantTask {
difficulty: (task.difficulty ?? null) as TenantTask['difficulty'],
due_date: task.due_date ?? null,
is_completed: Boolean(task.is_completed ?? false),
tenant_id: task.tenant_id ?? null,
collection_id: task.collection_id ?? null,
source_task_id: task.source_task_id ?? null,
source_collection_id: task.source_collection_id ?? null,
@@ -680,7 +694,11 @@ function normalizeTask(task: JsonValue): TenantTask {
function normalizeTaskCollection(raw: JsonValue): TenantTaskCollection {
const nameTranslations = normalizeTranslationMap(raw.name_translations ?? raw.name ?? {});
const descriptionTranslations = normalizeTranslationMap(raw.description_translations ?? raw.description ?? {}, true);
const descriptionTranslations = normalizeTranslationMap(
raw.description_translations ?? raw.description ?? {},
undefined,
true
);
const eventTypeRaw = raw.event_type ?? raw.eventType ?? null;
let eventType: TenantTaskCollection['event_type'] = null;
@@ -715,7 +733,11 @@ function normalizeTaskCollection(raw: JsonValue): TenantTaskCollection {
function normalizeEmotion(raw: JsonValue): TenantEmotion {
const nameTranslations = normalizeTranslationMap(raw.name_translations ?? raw.name ?? {});
const descriptionTranslations = normalizeTranslationMap(raw.description_translations ?? raw.description ?? {}, true);
const descriptionTranslations = normalizeTranslationMap(
raw.description_translations ?? raw.description ?? {},
undefined,
true
);
const eventTypes = Array.isArray(raw.event_types ?? raw.eventTypes)
? (raw.event_types ?? raw.eventTypes)
@@ -906,6 +928,10 @@ export async function getEventStats(slug: string): Promise<EventStats> {
recent_uploads: Number(data.recent_uploads ?? 0),
status: data.status ?? 'draft',
is_active: Boolean(data.is_active),
uploads_total: Number((data as JsonValue).uploads_total ?? data.total ?? 0),
uploads_24h: Number((data as JsonValue).uploads_24h ?? data.recent_uploads ?? 0),
likes_total: Number((data as JsonValue).likes_total ?? data.likes ?? 0),
pending_photos: Number((data as JsonValue).pending_photos ?? 0),
};
}
@@ -1006,8 +1032,8 @@ export async function getEventToolkit(slug: string): Promise<EventToolkit> {
: [],
},
photos: {
pending: pendingPhotosRaw.map((photo) => normalizePhoto(photo as TenantPhoto)),
recent: recentPhotosRaw.map((photo) => normalizePhoto(photo as TenantPhoto)),
pending: pendingPhotosRaw.map((photo: JsonValue) => normalizePhoto(photo as TenantPhoto)),
recent: recentPhotosRaw.map((photo: JsonValue) => normalizePhoto(photo as TenantPhoto)),
},
invites: {
summary: {