Update admin PWA events, branding, and packages
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-19 11:35:38 +01:00
parent 926bc7d070
commit fbff2afa3e
43 changed files with 6846 additions and 6323 deletions

View File

@@ -74,6 +74,7 @@ type WatermarkForm = {
mode: 'base' | 'custom' | 'off';
assetPath: string;
assetDataUrl: string;
assetPreviewUrl: string;
position: WatermarkPosition;
opacity: number;
scale: number;
@@ -97,6 +98,7 @@ export default function MobileBrandingPage() {
mode: 'base',
assetPath: '',
assetDataUrl: '',
assetPreviewUrl: '',
position: 'bottom-right',
opacity: 0.25,
scale: 0.2,
@@ -192,13 +194,6 @@ export default function MobileBrandingPage() {
async function handleSave() {
if (!event?.slug) return;
const eventTypeId = event.event_type_id ?? event.event_type?.id ?? null;
if (!eventTypeId) {
const msg = t('events.errors.missingType', 'Event type fehlt. Speichere das Event erneut im Admin.');
setError(msg);
toast.error(msg);
return;
}
setSaving(true);
setError(null);
try {
@@ -210,14 +205,6 @@ export default function MobileBrandingPage() {
return;
}
const payload = {
name: typeof event.name === 'string' ? event.name : renderName(event.name),
slug: event.slug,
event_type_id: eventTypeId,
event_date: event.event_date ?? undefined,
status: event.status ?? 'draft',
is_active: event.is_active ?? undefined,
};
const settings = { ...(event.settings ?? {}) };
const logoUploadValue = form.logoMode === 'upload' ? form.logoDataUrl.trim() : '';
const logoIsDataUrl = logoUploadValue.startsWith('data:image/');
@@ -288,11 +275,9 @@ export default function MobileBrandingPage() {
if (watermarkPayload) {
settings.watermark = watermarkPayload;
}
const updated = await updateEvent(event.slug, {
...payload,
settings,
});
const updated = await updateEvent(event.slug, { settings });
setEvent(updated);
setWatermarkForm(extractWatermark(updated));
void trackOnboarding('branding_configured', { event_id: updated.id });
toast.success(t('events.branding.saveSuccess', 'Branding gespeichert'));
} catch (err) {
@@ -340,6 +325,8 @@ export default function MobileBrandingPage() {
padding={watermarkForm.padding}
offsetX={watermarkForm.offsetX}
offsetY={watermarkForm.offsetY}
previewUrl={mode === 'off' ? '' : watermarkForm.assetDataUrl || watermarkForm.assetPreviewUrl}
previewAlt={t('events.watermark.previewAlt', 'Watermark preview')}
/>
</MobileCard>
@@ -404,12 +391,29 @@ export default function MobileBrandingPage() {
>
<UploadCloud size={18} color={primary} />
<Text fontSize="$sm" color={primary} fontWeight="700">
{watermarkForm.assetPath
{watermarkForm.assetPath || watermarkForm.assetDataUrl
? t('events.watermark.replace', 'Wasserzeichen ersetzen')
: t('events.watermark.uploadCta', 'PNG/SVG/JPG (max. 3 MB)')}
</Text>
</XStack>
</Pressable>
{watermarkForm.assetDataUrl ? (
<YStack
borderRadius={12}
borderWidth={1}
borderColor={border}
backgroundColor={surfaceMuted}
padding="$2"
alignItems="center"
justifyContent="center"
>
<img
src={watermarkForm.assetDataUrl}
alt={t('events.watermark.previewAlt', 'Wasserzeichen Vorschau')}
style={{ maxHeight: 120, maxWidth: '100%', objectFit: 'contain' }}
/>
</YStack>
) : null}
<MobileFileInput
id="watermark-upload-input"
accept="image/png,image/jpeg,image/webp,image/svg+xml"
@@ -423,7 +427,12 @@ export default function MobileBrandingPage() {
const reader = new FileReader();
reader.onload = () => {
const result = typeof reader.result === 'string' ? reader.result : '';
setWatermarkForm((prev) => ({ ...prev, assetDataUrl: result, assetPath: '' }));
setWatermarkForm((prev) => ({
...prev,
assetDataUrl: result,
assetPreviewUrl: result,
assetPath: '',
}));
setError(null);
};
reader.readAsDataURL(file);
@@ -1073,6 +1082,7 @@ function extractWatermark(event: TenantEvent): WatermarkForm {
mode,
assetPath: readString('asset', ''),
assetDataUrl: '',
assetPreviewUrl: readString('asset_url', ''),
position,
opacity: readNumber('opacity', 0.25),
scale: readNumber('scale', 0.2),
@@ -1339,6 +1349,8 @@ function WatermarkPreview({
padding,
offsetX,
offsetY,
previewUrl,
previewAlt,
}: {
position: WatermarkPosition;
scale: number;
@@ -1346,6 +1358,8 @@ function WatermarkPreview({
padding: number;
offsetX: number;
offsetY: number;
previewUrl?: string;
previewAlt?: string;
}) {
const { border, muted, textStrong, overlay } = useAdminTheme();
const width = 280;
@@ -1406,7 +1420,7 @@ function WatermarkPreview({
top: y,
width: wmWidth,
height: wmHeight,
background: 'rgba(255,255,255,0.8)',
background: previewUrl ? 'transparent' : 'rgba(255,255,255,0.8)',
borderRadius: 10,
display: 'flex',
alignItems: 'center',
@@ -1415,7 +1429,15 @@ function WatermarkPreview({
border: `1px dashed ${muted}`,
}}
>
<Droplets size={18} color={textStrong} />
{previewUrl ? (
<img
src={previewUrl}
alt={previewAlt ?? 'Watermark'}
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
/>
) : (
<Droplets size={18} color={textStrong} />
)}
</div>
</div>
</div>
@@ -1486,7 +1508,7 @@ function UpgradeCard({
}
function TabButton({ label, active, onPress }: { label: string; active: boolean; onPress: () => void }) {
const { backdrop, surfaceMuted, border, surface } = useAdminTheme();
const { primary, surfaceMuted, border, surface, textStrong } = useAdminTheme();
return (
<Pressable onPress={onPress} style={{ flex: 1 }}>
<XStack
@@ -1494,11 +1516,11 @@ function TabButton({ label, active, onPress }: { label: string; active: boolean;
justifyContent="center"
paddingVertical="$2.5"
borderRadius={12}
backgroundColor={active ? backdrop : surfaceMuted}
backgroundColor={active ? primary : surfaceMuted}
borderWidth={1}
borderColor={active ? backdrop : border}
borderColor={active ? primary : border}
>
<Text fontSize="$sm" color={active ? surface : backdrop} fontWeight="700">
<Text fontSize="$sm" color={active ? surface : textStrong} fontWeight="700">
{label}
</Text>
</XStack>
@@ -1517,7 +1539,7 @@ function ModeButton({
onPress: () => void;
disabled?: boolean;
}) {
const { backdrop, surfaceMuted, border, surface } = useAdminTheme();
const { primary, surfaceMuted, border, surface, textStrong } = useAdminTheme();
return (
<Pressable onPress={onPress} disabled={disabled} style={{ flex: 1, opacity: disabled ? 0.6 : 1 }}>
<XStack
@@ -1525,11 +1547,11 @@ function ModeButton({
justifyContent="center"
paddingVertical="$2"
borderRadius={10}
backgroundColor={active ? backdrop : surfaceMuted}
backgroundColor={active ? primary : surfaceMuted}
borderWidth={1}
borderColor={active ? backdrop : border}
borderColor={active ? primary : border}
>
<Text fontSize="$xs" color={active ? surface : backdrop} fontWeight="700">
<Text fontSize="$xs" color={active ? surface : textStrong} fontWeight="700">
{label}
</Text>
</XStack>