feat(addons): finalize event addon catalog and ai styling upgrade flow
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-02-07 12:35:07 +01:00
parent 8cc0918881
commit d2808ffa4f
36 changed files with 1372 additions and 457 deletions

View File

@@ -434,6 +434,24 @@ export default function MobileEventControlRoomPage() {
const aiStylingAddon = React.useMemo(() => {
return catalogAddons.find((addon) => addon.price_id && aiStylingAddonKeys.includes(addon.key)) ?? null;
}, [aiStylingAddonKeys, catalogAddons]);
const aiStylingAddonCta = React.useMemo(() => {
if (!aiStylingAddon) {
return t('controlRoom.aiUpsell.cta', 'Unlock AI Magic Edit');
}
if (typeof aiStylingAddon.price !== 'number' || !Number.isFinite(aiStylingAddon.price)) {
return t('controlRoom.aiUpsell.cta', 'Unlock AI Magic Edit');
}
const currency = aiStylingAddon.currency || 'EUR';
try {
const formattedPrice = new Intl.NumberFormat(undefined, { style: 'currency', currency }).format(aiStylingAddon.price);
return t('controlRoom.aiUpsell.ctaWithPrice', 'Unlock AI Magic Edit ({{price}})', { price: formattedPrice });
} catch {
return t('controlRoom.aiUpsell.cta', 'Unlock AI Magic Edit');
}
}, [aiStylingAddon, t]);
const aiSettingsDirty = React.useMemo(
() => isAiSettingsDirty(aiSettingsDraft, initialAiSettingsDraft),
[aiSettingsDraft, initialAiSettingsDraft],
@@ -907,12 +925,13 @@ export default function MobileEventControlRoomPage() {
const params = new URLSearchParams(location.search);
if (params.get('addon_success')) {
toast.success(t('mobileBilling.addonApplied', 'Add-on applied. Limits update shortly.'));
void refetch();
setModerationPage(1);
void loadModeration();
params.delete('addon_success');
navigate({ pathname: location.pathname, search: params.toString() ? `?${params.toString()}` : '' }, { replace: true });
}
}, [location.search, slug, loadModeration, navigate, t, location.pathname]);
}, [location.search, slug, loadModeration, navigate, t, location.pathname, refetch]);
const updateQueueState = React.useCallback((queue: PhotoModerationAction[]) => {
replacePhotoQueue(queue);
@@ -1161,7 +1180,7 @@ export default function MobileEventControlRoomPage() {
cancel_url: currentUrl,
accepted_terms: consents.acceptedTerms,
accepted_waiver: consents.acceptedWaiver,
} as any);
});
if (checkout.checkout_url) {
window.location.href = checkout.checkout_url;
} else {
@@ -1832,6 +1851,27 @@ export default function MobileEventControlRoomPage() {
/>
</XStack>
</MobileCard>
) : aiStylingAddon ? (
<MobileCard gap="$3" borderColor={border} backgroundColor={surface}>
<XStack alignItems="center" gap="$2">
<Sparkles size={18} color={primary} />
<Text fontSize="$md" fontWeight="800" color={textStrong}>
{t('controlRoom.aiUpsell.title', 'Unlock AI Magic Edit')}
</Text>
</XStack>
<Text fontSize="$sm" color={muted}>
{t(
'controlRoom.aiUpsell.body',
'Buy the AI Styling add-on for this event without upgrading your full package.'
)}
</Text>
<CTAButton
label={aiStylingAddonCta}
onPress={() => startAddonCheckout('ai')}
loading={busyScope === 'ai'}
disabled={consentBusy}
/>
</MobileCard>
) : null
) : null}