Refine branding labels and access checks
This commit is contained in:
@@ -1925,10 +1925,10 @@
|
|||||||
"surface": "Fläche",
|
"surface": "Fläche",
|
||||||
"lockedBranding": "Branding ist in diesem Paket gesperrt.",
|
"lockedBranding": "Branding ist in diesem Paket gesperrt.",
|
||||||
"source": "Branding-Quelle",
|
"source": "Branding-Quelle",
|
||||||
"sourceHint": "Nutze das Tenant-Branding oder überschreibe es für dieses Event.",
|
"sourceHint": "Nutze das Standard-Branding oder passe nur dieses Event an.",
|
||||||
"useDefault": "Tenant",
|
"useDefault": "Standard",
|
||||||
"useCustom": "Event",
|
"useCustom": "Dieses Event",
|
||||||
"usingDefault": "Tenant-Branding aktiv",
|
"usingDefault": "Standard-Branding aktiv",
|
||||||
"usingCustom": "Event-Branding aktiv",
|
"usingCustom": "Event-Branding aktiv",
|
||||||
"mode": "Theme",
|
"mode": "Theme",
|
||||||
"modeLight": "Hell",
|
"modeLight": "Hell",
|
||||||
|
|||||||
@@ -1929,10 +1929,10 @@
|
|||||||
"surface": "Surface",
|
"surface": "Surface",
|
||||||
"lockedBranding": "Branding is locked for this package.",
|
"lockedBranding": "Branding is locked for this package.",
|
||||||
"source": "Branding source",
|
"source": "Branding source",
|
||||||
"sourceHint": "Use tenant branding or override for this event.",
|
"sourceHint": "Use the default branding or customize this event only.",
|
||||||
"useDefault": "Tenant",
|
"useDefault": "Default",
|
||||||
"useCustom": "Event",
|
"useCustom": "This event",
|
||||||
"usingDefault": "Tenant branding active",
|
"usingDefault": "Default branding active",
|
||||||
"usingCustom": "Event branding active",
|
"usingCustom": "Event branding active",
|
||||||
"mode": "Theme",
|
"mode": "Theme",
|
||||||
"modeLight": "Light",
|
"modeLight": "Light",
|
||||||
|
|||||||
29
resources/js/admin/lib/__tests__/events.test.ts
Normal file
29
resources/js/admin/lib/__tests__/events.test.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { isBrandingAllowed, isWatermarkAllowed } from '../events';
|
||||||
|
|
||||||
|
describe('event branding access helpers', () => {
|
||||||
|
it('respects package-level disallow', () => {
|
||||||
|
const event = {
|
||||||
|
settings: { branding_allowed: true, watermark_allowed: true },
|
||||||
|
package: { branding_allowed: false, watermark_allowed: false },
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(isBrandingAllowed(event as any)).toBe(false);
|
||||||
|
expect(isWatermarkAllowed(event as any)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses settings when package allows', () => {
|
||||||
|
const event = {
|
||||||
|
settings: { branding_allowed: false, watermark_allowed: true },
|
||||||
|
package: { branding_allowed: true, watermark_allowed: true },
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(isBrandingAllowed(event as any)).toBe(false);
|
||||||
|
expect(isWatermarkAllowed(event as any)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('defaults to allow when nothing is set', () => {
|
||||||
|
expect(isBrandingAllowed({} as any)).toBe(true);
|
||||||
|
expect(isWatermarkAllowed({} as any)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -92,7 +92,28 @@ export function resolveEngagementMode(event?: TenantEvent | null): 'tasks' | 'ph
|
|||||||
|
|
||||||
export function isBrandingAllowed(event?: TenantEvent | null): boolean {
|
export function isBrandingAllowed(event?: TenantEvent | null): boolean {
|
||||||
if (!event) return true;
|
if (!event) return true;
|
||||||
return Boolean((event.package as any)?.branding_allowed ?? true);
|
const settings = (event.settings ?? {}) as Record<string, unknown>;
|
||||||
|
const packageAllowed = (event.package as any)?.branding_allowed;
|
||||||
|
if (packageAllowed === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (typeof settings.branding_allowed === 'boolean') {
|
||||||
|
return settings.branding_allowed;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isWatermarkAllowed(event?: TenantEvent | null): boolean {
|
||||||
|
if (!event) return true;
|
||||||
|
const settings = (event.settings ?? {}) as Record<string, unknown>;
|
||||||
|
const packageAllowed = (event.package as any)?.watermark_allowed;
|
||||||
|
if (packageAllowed === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (typeof settings.watermark_allowed === 'boolean') {
|
||||||
|
return settings.watermark_allowed;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatEventStatusLabel(
|
export function formatEventStatusLabel(
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { MobileCard, CTAButton, SkeletonCard } from './components/Primitives';
|
|||||||
import { TenantEvent, getEvent, updateEvent, getTenantFonts, getTenantSettings, TenantFont, WatermarkSettings, trackOnboarding } from '../api';
|
import { TenantEvent, getEvent, updateEvent, getTenantFonts, getTenantSettings, TenantFont, WatermarkSettings, trackOnboarding } from '../api';
|
||||||
import { isAuthError } from '../auth/tokens';
|
import { isAuthError } from '../auth/tokens';
|
||||||
import { ApiError, getApiErrorMessage } from '../lib/apiError';
|
import { ApiError, getApiErrorMessage } from '../lib/apiError';
|
||||||
import { isBrandingAllowed } from '../lib/events';
|
import { isBrandingAllowed, isWatermarkAllowed } from '../lib/events';
|
||||||
import { MobileSheet } from './components/Sheet';
|
import { MobileSheet } from './components/Sheet';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { adminPath } from '../constants';
|
import { adminPath } from '../constants';
|
||||||
@@ -179,7 +179,7 @@ export default function MobileBrandingPage() {
|
|||||||
const previewLogoUrl = previewForm.logoMode === 'upload' ? previewForm.logoDataUrl : '';
|
const previewLogoUrl = previewForm.logoMode === 'upload' ? previewForm.logoDataUrl : '';
|
||||||
const previewLogoValue = previewForm.logoMode === 'emoticon' ? previewForm.logoValue : '';
|
const previewLogoValue = previewForm.logoMode === 'emoticon' ? previewForm.logoValue : '';
|
||||||
const previewInitials = getInitials(previewTitle);
|
const previewInitials = getInitials(previewTitle);
|
||||||
const watermarkAllowed = event?.package?.watermark_allowed !== false;
|
const watermarkAllowed = isWatermarkAllowed(event ?? null);
|
||||||
const brandingAllowed = isBrandingAllowed(event ?? null);
|
const brandingAllowed = isBrandingAllowed(event ?? null);
|
||||||
const watermarkLocked = watermarkAllowed && !brandingAllowed;
|
const watermarkLocked = watermarkAllowed && !brandingAllowed;
|
||||||
const brandingDisabled = !brandingAllowed || form.useDefaultBranding;
|
const brandingDisabled = !brandingAllowed || form.useDefaultBranding;
|
||||||
@@ -616,17 +616,17 @@ export default function MobileBrandingPage() {
|
|||||||
{t('events.branding.source', 'Branding Source')}
|
{t('events.branding.source', 'Branding Source')}
|
||||||
</Text>
|
</Text>
|
||||||
<Text fontSize="$sm" color={muted}>
|
<Text fontSize="$sm" color={muted}>
|
||||||
{t('events.branding.sourceHint', 'Nutze das Tenant-Branding oder überschreibe es für dieses Event.')}
|
{t('events.branding.sourceHint', 'Use the default branding or customize this event only.')}
|
||||||
</Text>
|
</Text>
|
||||||
<XStack space="$2">
|
<XStack space="$2">
|
||||||
<ModeButton
|
<ModeButton
|
||||||
label={t('events.branding.useDefault', 'Tenant')}
|
label={t('events.branding.useDefault', 'Default')}
|
||||||
active={form.useDefaultBranding}
|
active={form.useDefaultBranding}
|
||||||
onPress={() => setForm((prev) => ({ ...prev, useDefaultBranding: true }))}
|
onPress={() => setForm((prev) => ({ ...prev, useDefaultBranding: true }))}
|
||||||
disabled={!brandingAllowed}
|
disabled={!brandingAllowed}
|
||||||
/>
|
/>
|
||||||
<ModeButton
|
<ModeButton
|
||||||
label={t('events.branding.useCustom', 'Event')}
|
label={t('events.branding.useCustom', 'This event')}
|
||||||
active={!form.useDefaultBranding}
|
active={!form.useDefaultBranding}
|
||||||
onPress={() => setForm((prev) => ({ ...prev, useDefaultBranding: false }))}
|
onPress={() => setForm((prev) => ({ ...prev, useDefaultBranding: false }))}
|
||||||
disabled={!brandingAllowed}
|
disabled={!brandingAllowed}
|
||||||
@@ -1275,7 +1275,7 @@ function LabeledSlider({
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
suffix?: string;
|
suffix?: string;
|
||||||
}) {
|
}) {
|
||||||
const { textStrong, muted } = useAdminTheme();
|
const { textStrong, muted, primary } = useAdminTheme();
|
||||||
return (
|
return (
|
||||||
<YStack space="$1.5">
|
<YStack space="$1.5">
|
||||||
<XStack alignItems="center" justifyContent="space-between">
|
<XStack alignItems="center" justifyContent="space-between">
|
||||||
@@ -1295,7 +1295,7 @@ function LabeledSlider({
|
|||||||
value={value}
|
value={value}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onChange={(event) => onChange(Number(event.target.value))}
|
onChange={(event) => onChange(Number(event.target.value))}
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%', height: 28, accentColor: primary }}
|
||||||
/>
|
/>
|
||||||
</YStack>
|
</YStack>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user