"use client"; /** * PurchaseSuccessModal — demo-only post-purchase confirmation. * * Mounted on the canvas root (`app/page.tsx`). On first paint it inspects * `?purchase_success=1[&item=]` on the current URL. If present, it * renders a centred modal styled after `ConfirmDialog`, schedules a 5s * auto-dismiss, and rewrites the URL via `history.replaceState` to drop * the params so a refresh after dismiss does NOT re-show the modal. * * Mock for the funding demo — there is no real billing surface behind * this. The marketplace "Purchase" button on the landing page redirects * here with the params; this modal is the only thing the user sees of * the "transaction". * * Styling matches the warm-paper @theme tokens (surface-sunken / line / * ink / good) so it tracks light + dark without per-mode overrides. */ import { useEffect, useRef, useState } from "react"; import { createPortal } from "react-dom"; const AUTO_DISMISS_MS = 5000; function readPurchaseParams(): { open: boolean; item: string | null } { if (typeof window === "undefined") return { open: false, item: null }; const sp = new URLSearchParams(window.location.search); const flag = sp.get("purchase_success"); if (flag !== "1" && flag !== "true") return { open: false, item: null }; return { open: true, item: sp.get("item") }; } function stripPurchaseParams() { if (typeof window === "undefined") return; const url = new URL(window.location.href); url.searchParams.delete("purchase_success"); url.searchParams.delete("item"); // replaceState (not pushState) so back-button doesn't return to the // pre-strip URL and re-trigger the modal. window.history.replaceState({}, "", url.toString()); } export function PurchaseSuccessModal() { const [open, setOpen] = useState(false); const [item, setItem] = useState(null); const [mounted, setMounted] = useState(false); const dialogRef = useRef(null); // Read the URL params once on mount. We don't subscribe to navigation — // this modal is a one-shot for the demo redirect, not a persistent // listener. useEffect(() => { setMounted(true); const { open: shouldOpen, item: itemName } = readPurchaseParams(); if (shouldOpen) { setOpen(true); setItem(itemName); // Clean the URL immediately so a refresh after the modal is closed // (or even while it's still open) does NOT re-trigger it. stripPurchaseParams(); } }, []); // Auto-dismiss timer + Escape handler. useEffect(() => { if (!open) return; const t = window.setTimeout(() => setOpen(false), AUTO_DISMISS_MS); const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") setOpen(false); }; window.addEventListener("keydown", onKey); // Focus the close button so keyboard users land on it after redirect. const raf = requestAnimationFrame(() => { dialogRef.current?.querySelector("button")?.focus(); }); return () => { window.clearTimeout(t); window.removeEventListener("keydown", onKey); cancelAnimationFrame(raf); }; }, [open]); if (!open || !mounted) return null; const itemLabel = item ? decodeURIComponent(item) : "Your new agent"; return createPortal(
{/* Backdrop — click closes, matches ConfirmDialog backdrop. */}
setOpen(false)} aria-hidden="true" />
{/* Success glyph — uses --color-good so it tracks the theme. Inline SVG over an emoji so it stays readable + on-brand in both light and dark. */}

Purchase successful

{itemLabel} has been added to your workspace. Provisioning starts in the background — you can keep working while it spins up.

auto-dismiss · {AUTO_DISMISS_MS / 1000}s
, document.body, ); }