Skip to main content

Queue Widget

The Queue Widget provides a complete virtual waiting room experience with position display, estimated wait times, and enter/leave actions.

Web Component

<fanfare-queue-widget queue-id="queue_123" show-header="true" show-actions="true" show-estimated-wait="true" />

Attributes

AttributeTypeDefaultDescription
queue-idstringRequiredThe queue identifier
show-headerstring"true"Show title and description
show-actionsstring"true"Show enter/leave buttons
show-estimated-waitstring"true"Show estimated wait time
container-classstring-Custom CSS class for container

Events

EventDetailDescription
fanfare-queue-enter{ queueId: string }User entered queue
fanfare-queue-leave{ queueId: string }User left queue
fanfare-queue-position-change{ queueId, position: number }Position updated
fanfare-queue-admitted{ queueId, token: string }User was admitted

Event Handling Example

import { useEffect, useRef } from "react";

function QueuePage() {
  const widgetRef = useRef<HTMLElement>(null);

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

    const handleEnter = (e: CustomEvent) => {
      console.log("Entered queue:", e.detail.queueId);
      analytics.track("queue_entered", { queueId: e.detail.queueId });
    };

    const handlePositionChange = (e: CustomEvent) => {
      console.log("New position:", e.detail.position);
    };

    const handleAdmitted = (e: CustomEvent) => {
      console.log("Admitted with token:", e.detail.token);
      window.location.href = `/checkout?token=${e.detail.token}`;
    };

    widget.addEventListener("fanfare-queue-enter", handleEnter);
    widget.addEventListener("fanfare-queue-position-change", handlePositionChange);
    widget.addEventListener("fanfare-queue-admitted", handleAdmitted);

    return () => {
      widget.removeEventListener("fanfare-queue-enter", handleEnter);
      widget.removeEventListener("fanfare-queue-position-change", handlePositionChange);
      widget.removeEventListener("fanfare-queue-admitted", handleAdmitted);
    };
  }, []);

  return (
    <div className="queue-container">
      <h1>Join Our Queue</h1>
      <fanfare-queue-widget ref={widgetRef} queue-id="queue_123" />
    </div>
  );
}

Queue Status States

The widget handles these queue states automatically:
StatusDescriptionUI Display
enterableQueue is open, user not in queueEnter button
queuedUser is in queuePosition + Leave button
admittedUser has been admittedSuccess message + CTA
errorError occurredError message + Retry

Styling

CSS Variables

Override these variables to customize the queue widget:
fanfare-queue-widget {
  --fanfare-primary: #3b82f6;
  --fanfare-primary-hover: #2563eb;
  --fanfare-background: #ffffff;
  --fanfare-foreground: #1f2937;
  --fanfare-muted: #f3f4f6;
  --fanfare-border: #e5e7eb;
  --fanfare-success: #22c55e;
  --fanfare-radius: 0.5rem;
}

Container Styling

<fanfare-queue-widget
  queue-id="queue_123"
  container-class="my-queue-widget"
/>

<style>
.my-queue-widget {
  max-width: 400px;
  margin: 0 auto;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
</style>

Redirect on Admission

function QueueWithRedirect() {
  const widgetRef = useRef<HTMLElement>(null);

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

    const handleAdmitted = (e: CustomEvent<{ token: string }>) => {
      const { token } = e.detail;

      // Option 1: Redirect to checkout
      window.location.href = `/checkout?token=${token}`;

      // Option 2: Navigate with React Router
      // navigate(`/checkout?token=${token}`);
    };

    widget.addEventListener("fanfare-queue-admitted", handleAdmitted);
    return () => widget.removeEventListener("fanfare-queue-admitted", handleAdmitted);
  }, []);

  return <fanfare-queue-widget ref={widgetRef} queue-id="queue_123" />;
}

Using with React Hook (Alternative)

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

function CustomQueueUI() {
  const { queue, status, position, estimatedWait, enter, leave, isLoading } = useQueue("queue_123");

  if (status === "admitted") {
    return (
      <div className="admitted">
        <h2>You are in!</h2>
        <a href="/checkout">Continue to Checkout</a>
      </div>
    );
  }

  return (
    <div className="queue-ui">
      {status === "queued" ? (
        <>
          <p>Position: {position}</p>
          {estimatedWait && <p>Estimated wait: {estimatedWait} minutes</p>}
          <button onClick={leave}>Leave Queue</button>
        </>
      ) : (
        <button onClick={enter} disabled={isLoading}>
          {isLoading ? "Joining..." : "Enter Queue"}
        </button>
      )}
    </div>
  );
}

TypeScript Declaration

declare namespace JSX {
  interface IntrinsicElements {
    "fanfare-queue-widget": React.DetailedHTMLProps<
      React.HTMLAttributes<HTMLElement> & {
        "queue-id": string;
        "show-header"?: string;
        "show-actions"?: string;
        "show-estimated-wait"?: string;
        "container-class"?: string;
      },
      HTMLElement
    >;
  }
}