Skip to content
Lockerverse SDK
Esc
↑↓navigate↵open⌘Jpreview
State lab14 examples

Behavior / Development

State lab

Inspect auction, payment, signup, and launcher states with deterministic data.

All examples
Previewlight site theme
Component
Server state

Deterministic test data. Select the active state again to replay it.

Loading payment…
CodeApp.tsx
import {
  LockerverseDonateLauncher,
  LockerversePayment,
  type LockerverseTheme,
  useLockerverseWidget,
} from "@lockerverse/react";
import type { AuctionLabState } from "../auction-state-lab-fixtures";
import "@lockerverse/react/styles.css";
import {
  LockerverseSignup,
  useLockerverseSignup,
} from "@lockerverse/react/signup";
import "@lockerverse/react/signup.css";
import { useState } from "react";
import {
  createStateLabFetch,
  type LauncherLabState,
  type PaymentLabState,
  type SignupLabState,
} from "../state-lab-fixtures";
import AuctionStateLabPreview from "./AuctionStateLabPreview";

type Flow = "auction" | "launcher" | "payment" | "signup";

const states = {
  auction: [
    "live",
    "bid-uncertain",
    "purchase-recovery",
    "loading",
    "load-error",
    "upcoming",
    "catalog-live",
    "closed",
    "sold-out",
    "start-boundary",
    "end-boundary",
    "draft-ended",
  ],
  launcher: ["loading", "ready", "empty", "load-error"],
  payment: ["loading", "ready", "quote-error", "changed-price", "recovered"],
  signup: ["loading", "ready", "closed", "submit-error", "success"],
} as const;

const initialState = {
  auction: "live",
  launcher: "ready",
  payment: "ready",
  signup: "ready",
} as const;
const flows: Flow[] = ["launcher", "payment", "signup", "auction"];

function isAuctionState(value: string): value is AuctionLabState {
  return states.auction.some((candidate) => candidate === value);
}

function isLauncherState(value: string): value is LauncherLabState {
  return states.launcher.some((candidate) => candidate === value);
}

function isPaymentState(value: string): value is PaymentLabState {
  return states.payment.some((candidate) => candidate === value);
}

function isSignupState(value: string): value is SignupLabState {
  return states.signup.some((candidate) => candidate === value);
}

const labels = {
  "bid-uncertain": "Unknown bid result",
  "catalog-live": "Live availability",
  "changed-price": "Changed price",
  closed: "Closed",
  "draft-ended": "Draft with past dates",
  empty: "Empty catalog",
  "end-boundary": "Closes soon",
  live: "Live auction",
  "load-error": "Load failure",
  loading: "Loading",
  "purchase-recovery": "Purchase recovery",
  "quote-error": "Quote failure",
  ready: "Ready",
  recovered: "Recovered payment",
  "sold-out": "Sold out",
  "start-boundary": "Starts then closes",
  "submit-error": "Submit failure",
  success: "Submit success",
  upcoming: "Upcoming",
} as const;

const runtime = {
  apiBaseUrl: "https://state-lab.lockerverse.test/api",
  communitySlug: "sdk-state-lab",
  environment: "development" as const,
  sentryDsn: null,
};

const style = { "--lockerverse-accent": "#f1511b" } as const;

const unavailableStripeLoader = async () => null;

function PaymentPreview({
  sharedFields,
  state,
  theme,
}: {
  sharedFields: boolean;
  state: PaymentLabState;
  theme: LockerverseTheme;
}) {
  const widget = useLockerverseWidget({
    ...runtime,
    fetch: createStateLabFetch({ flow: "payment", state }, sharedFields),
    widgetSlug: "payment",
  });
  return (
    <LockerversePayment
      {...(state === "recovered"
        ? {
            resumePayment: {
              connectedAccountId: null,
              paymentReference: "00000000-0000-4000-8000-000000000701",
            },
          }
        : {})}
      selection={[{ productSlug: "adult", quantity: 1 }]}
      stripeLoader={unavailableStripeLoader}
      style={style}
      theme={theme}
      widget={widget}
    />
  );
}

