Limit-Status im Upload-Flow anzeigen (Warnbanner + Sperrzustände).
Upload-Fehlercodes auswerten und freundliche Dialoge zeigen.
This commit is contained in:
@@ -13,3 +13,23 @@ export class ApiError extends Error {
|
||||
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;
|
||||
}
|
||||
|
||||
118
resources/js/admin/lib/limitWarnings.ts
Normal file
118
resources/js/admin/lib/limitWarnings.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
export type LimitWarningTone = 'warning' | 'danger';
|
||||
|
||||
export type LimitWarning = {
|
||||
id: string;
|
||||
scope: 'photos' | 'guests' | 'gallery';
|
||||
tone: LimitWarningTone;
|
||||
message: string;
|
||||
};
|
||||
|
||||
export type LimitUsageSummary = {
|
||||
limit: number | null;
|
||||
used: number;
|
||||
remaining: number | null;
|
||||
percentage: number | null;
|
||||
state: 'ok' | 'warning' | 'limit_reached' | 'unlimited';
|
||||
threshold_reached: number | null;
|
||||
next_threshold: number | null;
|
||||
thresholds: number[];
|
||||
} | null;
|
||||
|
||||
export type GallerySummary = {
|
||||
state: 'ok' | 'warning' | 'expired' | 'unlimited';
|
||||
expires_at: string | null;
|
||||
days_remaining: number | null;
|
||||
warning_thresholds: number[];
|
||||
warning_triggered: number | null;
|
||||
warning_sent_at: string | null;
|
||||
expired_notified_at: string | null;
|
||||
} | null;
|
||||
|
||||
export type EventLimitSummary = {
|
||||
photos: LimitUsageSummary;
|
||||
guests: LimitUsageSummary;
|
||||
gallery: GallerySummary;
|
||||
can_upload_photos: boolean;
|
||||
can_add_guests: boolean;
|
||||
} | null | undefined;
|
||||
|
||||
type TranslateFn = (key: string, options?: Record<string, unknown>) => string;
|
||||
|
||||
function hasRemaining(summary: LimitUsageSummary): summary is LimitUsageSummary & { remaining: number; limit: number } {
|
||||
return Boolean(summary)
|
||||
&& typeof summary?.remaining === 'number'
|
||||
&& typeof summary?.limit === 'number';
|
||||
}
|
||||
|
||||
export function buildLimitWarnings(limits: EventLimitSummary, t: TranslateFn): LimitWarning[] {
|
||||
if (!limits) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const warnings: LimitWarning[] = [];
|
||||
|
||||
if (limits.photos) {
|
||||
if (limits.photos.state === 'limit_reached') {
|
||||
warnings.push({
|
||||
id: 'photos-limit',
|
||||
scope: 'photos',
|
||||
tone: 'danger',
|
||||
message: t('photosBlocked'),
|
||||
});
|
||||
} else if (limits.photos.state === 'warning' && hasRemaining(limits.photos)) {
|
||||
warnings.push({
|
||||
id: 'photos-warning',
|
||||
scope: 'photos',
|
||||
tone: 'warning',
|
||||
message: t('photosWarning', {
|
||||
remaining: limits.photos.remaining,
|
||||
limit: limits.photos.limit,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (limits.guests) {
|
||||
if (limits.guests.state === 'limit_reached') {
|
||||
warnings.push({
|
||||
id: 'guests-limit',
|
||||
scope: 'guests',
|
||||
tone: 'danger',
|
||||
message: t('guestsBlocked'),
|
||||
});
|
||||
} else if (limits.guests.state === 'warning' && hasRemaining(limits.guests)) {
|
||||
warnings.push({
|
||||
id: 'guests-warning',
|
||||
scope: 'guests',
|
||||
tone: 'warning',
|
||||
message: t('guestsWarning', {
|
||||
remaining: limits.guests.remaining,
|
||||
limit: limits.guests.limit,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (limits.gallery) {
|
||||
if (limits.gallery.state === 'expired') {
|
||||
warnings.push({
|
||||
id: 'gallery-expired',
|
||||
scope: 'gallery',
|
||||
tone: 'danger',
|
||||
message: t('galleryExpired'),
|
||||
});
|
||||
} else if (limits.gallery.state === 'warning') {
|
||||
const days = limits.gallery.days_remaining ?? 0;
|
||||
const safeDays = Math.max(0, days);
|
||||
const key = safeDays === 1 ? 'galleryWarningDay' : 'galleryWarningDays';
|
||||
warnings.push({
|
||||
id: 'gallery-warning',
|
||||
scope: 'gallery',
|
||||
tone: 'warning',
|
||||
message: t(key, { days: safeDays }),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return warnings;
|
||||
}
|
||||
Reference in New Issue
Block a user