← Files WixARCHIVED FILE

skills/wix-headless-templates/pricing-plans/app/wix/pricing-plans/plans-store.ts

2.38 KB · Oct 8, 2026 · 12:02 UTC

↓ Download file

See the change to this file →

// The plans listing as a framework-free store — the logic behind usePlans, usable from React
// (usePlans wraps it with useSyncExternalStore), from a static page or Vue/Svelte (subscribe and
// render), or as the specification for a port. State, subscribe/getState, start/stop, emit after
// every change.
//
// SSR-friendly: seed with `initialPlans` and they render at once with no client fetch; without a
// seed `start()` fetches the public plans. A failed load leaves `plans: []` with the message in
// `error`, so the surface renders its empty state and the message, never a skeleton forever.
//
// One store per mounted listing (a pricing page and a home-page strip can coexist): createPlansStore(),
// not a singleton. It imports the data layer by names the REST twin exports identically, so the
// same file runs over the SDK in Astro and over REST on a static page.
import { fetchPlans } from "./plans";
import type { PlanSummary } from "./types";

export interface PlansStoreOptions {
  initialPlans?: PlanSummary[];
}

/** Everything a pricing grid renders from. Read it with getState() or through a subscription. */
export interface PlansState {
  /** null while the first load is in flight — render skeletons, not an empty state. */
  plans: PlanSummary[] | null;
  error: string | null;
}

export interface PlansStore {
  getState(): PlansState;
  subscribe(listener: () => void): () => void;
  /** Fetch the plans when no `initialPlans` were given. Call once when mounted (a browser). */
  start(): void;
  /** Stop reacting; drop a late response. */
  stop(): void;
}

export function createPlansStore({ initialPlans }: PlansStoreOptions = {}): PlansStore {
  let state: PlansState = { plans: initialPlans ?? null, error: null };
  let started = false;
  const listeners = new Set<() => void>();

  function setState(patch: Partial<PlansState>): void {
    state = { ...state, ...patch };
    for (const fn of listeners) fn();
  }

  return {
    getState: () => state,
    subscribe(fn) {
      listeners.add(fn);
      return () => listeners.delete(fn);
    },
    start() {
      if (started) return;
      started = true;
      if (initialPlans) return;
      fetchPlans()
        .then((plans) => { if (started) setState({ plans }); })
        .catch((e) => { if (started) setState({ plans: [], error: e instanceof Error ? e.message : String(e) }); });
    },
    stop() {
      started = false;
    },
  };
}

SHA-256: 62088dfac3843bf79bc3c2d050f495bf7889a4b1160c344e5fcfc92621e3223d