@startsimpli/billing 0.4.29 → 0.4.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@startsimpli/billing",
3
- "version": "0.4.29",
3
+ "version": "0.4.30",
4
4
  "description": "Universal billing integration for StartSimpli Next.js apps",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -221,6 +221,19 @@ describe("one purchase at a time (crochet_patterns-j2we6)", () => {
221
221
  });
222
222
  });
223
223
 
224
+ describe("the clicked button spins for a signed-in viewer too (crochet_patterns-j2we6)", () => {
225
+ it("spins Lite while a Free viewer's checkout starts", async () => {
226
+ const onSelectOffer = vi.fn(() => new Promise(() => {}));
227
+ const free = { id: "sub-0", status: "active", offer: product.offers[0] } as unknown as SubscriptionInfo;
228
+ renderPricing({ onSelectOffer, currentPlan: { signedIn: true, subscription: free } });
229
+ await waitFor(() => expect(cta("crochet-free").textContent).toMatch(/current plan/i));
230
+ fireEvent.click(cta("crochet-lite"));
231
+ await waitFor(() => expect(cta("crochet-lite").getAttribute("aria-busy")).toBe("true"));
232
+ expect(cta("crochet-pro").getAttribute("aria-busy")).toBeNull();
233
+ expect(cta("crochet-pro").disabled).toBe(true);
234
+ });
235
+ });
236
+
224
237
  describe("the page names itself (crochet_patterns-ln6hf)", () => {
225
238
  it("uses the host's title and subtitle", async () => {
226
239
  renderPricing({ title: "Pricing", subtitle: "Free to start." });
@@ -75,6 +75,8 @@ export function PricingDetailPage({
75
75
  // A purchase by a viewer with no plan (signed out) skips usePlanChange, so
76
76
  // it is tracked here: the clicked card spins and every other button waits.
77
77
  const [selecting, setSelecting] = React.useState<string | null>(null);
78
+ // The card last clicked, so its button is the one that spins.
79
+ const [clicked, setClicked] = React.useState<string | null>(null);
78
80
 
79
81
  if (loading) {
80
82
  return (
@@ -124,6 +126,7 @@ export function PricingDetailPage({
124
126
  const busy = planChange.busy || hostBusy || selecting !== null;
125
127
  const select = (offer: ProductOffer) => {
126
128
  if (busy || planUnknown) return;
129
+ setClicked(offer.id);
127
130
  if (!currentOffer) {
128
131
  setSelecting(offer.id);
129
132
  void Promise.resolve()
@@ -137,7 +140,7 @@ export function PricingDetailPage({
137
140
  setSelecting(null);
138
141
  void planChange.choose(offer, { paying, leavingUnlisted: onUnlistedPlan, currentOffer });
139
142
  };
140
- const spinningId = busy ? selecting ?? planChange.pending?.offer.id ?? null : null;
143
+ const spinningId = busy ? clicked : null;
141
144
 
142
145
  // What each tier already has, so a card under "Everything in X, plus:"
143
146
  // lists only what it adds (crochet_patterns-tv7fb).