Expand package limit and feature details
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled

This commit is contained in:
Codex Agent
2026-01-06 13:20:19 +01:00
parent 4fe589f0e2
commit 10fbee4e6e
6 changed files with 263 additions and 7 deletions

View File

@@ -1,11 +1,20 @@
import { describe, expect, it } from 'vitest';
import { formatPackageLimit, getPackageFeatureLabel } from './packageSummary';
import {
collectPackageFeatures,
formatEventUsage,
formatPackageLimit,
getPackageFeatureLabel,
getPackageLimitEntries,
} from './packageSummary';
const t = (key: string, options?: Record<string, unknown> | string) => {
if (typeof options === 'string') {
return options;
}
return (options?.defaultValue as string | undefined) ?? key;
const template = (options?.defaultValue as string | undefined) ?? key;
return template
.replace('{{used}}', String(options?.used ?? '{{used}}'))
.replace('{{limit}}', String(options?.limit ?? '{{limit}}'));
};
describe('packageSummary helpers', () => {
@@ -24,4 +33,28 @@ describe('packageSummary helpers', () => {
it('formats numeric package limits', () => {
expect(formatPackageLimit(12, t)).toBe('12');
});
it('collects features from package and limit payloads', () => {
const result = collectPackageFeatures({
features: ['custom_branding'],
package_limits: { features: ['reseller_dashboard'] },
branding_allowed: true,
watermark_allowed: false,
} as any);
expect(result).toEqual(expect.arrayContaining(['custom_branding', 'reseller_dashboard', 'branding_allowed']));
});
it('returns labeled limit entries', () => {
const result = getPackageLimitEntries({ max_photos: 120 }, t);
expect(result[0].label).toBe('Photos');
expect(result[0].value).toBe('120');
});
it('formats event usage copy', () => {
const result = formatEventUsage(3, 10, t);
expect(result).toBe('3 of 10 events created');
});
});

View File

@@ -1,3 +1,5 @@
import type { TenantPackageSummary } from '../../api';
type Translate = (key: string, options?: Record<string, unknown> | string) => string;
const FEATURE_LABELS: Record<string, { key: string; fallback: string }> = {
@@ -5,14 +7,42 @@ const FEATURE_LABELS: Record<string, { key: string; fallback: string }> = {
key: 'mobileDashboard.packageSummary.feature.priority_support',
fallback: 'Priority support',
},
reseller_dashboard: {
key: 'mobileDashboard.packageSummary.feature.reseller_dashboard',
fallback: 'Reseller dashboard',
},
custom_domain: {
key: 'mobileDashboard.packageSummary.feature.custom_domain',
fallback: 'Custom domain',
},
custom_branding: {
key: 'mobileDashboard.packageSummary.feature.custom_branding',
fallback: 'Custom branding',
},
custom_tasks: {
key: 'mobileDashboard.packageSummary.feature.custom_tasks',
fallback: 'Custom tasks',
},
unlimited_sharing: {
key: 'mobileDashboard.packageSummary.feature.unlimited_sharing',
fallback: 'Unlimited sharing',
},
analytics: {
key: 'mobileDashboard.packageSummary.feature.analytics',
fallback: 'Analytics',
},
advanced_reporting: {
key: 'mobileDashboard.packageSummary.feature.advanced_reporting',
fallback: 'Advanced reporting',
},
live_slideshow: {
key: 'mobileDashboard.packageSummary.feature.live_slideshow',
fallback: 'Live slideshow',
},
basic_uploads: {
key: 'mobileDashboard.packageSummary.feature.basic_uploads',
fallback: 'Guest uploads',
},
team_management: {
key: 'mobileDashboard.packageSummary.feature.team_management',
fallback: 'Team management',
@@ -47,6 +77,40 @@ const FEATURE_LABELS: Record<string, { key: string; fallback: string }> = {
},
};
const LIMIT_LABELS: Array<{ key: string; labelKey: string; fallback: string }> = [
{
key: 'max_photos',
labelKey: 'packageLimits.max_photos',
fallback: 'Photos',
},
{
key: 'max_guests',
labelKey: 'packageLimits.max_guests',
fallback: 'Guests',
},
{
key: 'max_tasks',
labelKey: 'packageLimits.max_tasks',
fallback: 'Tasks',
},
{
key: 'gallery_days',
labelKey: 'packageLimits.gallery_days',
fallback: 'Gallery days',
},
{
key: 'max_events_per_year',
labelKey: 'packageLimits.max_events_per_year',
fallback: 'Events per year',
},
];
export type PackageLimitEntry = {
key: string;
label: string;
value: string;
};
export function getPackageFeatureLabel(feature: string, t: Translate): string {
const entry = FEATURE_LABELS[feature];
if (entry) {
@@ -63,3 +127,51 @@ export function formatPackageLimit(value: number | null | undefined, t: Translat
return String(value);
}
export function getPackageLimitEntries(limits: Record<string, unknown> | null, t: Translate): PackageLimitEntry[] {
if (!limits) {
return [];
}
return LIMIT_LABELS.map(({ key, labelKey, fallback }) => ({
key,
label: t(labelKey, fallback),
value: formatPackageLimit((limits as Record<string, number | null>)[key], t),
}));
}
export function collectPackageFeatures(pkg: TenantPackageSummary): string[] {
const features = new Set<string>();
const direct = Array.isArray(pkg.features) ? pkg.features : [];
const limitFeatures = Array.isArray((pkg.package_limits as any)?.features) ? (pkg.package_limits as any).features : [];
direct.forEach((feature) => {
if (typeof feature === 'string' && feature.trim()) {
features.add(feature);
}
});
limitFeatures.forEach((feature: unknown) => {
if (typeof feature === 'string' && feature.trim()) {
features.add(feature);
}
});
if (pkg.branding_allowed) {
features.add('branding_allowed');
}
if (pkg.watermark_allowed) {
features.add('watermark_allowed');
}
return Array.from(features);
}
export function formatEventUsage(used: number | null, limit: number | null, t: Translate): string | null {
if (limit === null || used === null) {
return null;
}
return t('mobileBilling.eventsCreated', { used, limit, defaultValue: '{{used}} of {{limit}} events created' });
}