Services/Helpers sind entfernt, API/Frontend angepasst, Tests auf Paddle umgestellt. Außerdem wurde die CSP gestrafft und Stripe‑Texte in den Abandoned‑Checkout‑Mails ersetzt.
93 lines
2.4 KiB
TypeScript
93 lines
2.4 KiB
TypeScript
export class ApiError extends Error {
|
|
constructor(
|
|
message: string,
|
|
public readonly status?: number,
|
|
public readonly code?: string,
|
|
public readonly meta?: Record<string, unknown>,
|
|
) {
|
|
super(message);
|
|
this.name = 'ApiError';
|
|
}
|
|
}
|
|
|
|
export function isApiError(value: unknown): value is ApiError {
|
|
return value instanceof ApiError;
|
|
}
|
|
|
|
export function getApiErrorMessage(error: unknown, fallback: string): string {
|
|
if (isApiError(error)) {
|
|
if (error.message) {
|
|
return error.message;
|
|
}
|
|
|
|
if (error.status && error.status >= 500) {
|
|
return 'Der Server hat nicht reagiert. Bitte versuche es später erneut.';
|
|
}
|
|
|
|
return fallback;
|
|
}
|
|
|
|
if (error instanceof Error && error.message) {
|
|
return error.message;
|
|
}
|
|
|
|
return fallback;
|
|
}
|
|
|
|
export function getApiValidationMessage(error: unknown, fallback: string): string {
|
|
if (isApiError(error)) {
|
|
const errors = normalizeValidationErrors(error.meta);
|
|
if (errors.length) {
|
|
return errors.join('\n');
|
|
}
|
|
}
|
|
|
|
return getApiErrorMessage(error, fallback);
|
|
}
|
|
|
|
export type ApiErrorEventDetail = {
|
|
message: string;
|
|
status?: number;
|
|
code?: string;
|
|
meta?: Record<string, unknown>;
|
|
};
|
|
|
|
export const API_ERROR_EVENT = 'admin:api:error';
|
|
|
|
export function emitApiErrorEvent(detail: ApiErrorEventDetail): void {
|
|
if (typeof window === 'undefined') {
|
|
return;
|
|
}
|
|
|
|
window.dispatchEvent(new CustomEvent<ApiErrorEventDetail>(API_ERROR_EVENT, { detail }));
|
|
}
|
|
|
|
export function registerApiErrorListener(handler: (detail: ApiErrorEventDetail) => void): () => void {
|
|
if (typeof window === 'undefined') {
|
|
return () => {};
|
|
}
|
|
|
|
const listener = (event: Event) => {
|
|
const customEvent = event as CustomEvent<ApiErrorEventDetail>;
|
|
handler(customEvent.detail);
|
|
};
|
|
|
|
window.addEventListener(API_ERROR_EVENT, listener as EventListener);
|
|
return () => window.removeEventListener(API_ERROR_EVENT, listener as EventListener);
|
|
}
|
|
|
|
function normalizeValidationErrors(meta?: Record<string, unknown>): string[] {
|
|
if (!meta || typeof meta !== 'object') {
|
|
return [];
|
|
}
|
|
|
|
const errors = meta.errors;
|
|
if (!errors || typeof errors !== 'object') {
|
|
return [];
|
|
}
|
|
|
|
return Object.values(errors as Record<string, unknown>)
|
|
.flatMap((value) => (Array.isArray(value) ? value : [value]))
|
|
.filter((value): value is string => typeof value === 'string' && value.trim() !== '');
|
|
}
|