import { BotMitigationState, RoutingChallenge } from '@fanfare-io/fanfare-sdk-core/challenges'; import { SDKEvents } from '@fanfare-io/fanfare-sdk-core/events'; import { CheckoutNextAction, FanfareCheckoutResult, JourneyHandle, JourneySnapshot, JourneyView, PaymentInput, PaymentMethodSummary, SequenceView } from '@fanfare-io/fanfare-sdk-core/experiences'; import { BrandTheme } from '@fanfare-io/fanfare-sdk-core/theme'; import { JSX } from 'solid-js'; import { SelectionCheckoutDetails } from '../../lib/selection-display'; import { WidgetVariant } from '../../theme'; import { AuthInputMode } from '../auth/auth-input'; import { OutcomeAction } from '../compositions/outcome-panel'; type ParticipationType = "queue" | "draw" | "auction" | "timed_release" | "appointment"; export type EnterableSlotSequence = Extract; type ParticipatingSlotSequence = Extract; type GrantedSlotSequence = Extract; /** How a granted win settles: who consumes the grant, and whether money has already moved for it. */ type GrantedSettlement = GrantedSlotSequence["settlement"]; export interface SlotProps { snapshot: JourneySnapshot | null; view: JourneyView | null; error: string | null; } export interface StartSlotProps extends SlotProps { onStart: () => void; isStarting: boolean; } export interface AuthSlotProps extends SlotProps { onSubmit: (credential: string, type: AuthInputMode, options?: { marketingSmsConsent: boolean; marketingSmsConsentText?: string; }) => Promise; onVerify: (otp: string) => Promise; onSkip?: () => void; /** * Whether the sign-in should offer the optional marketing SMS panel. The widget sets it because its * `onSubmit` reads the consent from its third argument; pass it to `AuthForm` with `onSubmit`. */ showMarketingSmsConsent?: boolean; /** Merchant identity to pass to AuthForm beside showMarketingSmsConsent. */ marketingSmsMerchantName?: string; } export interface AccessCodeSlotProps extends SlotProps { onSubmit: (code: string) => Promise; onSkip?: () => void; } export interface UpcomingSlotProps extends SlotProps { startsAt?: Date; canEnter: boolean; onEnter: () => void; isEntering: boolean; } export interface WaitlistSlotProps extends SlotProps { startsAt?: Date; onLeave: () => void; isLeaving: boolean; } export interface EnterableSlotProps extends SlotProps { sequence: EnterableSlotSequence; participationType: ParticipationType; } export interface ParticipatingSlotProps extends SlotProps { sequence: ParticipatingSlotSequence; participationType: ParticipationType; position?: number; } export interface GrantedSlotProps extends SlotProps { sequence: GrantedSlotSequence; grant?: string; expiresAt?: number; } interface PaymentCollectionBaseSlotProps extends SlotProps { savedPaymentMethods: PaymentMethodSummary[]; /** Submits pre-auth entry or confirms at-end checkout; rejects when the panel should keep recovery UI mounted. */ onEnter: (paymentInput: PaymentInput) => Promise; isEntering: boolean; /** * Client configuration for the internal processor, as the server supplied it. Carried by the * enterable pre-auth and granted at-end arms; an ended retry holds no configuration. */ paymentProvider?: PaymentProviderConfig; /** Present while the internal processor is waiting on an out-of-band action. */ pendingAction?: CheckoutNextAction; /** * Re-reads the consumer's journeys from the server so a provider configuration that was missing * or unreadable at render can arrive. This is the panel's only recovery when `paymentProvider` is * absent — the value is a prop, so nothing the panel does to its own state can produce it. */ onRetryPaymentConfig?: () => Promise; } export type PaymentCollectionSlotProps = PaymentCollectionBaseSlotProps & ({ intent: "checkout"; onResume: () => Promise; /** Grant expiry as a ms epoch: the deadline the claim window counts down to. */ expiresAt?: number; /** * True while the deadline is the server's alone, so the claim window holds its last value * instead of counting a limit the SDK is no longer enforcing. */ expiryPaused: boolean; /** * Present while the pick being paid for may still change. Calling it withdraws the payment * form and opens the picker on the held pair; the payment step returns once the consumer * confirms a pair or backs out. */ onChangeSelection?: () => void; } | { intent: "enter" | "reenter"; preAuthAmount: string; preAuthCurrencyCode: string; onResume?: never; onChangeSelection?: never; }); /** * The processor's client configuration block, named through an arm that carries it: core names the * shape only within those arms, not as a standalone export. */ type PaymentProviderConfig = NonNullable["paymentProvider"]>; export type CheckoutProcessingSlotProps = SlotProps; /** * A Fanfare-managed payment is terminal: it produces a receipt, never an admission credential. The * `grant` arm carries the credential the mechanism itself issued alongside the payment; a purchase * that ends the journey has no credential to carry, so the arms are discriminated rather than * making the grant optional — a slot implementation can never read one that does not exist. */ export type ReceiptSlotProps = SlotProps & ({ kind: "grant"; admissionGrant: string; checkout: FanfareCheckoutResult; } | { kind: "purchase"; checkout: FanfareCheckoutResult; }); export interface ExpiredSlotProps extends SlotProps { reason: string; endedAt?: Date; } export interface EndedSlotProps extends SlotProps { reason: string; endedAt?: Date; } export interface ErrorSlotProps extends SlotProps { error: string; /** Absent when the refusal cannot be cleared by retrying — render no retry affordance. */ onRetry?: () => void; } export interface LoadingSlotProps extends SlotProps { /** Stage-specific message (e.g. routing). `undefined` for generic loading — show just a spinner. */ message?: string; } export interface ChallengeSlotProps extends SlotProps { challenge: RoutingChallenge | null; botMitigation?: BotMitigationState; onVerifyToken: (token: string) => Promise; onRetry: () => void; } export interface ExperienceWidgetSlots { start?: (props: StartSlotProps) => JSX.Element; loading?: (props: LoadingSlotProps) => JSX.Element; auth?: (props: AuthSlotProps) => JSX.Element; accessCode?: (props: AccessCodeSlotProps) => JSX.Element; challenge?: (props: ChallengeSlotProps) => JSX.Element; upcoming?: (props: UpcomingSlotProps) => JSX.Element; waitlist?: (props: WaitlistSlotProps) => JSX.Element; enterable?: (props: EnterableSlotProps) => JSX.Element; participating?: (props: ParticipatingSlotProps) => JSX.Element; granted?: (props: GrantedSlotProps) => JSX.Element; paymentCollection?: (props: PaymentCollectionSlotProps) => JSX.Element; /** Rendered by the at-end checkout surface while a collected payment is settling. */ checkoutProcessing?: (props: CheckoutProcessingSlotProps) => JSX.Element; receipt?: (props: ReceiptSlotProps) => JSX.Element; expired?: (props: ExpiredSlotProps) => JSX.Element; ended?: (props: EndedSlotProps) => JSX.Element; error?: (props: ErrorSlotProps) => JSX.Element; } /** * Surface the widget is mounted on, named through the bus payload that carries it: core * re-exports the event map but not the contract's own name for this shape. */ type BeaconPageType = SDKEvents["experience:widget-mounted"]["pageType"]; export interface ExperienceRenderProps { journey: JourneyHandle | null; view: JourneyView | null; snapshot: JourneySnapshot | null; error: string | null; start: () => Promise; isStarting: boolean; } export interface ExperienceWidgetProps { experienceId: string; autoStart?: boolean; /** * Whether the widget fetches the Google Fonts stylesheets for the families its theme names. * Off unless asked: an embed's page owns its own font delivery and its own content policy. * The fetch never blocks rendering and a failure is silent. */ loadFonts?: boolean; /** * Whether the SDK may enter the consumer on arrival at a distribution the merchant set to enter * automatically. Omit to keep the default (`true`); pass `false` to keep every entry explicit. */ automaticEntry?: boolean; accessCode?: string; autoEnterWaitlist?: boolean; theme?: BrandTheme; variant?: WidgetVariant; slots?: ExperienceWidgetSlots; /** * Surface this widget renders on, carried on the `experience:widget-mounted` announcement. * Defaults to `embed`, the merchant-page case; Fanfare-hosted drop pages pass `hosted`. */ pageType?: BeaconPageType; children?: (props: ExperienceRenderProps) => JSX.Element; checkoutUrl?: string; /** * Called when the consumer activates the granted checkout CTA, with the pick the grant settled * on and the catalogue objects behind it whenever the widget has loaded them. Takes precedence * over `checkoutUrl`. A rejected promise returns the consumer to the CTA with generic inline * copy, so a hand-off that never happened is not reported as started. * When a checkout pick was recorded before the panel mounted, the CTA stays disabled until its * catalogue details have loaded and a failed load offers a retry in its place, so the callback * runs inside the click, with the details, and keeps the click's user activation. */ onCheckoutClick?: (details?: SelectionCheckoutDetails) => void | Promise; /** * Action offered on terminal panels (not selected, sold out, closed), such as * a link back to what is still available. Omit to show none. */ endedAction?: OutcomeAction; onJourneyChange?: (snapshot: JourneySnapshot) => void; onGranted?: (grant: string) => void; /** Called when a Fanfare-managed checkout completes */ onFanfareCheckout?: (result: FanfareCheckoutResult) => void; onError?: (error: Error) => void; /** * Horizontal placement of the widget inside the host's container. The widget is start-aligned * unless this is `"center"`. */ align?: "start" | "center"; /** * Explicit override of the widget's adaptive maximum width. Omit to keep the value of * the `--ff-widget-max-width` custom property. */ size?: "compact" | "regular" | "wide"; class?: string; className?: string; } export declare function ExperienceWidget(props: ExperienceWidgetProps): JSX.Element; export declare namespace ExperienceWidget { var displayName: string; } export {};