State lab14 examples
Behavior / Development
State lab
Inspect auction, payment, signup, and launcher states with deterministic data.
Previewlight site theme
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>
);
}