|
|
|
|
@@ -12,7 +12,7 @@ import { MobileColorInput, MobileField, MobileFileInput, MobileInput, MobileSele
|
|
|
|
|
import { TenantEvent, getEvent, updateEvent, getTenantFonts, getTenantSettings, TenantFont, WatermarkSettings, trackOnboarding } from '../api';
|
|
|
|
|
import { isAuthError } from '../auth/tokens';
|
|
|
|
|
import { ApiError, getApiErrorMessage } from '../lib/apiError';
|
|
|
|
|
import { isBrandingAllowed, isWatermarkAllowed } from '../lib/events';
|
|
|
|
|
import { isBrandingAllowed, isWatermarkAllowed, isWatermarkRemovalAllowed } from '../lib/events';
|
|
|
|
|
import { MobileSheet } from './components/Sheet';
|
|
|
|
|
import toast from 'react-hot-toast';
|
|
|
|
|
import { adminPath } from '../constants';
|
|
|
|
|
@@ -188,10 +188,24 @@ export default function MobileBrandingPage() {
|
|
|
|
|
const previewLogoValue = previewForm.logoMode === 'emoticon' ? previewForm.logoValue : '';
|
|
|
|
|
const previewInitials = getInitials(previewTitle);
|
|
|
|
|
const watermarkAllowed = isWatermarkAllowed(event ?? null);
|
|
|
|
|
const watermarkRemovalAllowed = isWatermarkRemovalAllowed(event ?? null);
|
|
|
|
|
const brandingAllowed = isBrandingAllowed(event ?? null);
|
|
|
|
|
const customWatermarkAllowed = watermarkAllowed && brandingAllowed;
|
|
|
|
|
const watermarkLocked = watermarkAllowed && !brandingAllowed;
|
|
|
|
|
const brandingDisabled = !brandingAllowed || form.useDefaultBranding;
|
|
|
|
|
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
|
setWatermarkForm((prev) => {
|
|
|
|
|
if (prev.mode === 'custom' && !customWatermarkAllowed) {
|
|
|
|
|
return { ...prev, mode: 'base' };
|
|
|
|
|
}
|
|
|
|
|
if (prev.mode === 'off' && !watermarkRemovalAllowed) {
|
|
|
|
|
return { ...prev, mode: 'base' };
|
|
|
|
|
}
|
|
|
|
|
return prev;
|
|
|
|
|
});
|
|
|
|
|
}, [customWatermarkAllowed, watermarkRemovalAllowed]);
|
|
|
|
|
|
|
|
|
|
async function handleSave() {
|
|
|
|
|
if (!event?.slug) return;
|
|
|
|
|
setSaving(true);
|
|
|
|
|
@@ -271,7 +285,12 @@ export default function MobileBrandingPage() {
|
|
|
|
|
size: form.logoSize,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
const watermarkPayload = buildWatermarkPayload(watermarkForm, watermarkAllowed, brandingAllowed);
|
|
|
|
|
const watermarkPayload = buildWatermarkPayload(
|
|
|
|
|
watermarkForm,
|
|
|
|
|
watermarkAllowed,
|
|
|
|
|
brandingAllowed,
|
|
|
|
|
watermarkRemovalAllowed
|
|
|
|
|
);
|
|
|
|
|
if (watermarkPayload) {
|
|
|
|
|
settings.watermark = watermarkPayload;
|
|
|
|
|
}
|
|
|
|
|
@@ -307,10 +326,14 @@ export default function MobileBrandingPage() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function renderWatermarkTab() {
|
|
|
|
|
const policyLabel = watermarkAllowed ? 'basic' : 'none';
|
|
|
|
|
const disabled = !watermarkAllowed;
|
|
|
|
|
const controlsLocked = watermarkLocked || disabled;
|
|
|
|
|
const controlsLocked = watermarkLocked;
|
|
|
|
|
const mode = controlsLocked ? 'base' : watermarkForm.mode;
|
|
|
|
|
const resolvedMode = mode === 'custom' && !customWatermarkAllowed
|
|
|
|
|
? 'base'
|
|
|
|
|
: mode === 'off' && !watermarkRemovalAllowed
|
|
|
|
|
? 'base'
|
|
|
|
|
: mode;
|
|
|
|
|
const customizationDisabled = controlsLocked || resolvedMode !== 'custom';
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
@@ -325,12 +348,12 @@ export default function MobileBrandingPage() {
|
|
|
|
|
padding={watermarkForm.padding}
|
|
|
|
|
offsetX={watermarkForm.offsetX}
|
|
|
|
|
offsetY={watermarkForm.offsetY}
|
|
|
|
|
previewUrl={mode === 'off' ? '' : watermarkForm.assetDataUrl || watermarkForm.assetPreviewUrl}
|
|
|
|
|
previewUrl={resolvedMode === 'off' ? '' : watermarkForm.assetDataUrl || watermarkForm.assetPreviewUrl}
|
|
|
|
|
previewAlt={t('events.watermark.previewAlt', 'Watermark preview')}
|
|
|
|
|
/>
|
|
|
|
|
</MobileCard>
|
|
|
|
|
|
|
|
|
|
{disabled ? (
|
|
|
|
|
{!watermarkAllowed ? (
|
|
|
|
|
<UpgradeCard
|
|
|
|
|
title={t('events.watermark.lockedTitle', 'Unlock watermarks')}
|
|
|
|
|
body={t('events.watermark.lockedBody', 'Custom watermarks are available with the Premium package.')}
|
|
|
|
|
@@ -353,7 +376,7 @@ export default function MobileBrandingPage() {
|
|
|
|
|
|
|
|
|
|
<MobileField label={t('events.watermark.mode', 'Modus')}>
|
|
|
|
|
<MobileSelect
|
|
|
|
|
value={mode}
|
|
|
|
|
value={resolvedMode}
|
|
|
|
|
disabled={controlsLocked}
|
|
|
|
|
onChange={(event) => {
|
|
|
|
|
const value = event.target.value;
|
|
|
|
|
@@ -364,16 +387,16 @@ export default function MobileBrandingPage() {
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<option value="base">{t('events.watermark.modeBase', 'Basis')}</option>
|
|
|
|
|
<option value="custom" disabled={watermarkLocked}>
|
|
|
|
|
<option value="custom" disabled={!customWatermarkAllowed}>
|
|
|
|
|
{t('events.watermark.modeCustom', 'Eigenes Wasserzeichen')}
|
|
|
|
|
</option>
|
|
|
|
|
<option value="off" disabled={policyLabel === 'basic'}>
|
|
|
|
|
<option value="off" disabled={!watermarkRemovalAllowed}>
|
|
|
|
|
{t('events.watermark.modeOff', 'Deaktiviert')}
|
|
|
|
|
</option>
|
|
|
|
|
</MobileSelect>
|
|
|
|
|
</MobileField>
|
|
|
|
|
|
|
|
|
|
{mode === 'custom' && !controlsLocked ? (
|
|
|
|
|
{resolvedMode === 'custom' && !controlsLocked ? (
|
|
|
|
|
<YStack space="$2">
|
|
|
|
|
<Text fontSize="$sm" fontWeight="700" color={textStrong}>
|
|
|
|
|
{t('events.watermark.upload', 'Wasserzeichen hochladen')}
|
|
|
|
|
@@ -452,7 +475,7 @@ export default function MobileBrandingPage() {
|
|
|
|
|
<PositionGrid
|
|
|
|
|
value={watermarkForm.position}
|
|
|
|
|
onChange={(next) => setWatermarkForm((prev) => ({ ...prev, position: next }))}
|
|
|
|
|
disabled={controlsLocked}
|
|
|
|
|
disabled={customizationDisabled}
|
|
|
|
|
/>
|
|
|
|
|
<LabeledSlider
|
|
|
|
|
label={t('events.watermark.size', 'Größe')}
|
|
|
|
|
@@ -461,7 +484,7 @@ export default function MobileBrandingPage() {
|
|
|
|
|
max={60}
|
|
|
|
|
step={1}
|
|
|
|
|
onChange={(value) => setWatermarkForm((prev) => ({ ...prev, scale: value / 100 }))}
|
|
|
|
|
disabled={controlsLocked}
|
|
|
|
|
disabled={customizationDisabled}
|
|
|
|
|
/>
|
|
|
|
|
<LabeledSlider
|
|
|
|
|
label={t('events.watermark.opacity', 'Transparenz')}
|
|
|
|
|
@@ -470,7 +493,7 @@ export default function MobileBrandingPage() {
|
|
|
|
|
max={80}
|
|
|
|
|
step={5}
|
|
|
|
|
onChange={(value) => setWatermarkForm((prev) => ({ ...prev, opacity: value / 100 }))}
|
|
|
|
|
disabled={controlsLocked}
|
|
|
|
|
disabled={customizationDisabled}
|
|
|
|
|
/>
|
|
|
|
|
<LabeledSlider
|
|
|
|
|
label={t('events.watermark.padding', 'Abstand zum Rand')}
|
|
|
|
|
@@ -479,7 +502,7 @@ export default function MobileBrandingPage() {
|
|
|
|
|
max={80}
|
|
|
|
|
step={2}
|
|
|
|
|
onChange={(value) => setWatermarkForm((prev) => ({ ...prev, padding: value }))}
|
|
|
|
|
disabled={controlsLocked}
|
|
|
|
|
disabled={customizationDisabled}
|
|
|
|
|
/>
|
|
|
|
|
<LabeledSlider
|
|
|
|
|
label={t('events.watermark.offset', 'Feinjustierung')}
|
|
|
|
|
@@ -488,7 +511,7 @@ export default function MobileBrandingPage() {
|
|
|
|
|
max={30}
|
|
|
|
|
step={1}
|
|
|
|
|
onChange={(value) => setWatermarkForm((prev) => ({ ...prev, offsetX: value }))}
|
|
|
|
|
disabled={controlsLocked}
|
|
|
|
|
disabled={customizationDisabled}
|
|
|
|
|
suffix={t('events.watermark.offsetX', 'X-Achse')}
|
|
|
|
|
/>
|
|
|
|
|
<LabeledSlider
|
|
|
|
|
@@ -498,7 +521,7 @@ export default function MobileBrandingPage() {
|
|
|
|
|
max={30}
|
|
|
|
|
step={1}
|
|
|
|
|
onChange={(value) => setWatermarkForm((prev) => ({ ...prev, offsetY: value }))}
|
|
|
|
|
disabled={controlsLocked}
|
|
|
|
|
disabled={customizationDisabled}
|
|
|
|
|
/>
|
|
|
|
|
</MobileCard>
|
|
|
|
|
</>
|
|
|
|
|
@@ -1095,15 +1118,19 @@ function extractWatermark(event: TenantEvent): WatermarkForm {
|
|
|
|
|
function buildWatermarkPayload(
|
|
|
|
|
form: WatermarkForm,
|
|
|
|
|
watermarkAllowed: boolean,
|
|
|
|
|
brandingAllowed: boolean
|
|
|
|
|
brandingAllowed: boolean,
|
|
|
|
|
removalAllowed: boolean
|
|
|
|
|
): WatermarkSettings | null {
|
|
|
|
|
if (!watermarkAllowed) {
|
|
|
|
|
return { mode: 'off' };
|
|
|
|
|
const customAllowed = watermarkAllowed && brandingAllowed;
|
|
|
|
|
let mode = form.mode;
|
|
|
|
|
|
|
|
|
|
if (mode === 'custom' && !customAllowed) {
|
|
|
|
|
mode = 'base';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const policy = watermarkAllowed ? 'basic' : 'none';
|
|
|
|
|
const desiredMode = brandingAllowed ? form.mode : 'base';
|
|
|
|
|
const mode = desiredMode === 'off' && policy === 'basic' ? 'base' : desiredMode;
|
|
|
|
|
if (mode === 'off' && !removalAllowed) {
|
|
|
|
|
mode = 'base';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const payload: WatermarkSettings = {
|
|
|
|
|
mode,
|
|
|
|
|
@@ -1115,7 +1142,7 @@ function buildWatermarkPayload(
|
|
|
|
|
offset_y: Math.max(-500, Math.min(500, Math.round(form.offsetY))),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (mode === 'custom' && brandingAllowed) {
|
|
|
|
|
if (mode === 'custom' && customAllowed) {
|
|
|
|
|
if (form.assetDataUrl) {
|
|
|
|
|
payload.asset_data_url = form.assetDataUrl;
|
|
|
|
|
}
|
|
|
|
|
|