Skip to main content

Experience Widget

The Experience Widget is a top-level orchestrator that manages the full consumer journey flow, automatically routing to appropriate views based on journey and sequence stages.

Web Component

<fanfare-experience-widget experience-id="exp_123" auto-start="true" checkout-url="/checkout" />

Attributes

AttributeTypeDefaultDescription
experience-idstringRequiredThe experience identifier
auto-startstring"false"Automatically start journey on mount
access-codestring-Pre-fill access code for VIP access
auto-enter-waitliststring"false"Auto-join waitlist if upcoming
checkout-urlstring-URL to redirect on admission
container-classstring-Custom CSS class for container

Events

EventDetailDescription
fanfare-journey-change{ snapshot: JourneySnapshot }Journey state changed
fanfare-admitted{ token: string }User was admitted
fanfare-error{ error: string }Error occurred

Event Handling Example

import { useEffect, useRef } from "react";

function ExperiencePage({ experienceId }: { experienceId: string }) {
  const widgetRef = useRef<HTMLElement>(null);

  useEffect(() => {
    const widget = widgetRef.current;
    if (!widget) return;

    const handleJourneyChange = (e: CustomEvent) => {
      const { snapshot } = e.detail;
      console.log("Journey stage:", snapshot.journeyStage);
      console.log("Sequence stage:", snapshot.sequenceStage);

      // Track analytics
      analytics.track("journey_stage_change", {
        journeyStage: snapshot.journeyStage,
        sequenceStage: snapshot.sequenceStage,
      });
    };

    const handleAdmitted = (e: CustomEvent<{ token: string }>) => {
      console.log("Admitted with token:", e.detail.token);
      // Widget will auto-redirect if checkout-url is set
      // Or handle manually:
      window.location.href = `/checkout?token=${e.detail.token}`;
    };

    const handleError = (e: CustomEvent<{ error: string }>) => {
      console.error("Journey error:", e.detail.error);
      showErrorToast(e.detail.error);
    };

    widget.addEventListener("fanfare-journey-change", handleJourneyChange);
    widget.addEventListener("fanfare-admitted", handleAdmitted);
    widget.addEventListener("fanfare-error", handleError);

    return () => {
      widget.removeEventListener("fanfare-journey-change", handleJourneyChange);
      widget.removeEventListener("fanfare-admitted", handleAdmitted);
      widget.removeEventListener("fanfare-error", handleError);
    };
  }, []);

  return (
    <div className="experience-container">
      <fanfare-experience-widget
        ref={widgetRef}
        experience-id={experienceId}
        auto-start="true"
        checkout-url="/checkout"
      />
    </div>
  );
}

Journey Stages

The widget automatically handles all journey stages:

Journey Stages

StageDescriptionWidget View
not_startedJourney not yet startedStart button
enteringStarting the journeyLoading spinner
needs_authAuthentication requiredAuth form
needs_access_codeAccess code requiredAccess code input
routingFinding appropriate sequenceLoading spinner
routedRouted to a sequenceSequence-specific view
errorError occurredError message + retry

Sequence Stages

StageDescriptionWidget View
upcomingSequence not yet openCountdown + Waitlist CTA
waitlist_enteredOn the waitlistWaitlist confirmation
active_enterableCan enter distributionEnter button
participatingActively participatingDistribution-specific UI
admittedUser was admittedSuccess + Checkout CTA
admission_expiredAdmission window expiredRe-enter option
endedSequence has endedEnded message

VIP Access Code Flow

function VIPExperience({ experienceId }: { experienceId: string }) {
  const [accessCode, setAccessCode] = useState("");
  const [submitted, setSubmitted] = useState(false);

  if (!submitted) {
    return (
      <div className="access-code-entry">
        <h2>VIP Access</h2>
        <input
          type="text"
          value={accessCode}
          onChange={(e) => setAccessCode(e.target.value)}
          placeholder="Enter your access code"
        />
        <button onClick={() => setSubmitted(true)}>Continue</button>
      </div>
    );
  }

  return (
    <fanfare-experience-widget
      experience-id={experienceId}
      access-code={accessCode}
      auto-start="true"
      checkout-url="/checkout"
    />
  );
}

Auto-Start with Waitlist

// Automatically start and join waitlist for upcoming experiences
<fanfare-experience-widget experience-id="exp_123" auto-start="true" auto-enter-waitlist="true" />

Styling

CSS Variables

fanfare-experience-widget {
  --fanfare-primary: #3b82f6;
  --fanfare-primary-hover: #2563eb;
  --fanfare-background: #ffffff;
  --fanfare-foreground: #1f2937;
  --fanfare-muted: #f3f4f6;
  --fanfare-border: #e5e7eb;
  --fanfare-success: #22c55e;
  --fanfare-destructive: #ef4444;
  --fanfare-radius: 0.5rem;
}

Using with React Hook (Alternative)

For more control, use the useExperienceJourney hook:
import { useExperienceJourney, useFanfareAuth } from "@waitify-io/fanfare-sdk-react";

function CustomExperienceUI({ experienceId }: { experienceId: string }) {
  const { isAuthenticated, guest } = useFanfareAuth();
  const { journey, state, status, error, start } = useExperienceJourney(experienceId);

  const handleStart = async () => {
    if (!isAuthenticated) {
      await guest();
    }
    await start();
  };

  if (error) {
    return (
      <div className="error">
        <p>{error}</p>
        <button onClick={() => start()}>Try Again</button>
      </div>
    );
  }

  switch (status) {
    case "idle":
      return <button onClick={handleStart}>Start Experience</button>;

    case "entering_experience":
    case "routing_sequence":
      return <LoadingSpinner />;

    case "needs_authentication":
      return <AuthForm onComplete={() => journey?.authenticate()} />;

    case "needs_access_code":
      return <AccessCodeForm onSubmit={(code) => journey?.provideAccessCode(code)} />;

    case "waiting":
      return <WaitlistView onJoin={() => journey?.enterWaitlist()} />;

    case "ready":
      return <ParticipationView journey={journey!} state={state!} />;

    default:
      return null;
  }
}

TypeScript Declaration

declare namespace JSX {
  interface IntrinsicElements {
    "fanfare-experience-widget": React.DetailedHTMLProps<
      React.HTMLAttributes<HTMLElement> & {
        "experience-id": string;
        "auto-start"?: string;
        "access-code"?: string;
        "auto-enter-waitlist"?: string;
        "checkout-url"?: string;
        "container-class"?: string;
      },
      HTMLElement
    >;
  }
}