119 lines
3.2 KiB
TypeScript
119 lines
3.2 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 key = safeDays === 1 ? 'galleryWarningDay' : 'galleryWarningDays';
|
|
warnings.push({
|
|
id: 'gallery-warning',
|
|
scope: 'gallery',
|
|
tone: 'warning',
|
|
message: t(key, { days: safeDays }),
|
|
});
|
|
}
|
|
}
|
|
|
|
return warnings;
|
|
}
|