feat(addons): finalize event addon catalog and ai styling upgrade flow
This commit is contained in:
@@ -14,10 +14,12 @@ import {
|
||||
getTenantPackagesOverview,
|
||||
getTenantPackageCheckoutStatus,
|
||||
getEvent,
|
||||
getAddonCatalog,
|
||||
TenantPackageSummary,
|
||||
TenantEvent,
|
||||
TenantBillingTransactionSummary,
|
||||
EventAddonSummary,
|
||||
EventAddonCatalogItem,
|
||||
PaginationMeta,
|
||||
downloadTenantBillingReceipt,
|
||||
} from '../api';
|
||||
@@ -76,6 +78,7 @@ export default function MobileBillingPage() {
|
||||
);
|
||||
const [transactionsLoadingMore, setTransactionsLoadingMore] = React.useState(false);
|
||||
const [addons, setAddons] = React.useState<TenantAddonHistoryEntry[]>([]);
|
||||
const [catalogAddons, setCatalogAddons] = React.useState<EventAddonCatalogItem[]>([]);
|
||||
const [addonsMeta, setAddonsMeta] = React.useState<PaginationMeta>(() =>
|
||||
createInitialPaginationMeta(BILLING_HISTORY_PAGE_SIZE)
|
||||
);
|
||||
@@ -109,7 +112,7 @@ export default function MobileBillingPage() {
|
||||
const load = React.useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const [pkg, trx, addonHistory] = await Promise.all([
|
||||
const [pkg, trx, addonHistory, addonCatalog] = await Promise.all([
|
||||
getTenantPackagesOverview({ force: true }),
|
||||
getTenantBillingTransactions(1, BILLING_HISTORY_PAGE_SIZE).catch(() => ({
|
||||
data: [] as TenantBillingTransactionSummary[],
|
||||
@@ -119,6 +122,7 @@ export default function MobileBillingPage() {
|
||||
data: [] as TenantAddonHistoryEntry[],
|
||||
meta: createInitialPaginationMeta(BILLING_HISTORY_PAGE_SIZE),
|
||||
})),
|
||||
getAddonCatalog().catch(() => [] as EventAddonCatalogItem[]),
|
||||
]);
|
||||
|
||||
let scopedEvent: TenantEvent | null = null;
|
||||
@@ -153,6 +157,7 @@ export default function MobileBillingPage() {
|
||||
setTransactions(trx.data ?? []);
|
||||
setTransactionsMeta(trx.meta ?? createInitialPaginationMeta(BILLING_HISTORY_PAGE_SIZE));
|
||||
setAddons(addonHistory.data ?? []);
|
||||
setCatalogAddons(addonCatalog);
|
||||
setAddonsMeta(addonHistory.meta ?? createInitialPaginationMeta(BILLING_HISTORY_PAGE_SIZE));
|
||||
setScopeEvent(scopedEvent);
|
||||
setScopeAddons(scopedAddons);
|
||||
@@ -195,6 +200,9 @@ export default function MobileBillingPage() {
|
||||
}, [scopeEvent, t]);
|
||||
const scopedEventPath = scopeEvent?.slug ? ADMIN_EVENT_VIEW_PATH(scopeEvent.slug) : null;
|
||||
const activeEventId = scopeEvent?.id ?? activeEvent?.id ?? null;
|
||||
const hasSellableAddons = React.useMemo(() => catalogAddons.some((addon) => Boolean(addon.price_id)), [catalogAddons]);
|
||||
const eventRecapPath = scopeEvent?.slug ? adminPath(`/mobile/events/${scopeEvent.slug}/recap`) : null;
|
||||
const eventControlRoomPath = scopeEvent?.slug ? adminPath(`/mobile/events/${scopeEvent.slug}/control-room`) : null;
|
||||
const scopedEventPackage = scopeEvent?.package ?? null;
|
||||
const scopedEventAddons = React.useMemo<EventAddonSummary[]>(() => {
|
||||
const rows = scopeEvent?.addons;
|
||||
@@ -689,6 +697,25 @@ export default function MobileBillingPage() {
|
||||
{t('billing.sections.currentEvent.noAddons', 'No add-ons purchased for this event.')}
|
||||
</Text>
|
||||
)}
|
||||
{scopeEvent?.slug ? (
|
||||
hasSellableAddons ? (
|
||||
<YStack gap="$2" marginTop="$1">
|
||||
<CTAButton
|
||||
label={t('billing.sections.currentEvent.openAddonShop', 'Buy add-ons for this event')}
|
||||
onPress={() => navigate(eventRecapPath ?? adminPath(`/mobile/events/${scopeEvent.slug}/recap`))}
|
||||
/>
|
||||
<CTAButton
|
||||
label={t('billing.sections.currentEvent.openAddonUpsell', 'Open control-room upgrades')}
|
||||
tone="ghost"
|
||||
onPress={() => navigate(eventControlRoomPath ?? adminPath(`/mobile/events/${scopeEvent.slug}/control-room`))}
|
||||
/>
|
||||
</YStack>
|
||||
) : (
|
||||
<Text fontSize="$sm" color={muted}>
|
||||
{t('billing.sections.currentEvent.noAddonCatalog', 'No add-ons are currently available for purchase.')}
|
||||
</Text>
|
||||
)
|
||||
) : null}
|
||||
</YStack>
|
||||
</YStack>
|
||||
)}
|
||||
@@ -862,9 +889,17 @@ export default function MobileBillingPage() {
|
||||
{t('common.loading', 'Lädt...')}
|
||||
</Text>
|
||||
) : addons.length === 0 ? (
|
||||
<Text fontSize="$sm" color={text}>
|
||||
{t('billing.sections.addOns.empty', 'Keine Add-ons gebucht.')}
|
||||
</Text>
|
||||
<YStack gap="$2">
|
||||
<Text fontSize="$sm" color={text}>
|
||||
{t('billing.sections.addOns.empty', 'Keine Add-ons gebucht.')}
|
||||
</Text>
|
||||
{scopeEvent?.slug && hasSellableAddons ? (
|
||||
<CTAButton
|
||||
label={t('billing.sections.addOns.openShop', 'Buy add-ons now')}
|
||||
onPress={() => navigate(eventRecapPath ?? adminPath(`/mobile/events/${scopeEvent.slug}/recap`))}
|
||||
/>
|
||||
) : null}
|
||||
</YStack>
|
||||
) : (
|
||||
<YStack gap="$1.5">
|
||||
{addons.map((addon) => (
|
||||
|
||||
Reference in New Issue
Block a user