Improve package usage visibility
This commit is contained in:
@@ -14,7 +14,8 @@ const t = (key: string, options?: Record<string, unknown> | string) => {
|
||||
const template = (options?.defaultValue as string | undefined) ?? key;
|
||||
return template
|
||||
.replace('{{used}}', String(options?.used ?? '{{used}}'))
|
||||
.replace('{{limit}}', String(options?.limit ?? '{{limit}}'));
|
||||
.replace('{{limit}}', String(options?.limit ?? '{{limit}}'))
|
||||
.replace('{{remaining}}', String(options?.remaining ?? '{{remaining}}'));
|
||||
};
|
||||
|
||||
describe('packageSummary helpers', () => {
|
||||
@@ -46,10 +47,10 @@ describe('packageSummary helpers', () => {
|
||||
});
|
||||
|
||||
it('returns labeled limit entries', () => {
|
||||
const result = getPackageLimitEntries({ max_photos: 120 }, t);
|
||||
const result = getPackageLimitEntries({ max_photos: 120, remaining_photos: 30 }, t);
|
||||
|
||||
expect(result[0].label).toBe('Photos');
|
||||
expect(result[0].value).toBe('120');
|
||||
expect(result[0].value).toBe('30 of 120 remaining');
|
||||
});
|
||||
|
||||
it('formats event usage copy', () => {
|
||||
|
||||
@@ -2,6 +2,11 @@ import type { TenantPackageSummary } from '../../api';
|
||||
|
||||
type Translate = (key: string, options?: Record<string, unknown> | string) => string;
|
||||
|
||||
type LimitUsageOverrides = {
|
||||
remainingEvents?: number | null;
|
||||
usedEvents?: number | null;
|
||||
};
|
||||
|
||||
const FEATURE_LABELS: Record<string, { key: string; fallback: string }> = {
|
||||
priority_support: {
|
||||
key: 'mobileDashboard.packageSummary.feature.priority_support',
|
||||
@@ -111,6 +116,38 @@ export type PackageLimitEntry = {
|
||||
value: string;
|
||||
};
|
||||
|
||||
const toNumber = (value: unknown): number | null => {
|
||||
if (typeof value === 'number' && Number.isFinite(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (typeof value === 'string' && value.trim() !== '') {
|
||||
const parsed = Number(value);
|
||||
if (Number.isFinite(parsed)) {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const formatLimitWithRemaining = (limit: number | null, remaining: number | null, t: Translate): string => {
|
||||
if (limit === null || limit === undefined) {
|
||||
return t('mobileDashboard.packageSummary.unlimited', 'Unlimited');
|
||||
}
|
||||
|
||||
if (remaining !== null && remaining >= 0) {
|
||||
const normalizedRemaining = Number.isFinite(remaining) ? Math.max(0, Math.round(remaining)) : remaining;
|
||||
return t('mobileBilling.usage.remainingOf', {
|
||||
remaining: normalizedRemaining,
|
||||
limit,
|
||||
defaultValue: '{{remaining}} of {{limit}} remaining',
|
||||
});
|
||||
}
|
||||
|
||||
return String(limit);
|
||||
};
|
||||
|
||||
export function getPackageFeatureLabel(feature: string, t: Translate): string {
|
||||
const entry = FEATURE_LABELS[feature];
|
||||
if (entry) {
|
||||
@@ -128,7 +165,11 @@ export function formatPackageLimit(value: number | null | undefined, t: Translat
|
||||
return String(value);
|
||||
}
|
||||
|
||||
export function getPackageLimitEntries(limits: Record<string, unknown> | null, t: Translate): PackageLimitEntry[] {
|
||||
export function getPackageLimitEntries(
|
||||
limits: Record<string, unknown> | null,
|
||||
t: Translate,
|
||||
usageOverrides: LimitUsageOverrides = {}
|
||||
): PackageLimitEntry[] {
|
||||
if (!limits) {
|
||||
return [];
|
||||
}
|
||||
@@ -136,10 +177,37 @@ export function getPackageLimitEntries(limits: Record<string, unknown> | null, t
|
||||
return LIMIT_LABELS.map(({ key, labelKey, fallback }) => ({
|
||||
key,
|
||||
label: t(labelKey, fallback),
|
||||
value: formatPackageLimit((limits as Record<string, number | null>)[key], t),
|
||||
value: formatLimitWithRemaining(
|
||||
toNumber((limits as Record<string, number | null>)[key]),
|
||||
resolveRemainingForKey(limits, key, usageOverrides),
|
||||
t
|
||||
),
|
||||
}));
|
||||
}
|
||||
|
||||
const resolveRemainingForKey = (
|
||||
limits: Record<string, unknown>,
|
||||
key: string,
|
||||
usageOverrides: LimitUsageOverrides
|
||||
): number | null => {
|
||||
if (key === 'max_events_per_year') {
|
||||
return toNumber(usageOverrides.remainingEvents ?? limits.remaining_events);
|
||||
}
|
||||
|
||||
const map: Record<string, string> = {
|
||||
max_photos: 'remaining_photos',
|
||||
max_guests: 'remaining_guests',
|
||||
gallery_days: 'remaining_gallery_days',
|
||||
};
|
||||
|
||||
const remainingKey = map[key];
|
||||
if (!remainingKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return toNumber(limits[remainingKey]);
|
||||
};
|
||||
|
||||
export function collectPackageFeatures(pkg: TenantPackageSummary): string[] {
|
||||
const features = new Set<string>();
|
||||
const direct = Array.isArray(pkg.features) ? pkg.features : [];
|
||||
|
||||
Reference in New Issue
Block a user