function SignupPreview({
  sharedFields,
  state,
  theme,
}: {
  sharedFields: boolean;
  state: SignupLabState;
  theme: LockerverseTheme;
}) {
  const widget = useLockerverseSignup({
    ...runtime,
    fetch: createStateLabFetch({ flow: "signup", state }, sharedFields),
    signupSlug: "signup",
  });
  return <LockerverseSignup style={style} theme={theme} widget={widget} />;
}

function LauncherPreview({
  state,
  theme,
}: {
  state: LauncherLabState;
  theme: LockerverseTheme;
}) {
  const widget = useLockerverseWidget({
    ...runtime,
    fetch: createStateLabFetch({ flow: "launcher", state }),
    widgetSlug: "launcher",
  });
  return (
    <LockerverseDonateLauncher
      buttonLabel="Open donate launcher"
      position="inline"
      stripeLoader={unavailableStripeLoader}
      style={style}
      theme={theme}
      widget={widget}
    />
  );
}

export default function StateLabExample({
  theme = "light",
}: {
  theme?: LockerverseTheme;
}) {
  const [sharedFields, setSharedFields] = useState(false);
  const [flow, setFlow] = useState<Flow>("payment");
  const [selected, setSelected] = useState<Record<Flow, string>>(initialState);
  const [revision, setRevision] = useState(0);
  const [auctionLocked, setAuctionLocked] = useState(false);
  const state = selected[flow];

  const selectState = (nextState: string) => {
    setSelected((current) => ({ ...current, [flow]: nextState }));
    setRevision((current) => current + 1);
  };

  return (
    <div className="state-lab">
      <div className="state-lab__controls">
        <fieldset>
          <legend>Component</legend>
          <div className="state-lab__options">
            {flows.map((name) => (
              <button
                aria-pressed={flow === name}
                disabled={auctionLocked}
                key={name}
                onClick={() => setFlow(name)}
                type="button"
              >
                {name}
              </button>
            ))}
          </div>
        </fieldset>
        <fieldset>
          <legend>Server state</legend>
          <div className="state-lab__options state-lab__options--states">
            {states[flow].map((name) => (
              <button
                aria-pressed={state === name}
                disabled={auctionLocked}
                key={name}
                onClick={() => selectState(name)}
                type="button"
              >
                {labels[name]}
              </button>
            ))}
          </div>
        </fieldset>
        {flow === "payment" || flow === "signup" ? (
          <label>
            <input
              checked={sharedFields}
              onChange={(event) => {
                setSharedFields(event.target.checked);
                setRevision((current) => current + 1);
              }}
              type="checkbox"
            />{" "}
            Show shared phone and custom fields
          </label>
        ) : null}
        <p>
          Deterministic test data. Select the active state again to replay it.
        </p>
      </div>
      <div className="state-lab__preview" data-flow={flow}>
        {flow === "auction" && isAuctionState(state) ? (
          <AuctionStateLabPreview
            key={`${flow}-${state}-${revision}`}
            onLockChange={setAuctionLocked}
            onReset={() => {
              setAuctionLocked(false);
              setRevision((current) => current + 1);
            }}
            state={state}
            theme={theme}
          />
        ) : null}
        {flow === "payment" && isPaymentState(state) ? (
          <PaymentPreview
            key={`${flow}-${state}-${revision}`}
            sharedFields={sharedFields}
            state={state}
            theme={theme}
          />
        ) : null}
        {flow === "signup" && isSignupState(state) ? (
          <SignupPreview
            key={`${flow}-${state}-${revision}`}
            sharedFields={sharedFields}
            state={state}
            theme={theme}
          />
        ) : null}
        {flow === "launcher" && isLauncherState(state) ? (
          <LauncherPreview
            key={`${flow}-${state}-${revision}`}
            state={state}
            theme={theme}
          />
        ) : null}
      </div>
    </div>
  );
}