Update admin PWA events, branding, and packages
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user