import type { BillingCheckoutStatus } from "@opengeni/sdk"; import { useEffect, useState } from "react"; import { toast } from "../../lib/toast"; import { CelebrationBurst } from "../onboarding/celebration-burst"; import { CreditsPrize } from "../onboarding/credits-prize"; import { Button } from "../../../ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "../../../ui/dialog"; import { waitForCheckoutCredits, type CreditCheckoutClient } from "../../lib/credit-checkout"; import { formatCreditAmount } from "../../lib/onboarding-use-case"; import { notifyCreditBalanceChanged } from "../../lib/credit-balance-events"; /** "You got $100 in free credits" for a coupon, "You added $25 in credits" for a purchase. */ export function checkoutCreditsHeading(status: Pick): string { const amount = formatCreditAmount(status.credit.amountMicros, status.credit.currency); return status.credit.free ? `You got ${amount} in free credits` : `You added ${amount} in credits`; } /** * After a same-tab Stripe return to organization billing: wait for that * checkout's credits to reach the balance, then celebrate them with the real * amount, the same prize and confetti as onboarding. */ export function CheckoutCreditsCelebration({ client, accountId, checkoutSessionId, }: { client: CreditCheckoutClient; accountId: string; checkoutSessionId: string; }) { const [granted, setGranted] = useState(null); const [open, setOpen] = useState(false); useEffect(() => { const controller = new AbortController(); const toastId = `checkout-credits-${checkoutSessionId}`; toast.loading("Adding your credits…", { id: toastId }); void waitForCheckoutCredits(client, { accountId, checkoutSessionId, signal: controller.signal }) .then((status) => { if (controller.signal.aborted) return; toast.dismiss(toastId); if (status.credit.state === "granted") { notifyCreditBalanceChanged(accountId); setGranted(status); setOpen(true); } else { toast("Checkout expired", { description: "No charge was made." }); } }) .catch(() => toast.dismiss(toastId)); return () => { controller.abort(); toast.dismiss(toastId); }; }, [accountId, checkoutSessionId, client]); if (!granted) return null; return ; } /** The credits celebration as a dialog: confetti, the prize with the real amount, and Done. */ export function CreditsCelebrationDialog({ status, open, onOpenChange, }: { status: BillingCheckoutStatus; open: boolean; onOpenChange: (open: boolean) => void; }) { return ( <> {open ? : null} {checkoutCreditsHeading(status)} Ready to use. Pick a model to start chatting. ); }