Update partner packages, copy, and demo switcher
This commit is contained in:
@@ -9,7 +9,19 @@ import { MobileShell } from './components/MobileShell';
|
||||
import { MobileCard, CTAButton } from './components/Primitives';
|
||||
import { MobileField, MobileInput, MobileSelect, MobileTextArea } from './components/FormControls';
|
||||
import { LegalConsentSheet } from './components/LegalConsentSheet';
|
||||
import { createEvent, getEvent, updateEvent, getEventTypes, getPackages, Package, TenantEvent, TenantEventType, trackOnboarding } from '../api';
|
||||
import {
|
||||
createEvent,
|
||||
getEvent,
|
||||
updateEvent,
|
||||
getEventTypes,
|
||||
getPackages,
|
||||
getTenantPackagesOverview,
|
||||
Package,
|
||||
TenantEvent,
|
||||
TenantEventType,
|
||||
TenantPackageSummary,
|
||||
trackOnboarding,
|
||||
} from '../api';
|
||||
import { resolveEventSlugAfterUpdate } from './eventFormNavigation';
|
||||
import { adminPath } from '../constants';
|
||||
import { isAuthError } from '../auth/tokens';
|
||||
@@ -30,6 +42,7 @@ type FormState = {
|
||||
autoApproveUploads: boolean;
|
||||
tasksEnabled: boolean;
|
||||
packageId: number | null;
|
||||
servicePackageSlug: string | null;
|
||||
};
|
||||
|
||||
export default function MobileEventFormPage() {
|
||||
@@ -52,11 +65,14 @@ export default function MobileEventFormPage() {
|
||||
autoApproveUploads: true,
|
||||
tasksEnabled: true,
|
||||
packageId: null,
|
||||
servicePackageSlug: null,
|
||||
});
|
||||
const [eventTypes, setEventTypes] = React.useState<TenantEventType[]>([]);
|
||||
const [typesLoading, setTypesLoading] = React.useState(false);
|
||||
const [packages, setPackages] = React.useState<Package[]>([]);
|
||||
const [packagesLoading, setPackagesLoading] = React.useState(false);
|
||||
const [kontingentOptions, setKontingentOptions] = React.useState<Array<{ slug: string; remaining: number }>>([]);
|
||||
const [kontingentLoading, setKontingentLoading] = React.useState(false);
|
||||
const [loading, setLoading] = React.useState(isEdit);
|
||||
const [saving, setSaving] = React.useState(false);
|
||||
const [consentOpen, setConsentOpen] = React.useState(false);
|
||||
@@ -84,6 +100,7 @@ export default function MobileEventFormPage() {
|
||||
(data.settings?.engagement_mode as string | undefined) !== 'photo_only' &&
|
||||
(data.engagement_mode as string | undefined) !== 'photo_only',
|
||||
packageId: null,
|
||||
servicePackageSlug: null,
|
||||
});
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
@@ -139,6 +156,75 @@ export default function MobileEventFormPage() {
|
||||
})();
|
||||
}, [isSuperAdmin, isEdit]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isEdit) {
|
||||
return;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
setKontingentLoading(true);
|
||||
try {
|
||||
const overview = await getTenantPackagesOverview();
|
||||
const packages = overview.packages ?? [];
|
||||
|
||||
const active = packages.filter((pkg) => pkg.active && pkg.package_type === 'reseller');
|
||||
const totals = new Map<string, number>();
|
||||
|
||||
active.forEach((pkg: TenantPackageSummary) => {
|
||||
const slugValue = pkg.included_package_slug ?? 'standard';
|
||||
if (!slugValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
const remaining = Number.isFinite(pkg.remaining_events as number) ? Number(pkg.remaining_events) : 0;
|
||||
if (remaining <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
totals.set(slugValue, (totals.get(slugValue) ?? 0) + remaining);
|
||||
});
|
||||
|
||||
const options = Array.from(totals.entries())
|
||||
.map(([slugValue, remaining]) => ({ slug: slugValue, remaining }))
|
||||
.sort((a, b) => a.slug.localeCompare(b.slug));
|
||||
|
||||
setKontingentOptions(options);
|
||||
setForm((prev) => {
|
||||
if (prev.servicePackageSlug || options.length === 0) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
if (options.length === 1) {
|
||||
return { ...prev, servicePackageSlug: options[0].slug };
|
||||
}
|
||||
|
||||
const standard = options.find((row) => row.slug === 'standard');
|
||||
return { ...prev, servicePackageSlug: standard?.slug ?? options[0].slug };
|
||||
});
|
||||
} catch {
|
||||
setKontingentOptions([]);
|
||||
} finally {
|
||||
setKontingentLoading(false);
|
||||
}
|
||||
})();
|
||||
}, [isEdit]);
|
||||
|
||||
const resolveServiceTierLabel = React.useCallback((slugValue: string) => {
|
||||
if (slugValue === 'starter') {
|
||||
return 'Starter';
|
||||
}
|
||||
|
||||
if (slugValue === 'standard') {
|
||||
return 'Standard';
|
||||
}
|
||||
|
||||
if (slugValue === 'pro') {
|
||||
return 'Premium';
|
||||
}
|
||||
|
||||
return slugValue;
|
||||
}, []);
|
||||
|
||||
async function handleSubmit() {
|
||||
setSaving(true);
|
||||
setError(null);
|
||||
@@ -165,6 +251,7 @@ export default function MobileEventFormPage() {
|
||||
event_date: form.date || undefined,
|
||||
status: form.published ? 'published' : 'draft',
|
||||
package_id: isSuperAdmin ? form.packageId ?? undefined : undefined,
|
||||
service_package_slug: form.servicePackageSlug ?? undefined,
|
||||
settings: {
|
||||
location: form.location,
|
||||
guest_upload_visibility: form.autoApproveUploads ? 'immediate' : 'review',
|
||||
@@ -188,6 +275,7 @@ export default function MobileEventFormPage() {
|
||||
event_date: form.date || undefined,
|
||||
status: form.published ? 'published' : 'draft',
|
||||
package_id: isSuperAdmin ? form.packageId ?? undefined : undefined,
|
||||
service_package_slug: form.servicePackageSlug ?? undefined,
|
||||
settings: {
|
||||
location: form.location,
|
||||
guest_upload_visibility: form.autoApproveUploads ? 'immediate' : 'review',
|
||||
@@ -283,6 +371,34 @@ export default function MobileEventFormPage() {
|
||||
</MobileField>
|
||||
) : null}
|
||||
|
||||
{!isEdit && (kontingentLoading || kontingentOptions.length > 0) ? (
|
||||
<MobileField label={t('eventForm.fields.servicePackage.label', 'Event-Level (Event-Kontingent)')}>
|
||||
{kontingentLoading ? (
|
||||
<Text fontSize="$sm" color={muted}>
|
||||
{t('eventForm.fields.servicePackage.loading', 'Loading Event-Kontingente…')}
|
||||
</Text>
|
||||
) : (
|
||||
<MobileSelect
|
||||
value={form.servicePackageSlug ?? ''}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, servicePackageSlug: String(e.target.value) }))}
|
||||
>
|
||||
<option value="">{t('eventForm.fields.servicePackage.placeholder', 'Select tier')}</option>
|
||||
{kontingentOptions.map((opt) => (
|
||||
<option key={opt.slug} value={opt.slug}>
|
||||
{resolveServiceTierLabel(opt.slug)} · {opt.remaining} {t('eventForm.fields.servicePackage.events', 'Events')}
|
||||
</option>
|
||||
))}
|
||||
</MobileSelect>
|
||||
)}
|
||||
<Text fontSize="$xs" color={muted}>
|
||||
{t(
|
||||
'eventForm.fields.servicePackage.help',
|
||||
'Wählt das Event-Level. Pro Event wird 1 aus dem passenden Event-Kontingent verbraucht.',
|
||||
)}
|
||||
</Text>
|
||||
</MobileField>
|
||||
) : null}
|
||||
|
||||
<MobileField label={t('eventForm.fields.date.label', 'Date & time')}>
|
||||
<XStack alignItems="center" space="$2">
|
||||
<NativeDateTimeInput
|
||||
|
||||
Reference in New Issue
Block a user