Add event addon purchase success page with confetti
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 17:04:14 +01:00
parent 64b3bf3ed4
commit c0c082975e
15 changed files with 837 additions and 7 deletions

View File

@@ -667,6 +667,13 @@ export type TenantAddonHistoryEntry = {
receipt_url?: string | null;
};
export type EventAddonPurchaseSummary = TenantAddonHistoryEntry & {
checkout_id: string | null;
transaction_id: string | null;
created_at: string | null;
addon_intent?: string | null;
};
export type TenantBillingAddonScope = {
type: 'tenant' | 'event';
event: TenantAddonEventSummary | null;
@@ -1435,6 +1442,18 @@ function normalizeTenantAddonHistoryEntry(entry: JsonValue): TenantAddonHistoryE
};
}
function normalizeEventAddonPurchaseSummary(entry: JsonValue): EventAddonPurchaseSummary {
const base = normalizeTenantAddonHistoryEntry(entry);
return {
...base,
checkout_id: typeof entry.checkout_id === 'string' ? entry.checkout_id : null,
transaction_id: typeof entry.transaction_id === 'string' ? entry.transaction_id : null,
created_at: typeof entry.created_at === 'string' ? entry.created_at : null,
addon_intent: typeof entry.addon_intent === 'string' ? entry.addon_intent : null,
};
}
function normalizeTask(task: JsonValue): TenantTask {
const titleTranslations = normalizeTranslationMap(task.title_translations ?? task.title ?? {});
const descriptionTranslations = normalizeTranslationMap(task.description_translations ?? task.description ?? {});
@@ -2013,6 +2032,46 @@ export async function createEventAddonCheckout(
);
}
export async function getEventAddonPurchase(
eventSlug: string,
options?: {
addonIntent?: string;
checkoutId?: string;
addonKey?: string;
}
): Promise<EventAddonPurchaseSummary | null> {
const params = new URLSearchParams();
if (options?.addonIntent) {
params.set('addon_intent', options.addonIntent);
}
if (options?.checkoutId) {
params.set('checkout_id', options.checkoutId);
}
if (options?.addonKey) {
params.set('addon_key', options.addonKey);
}
const query = params.toString();
const response = await authorizedFetch(
`${eventEndpoint(eventSlug)}/addons/purchase${query ? `?${query}` : ''}`
);
if (response.status === 404) {
return null;
}
const payload = await jsonOrThrow<{ data?: JsonValue }>(response, 'Failed to load add-on purchase');
if (!payload.data || typeof payload.data !== 'object') {
return null;
}
return normalizeEventAddonPurchaseSummary(payload.data);
}
export async function getAddonCatalog(): Promise<EventAddonCatalogItem[]> {
const response = await authorizedFetch('/api/v1/tenant/addons/catalog');
const data = await jsonOrThrow<{ data?: EventAddonCatalogItem[] }>(response, 'Failed to load add-ons');