import { type ReactElement } from "react"; import { type PaywallModalProps, type PackageOption, type SubscriptionPlanOption } from "./PaywallModal"; /** * Props for usePaywall. Accepts everything PaywallModal accepts, except the * three props the hook manages itself (open, onClose, onAdd). Callback props * that the hook wraps (onSelectPackage, onSelectPlan) are overridden to also * accept async handlers. Add watchFetch to auto-open on detected credit errors. */ export type UsePaywallProps = Omit & { /** Called when the user picks a legacy preset/custom top-up amount. The * second argument is the promo code entered, if any (see `allowPromoCode`). */ onTopup?: (amount: number, code?: string) => void | Promise; /** Called when the user picks a one-time package (async override). The second * argument is the promo code entered, if any (see `allowPromoCode`). */ onSelectPackage?: (pkg: PackageOption, code?: string) => void | Promise; /** Called when the user picks a subscription plan (async override). */ onSelectPlan?: (plan: SubscriptionPlanOption) => void | Promise; /** * Auto-detect credit errors from any fetch() call and open the paywall * automatically. Defaults to false. * * Set to true only if you are NOT using and want automatic * interception. If your app already wraps with * (the recommended pattern), leave this false — double-patching is harmless * but unnecessary. * * Both HTTP 402 (regular routes) and text/event-stream / ndjson streaming * responses are handled automatically. */ watchFetch?: boolean; }; export interface UsePaywallResult { /** * Open the paywall modal. Calling this is ALWAYS safe — it opens the modal * whether or not it was previously dismissed. Never add a "paywallAlreadyShown" * guard around this: it is idempotent (calling while already open is a no-op) * and every InsufficientCreditsError should call it unconditionally. */ trigger: () => void; /** Close the paywall without a purchase. The next trigger() call will re-open it. */ dismiss: () => void; /** Whether the paywall is currently open. */ isOpen: boolean; /** * Drop this into your JSX tree — it renders the modal when open. * It must be rendered for the paywall to appear; place it near the root of * the component where you call `trigger()`. * * return ( *
* * {paywall} *
* ); */ paywall: ReactElement; /** * Pass directly to useChat / useCompletion as the `onError` prop. Triggers * the paywall automatically when the Vercel AI SDK surfaces a credit error * from a streaming route (the AI SDK captures `fetch` before window.fetch * patching applies, so watchFetch cannot intercept streaming responses). * * const { paywall, onError } = usePaywall({ ... }); * const { messages } = useChat({ api: '/api/generate', onError }); */ onError: (error: Error) => void; } /** * Manages a Crediball paywall modal with a simple, misuse-proof API. * * The key rule: call `trigger()` unconditionally whenever you catch an * InsufficientCreditsError — never guard it with "already shown" state. * Dismissing the modal ("Not now") does NOT prevent the next trigger() from * reopening it. This is what makes the paywall reliably reappear on retry. * * const { trigger, paywall } = usePaywall({ * packages: pkgs, * subscriptionPlans: plans, * activeSubscription: activeSub, * onSelectPackage: async (pkg) => checkout(pkg), * onSelectPlan: async (plan) => subscribe(plan), * onCancelSubscription: async (id) => cancel(id), * }); */ export declare function usePaywall({ onTopup, onSelectPackage, onSelectPlan, watchFetch, ...rest }?: UsePaywallProps): UsePaywallResult; /** * Returns true when an error from the Vercel AI SDK (or any other source) * represents an insufficient-credits rejection from Crediball. Useful when * you need to check the error yourself before calling trigger(). */ export declare function isInsufficientCreditsError(error: unknown): boolean;