Files
fotospiel-app/resources/js/admin/lib/limitWarnings.ts
2025-12-29 19:31:26 +01:00

128 lines
3.5 KiB
TypeScript

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 useHours = safeDays > 0 && safeDays < 2;
const roundedDays = Math.max(1, Math.ceil(safeDays));
const roundedHours = Math.max(1, Math.ceil(safeDays * 24));
const key = useHours
? roundedHours === 1
? 'galleryWarningHour'
: 'galleryWarningHours'
: roundedDays === 1
? 'galleryWarningDay'
: 'galleryWarningDays';
warnings.push({
id: 'gallery-warning',
scope: 'gallery',
tone: 'warning',
message: t(key, useHours ? { hours: roundedHours } : { days: roundedDays }),
});
}
}
return warnings;
}