added watermark settings tab on the branding page and added more package details to the billing page, added a new guest notifications page

This commit is contained in:
Codex Agent
2025-12-17 16:39:25 +01:00
parent efe697f155
commit 5f3e7ae8c8
25 changed files with 2062 additions and 202 deletions

View File

@@ -44,7 +44,8 @@ export default function MobileBillingPage() {
setAddons(addonHistory.data ?? []);
setError(null);
} catch (err) {
setError(getApiErrorMessage(err, t('billing.errors.load', 'Konnte Abrechnung nicht laden.')));
const message = getApiErrorMessage(err, t('billing.errors.load', 'Konnte Abrechnung nicht laden.'));
setError(message);
} finally {
setLoading(false);
}
@@ -206,18 +207,71 @@ function PackageCard({ pkg, label }: { pkg: TenantPackageSummary; label?: string
{expires}
</Text>
) : null}
<XStack space="$2" marginTop="$2">
<XStack space="$2" marginTop="$2" flexWrap="wrap">
<PillBadge tone="muted">
{t('mobileBilling.remainingEvents', '{{count}} events', { count: remaining })}
</PillBadge>
{pkg.price !== null && pkg.price !== undefined ? (
<PillBadge tone="muted">{formatAmount(pkg.price, pkg.currency ?? 'EUR')}</PillBadge>
) : null}
{renderFeatureBadge(pkg, t, 'branding_allowed', t('billing.features.branding', 'Branding'))}
{renderFeatureBadge(pkg, t, 'watermark_allowed', t('billing.features.watermark', 'Watermark'))}
</XStack>
<YStack space="$1.5" marginTop="$2">
<FeatureList pkg={pkg} />
</YStack>
</MobileCard>
);
}
function renderFeatureBadge(pkg: TenantPackageSummary, t: any, key: string, label: string) {
const value = (pkg.package_limits as any)?.[key] ?? (pkg as any)[key];
if (value === undefined || value === null) return null;
const enabled = value !== false;
return <PillBadge tone={enabled ? 'success' : 'muted'}>{enabled ? label : `${label} off`}</PillBadge>;
}
function FeatureList({ pkg }: { pkg: TenantPackageSummary }) {
const { t } = useTranslation('management');
const limits = pkg.package_limits ?? {};
const features = (pkg as any).features as string[] | undefined;
const rows: Array<{ label: string; value: string }> = [];
if (limits.max_photos !== undefined && limits.max_photos !== null) {
rows.push({ label: t('billing.features.maxPhotos', 'Max photos'), value: String(limits.max_photos) });
}
if (limits.max_guests !== undefined && limits.max_guests !== null) {
rows.push({ label: t('billing.features.maxGuests', 'Max guests'), value: String(limits.max_guests) });
}
if (limits.gallery_days !== undefined && limits.gallery_days !== null) {
rows.push({ label: t('billing.features.galleryDays', 'Gallery days'), value: String(limits.gallery_days) });
}
if (limits.max_tasks !== undefined && limits.max_tasks !== null) {
rows.push({ label: t('billing.features.maxTasks', 'Max tasks'), value: String(limits.max_tasks) });
}
if (Array.isArray(features) && features.length) {
rows.push({ label: t('billing.features.featureList', 'Included features'), value: features.join(', ') });
}
if (!rows.length) return null;
return (
<YStack space="$1">
{rows.map((row) => (
<XStack key={row.label} alignItems="center" justifyContent="space-between">
<Text fontSize="$xs" color="#6b7280">
{row.label}
</Text>
<Text fontSize="$xs" color="#0f172a" fontWeight="700">
{row.value}
</Text>
</XStack>
))}
</YStack>
);
}
function formatAmount(value: number | null | undefined, currency: string | null | undefined): string {
if (value === null || value === undefined) {
return '—';