@zuplo/zudoku-plugin-monetization 0.0.44 → 0.0.46

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/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { D as formatDurationAdjective, E as formatDuration, O as formatDurationInterval, T as formatPrice, _ as formatPlanPrice, a as planHasDefaultTaxBehavior, b as sameEntitlementSet, c as getPlanPriceSchedule, d as PlanEntitlements, f as PlanPhaseHeader, l as PlanPriceTag, o as subscriptionTaxLegendSentence, p as EntitlementList, r as isCustomPlan, t as PricingTable, u as PlanPriceSchedule, w as formatMinorCurrencyAmount, x as categorizeRateCards, y as comparePlanEntitlements } from "./PricingTable-WkG2n7V-.mjs";
2
2
  import { cn, createPlugin, joinUrl, throwIfProblemJson } from "zudoku";
3
- import { AlertTriangleIcon, ArrowDownIcon, ArrowLeftRightIcon, ArrowUpIcon, CalendarIcon, CheckCheckIcon, CheckIcon, ChevronDownIcon, CircleSlashIcon, ClockIcon, CreditCardIcon, Grid2x2XIcon, InfoIcon, Loader2Icon, LockIcon, MoreVerticalIcon, RefreshCcw, RefreshCwIcon, Settings, ShieldIcon, StarsIcon, Trash2Icon, XIcon } from "zudoku/icons";
4
- import { Link, Outlet, useLocation, useNavigate, useSearchParams } from "zudoku/router";
3
+ import { AlertTriangleIcon, ArrowDownIcon, ArrowLeftRightIcon, ArrowUpIcon, BadgePercentIcon, CalendarIcon, CheckCheckIcon, CheckIcon, ChevronDownIcon, CircleSlashIcon, ClockIcon, CreditCardIcon, Grid2x2XIcon, HistoryIcon, InfoIcon, Loader2Icon, LockIcon, MoreVerticalIcon, RefreshCcw, RefreshCwIcon, Settings, ShieldIcon, StarsIcon, Trash2Icon, XIcon } from "zudoku/icons";
4
+ import { Link, Navigate, Outlet, useLocation, useNavigate, useSearchParams } from "zudoku/router";
5
5
  import { useAuth, useZudoku } from "zudoku/hooks";
6
6
  import { QueryClient, QueryClientProvider, queryOptions, useMutation, useQuery, useQueryClient, useSuspenseQuery } from "zudoku/react-query";
7
7
  import { Button, ClientOnly, Head, Heading, Link as Link$1, Slot } from "zudoku/components";
@@ -98,7 +98,8 @@ const queryClient = new QueryClient({ defaultOptions: {
98
98
  ...q.meta?.request?.headers
99
99
  }
100
100
  });
101
- const response = await fetch(q.meta?.context ? await q.meta.context.signRequest(request) : request);
101
+ const queryContext = q.meta?.context;
102
+ const response = await fetch(queryContext?.getAuthState().isAuthenticated ? await queryContext.signRequest(request) : request);
102
103
  await throwIfProblemJson(response);
103
104
  if (!response.ok) throw new Error("Failed to fetch request");
104
105
  return response.json();
@@ -128,7 +129,8 @@ const queryClient = new QueryClient({ defaultOptions: {
128
129
  ...init.headers
129
130
  }
130
131
  });
131
- const response = await fetch(m.meta?.context ? await m.meta.context.signRequest(request) : request);
132
+ const mutationContext = m.meta?.context;
133
+ const response = await fetch(mutationContext?.getAuthState().isAuthenticated ? await mutationContext.signRequest(request) : request);
132
134
  await throwIfProblemJson(response);
133
135
  if (!response.ok) {
134
136
  const errorText = await response.text();
@@ -451,17 +453,13 @@ const useUrlUtils = () => {
451
453
  };
452
454
  //#endregion
453
455
  //#region src/pages/CheckoutPage.tsx
454
- const CheckoutPage = () => {
455
- const [searchParams] = useSearchParams();
456
- const planId = searchParams.get("planId");
456
+ const CheckoutRedirect = ({ planId }) => {
457
457
  const zudoku = useZudoku();
458
458
  const auth = useAuth();
459
459
  const { generateUrl } = useUrlUtils();
460
- const deploymentName = useDeploymentName();
461
- if (!planId) throw new Error(`missing planId in URL`);
462
460
  const checkoutLink = useQuery({
463
461
  queryKey: [
464
- `/v3/zudoku-metering/${deploymentName}/stripe/checkout`,
462
+ `/v3/zudoku-metering/${useDeploymentName()}/stripe/checkout`,
465
463
  planId,
466
464
  auth.profile?.sub
467
465
  ],
@@ -499,6 +497,15 @@ const CheckoutPage = () => {
499
497
  })
500
498
  });
501
499
  };
500
+ const CheckoutPage = () => {
501
+ const [searchParams] = useSearchParams();
502
+ const planId = searchParams.get("planId");
503
+ if (!planId) return /* @__PURE__ */ jsx(Navigate, {
504
+ to: "/pricing",
505
+ replace: true
506
+ });
507
+ return /* @__PURE__ */ jsx(CheckoutRedirect, { planId });
508
+ };
502
509
  //#endregion
503
510
  //#region src/pages/ManagePaymentPage.tsx
504
511
  const ManagePaymentPage = () => {
@@ -1034,6 +1041,24 @@ const useSubscriptions = () => {
1034
1041
  });
1035
1042
  };
1036
1043
  //#endregion
1044
+ //#region src/hooks/usePendingCredits.ts
1045
+ /**
1046
+ * Fetch the operator-applied usage credits for a subscription, via the Zuplo
1047
+ * metering `.../pending-credits` endpoint. Failures are swallowed (`retry: false`,
1048
+ * `throwOnError: false`) so the usage page never breaks when credits are
1049
+ * unavailable — the credit banner simply isn't shown.
1050
+ */
1051
+ const usePendingCredits = (deploymentName, subscriptionId) => {
1052
+ const zudoku = useZudoku();
1053
+ return useQuery({
1054
+ queryKey: [`/v3/zudoku-metering/${deploymentName}/subscriptions/${subscriptionId}/pending-credits`],
1055
+ meta: { context: zudoku },
1056
+ refetchOnWindowFocus: true,
1057
+ retry: false,
1058
+ throwOnError: false
1059
+ });
1060
+ };
1061
+ //#endregion
1037
1062
  //#region src/pages/subscriptions/ConfirmDeleteKeyAlert.tsx
1038
1063
  const ConfirmDeleteKeyAlert = ({ children, onDelete }) => {
1039
1064
  return /* @__PURE__ */ jsxs(AlertDialog, { children: [/* @__PURE__ */ jsx(AlertDialogTrigger, {
@@ -2142,6 +2167,7 @@ const sectionLabelClassName = "text-base font-semibold tracking-wide mb-3 mt-2";
2142
2167
  const formatDateTimeRange = (from, to) => `${formatDateTime(from)} – ${formatDateTime(to)}`;
2143
2168
  const SubscriptionPlanDetails = ({ subscription }) => {
2144
2169
  const { pricing } = useMonetizationConfig();
2170
+ const hasEnded = !!subscription.activeTo && new Date(subscription.activeTo).getTime() < Date.now();
2145
2171
  const plan = subscription.plan;
2146
2172
  const view = getSubscriptionPlanView(subscription, { units: pricing?.units });
2147
2173
  const { priceLabel } = view;
@@ -2195,10 +2221,10 @@ const SubscriptionPlanDetails = ({ subscription }) => {
2195
2221
  }) : null] })] }),
2196
2222
  /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", {
2197
2223
  className: detailLabelClassName,
2198
- children: "Current period"
2224
+ children: hasEnded ? "Ended" : "Current period"
2199
2225
  }), /* @__PURE__ */ jsx("dd", {
2200
2226
  className: "text-foreground",
2201
- children: subscription.alignment?.currentAlignedBillingPeriod ? formatDateTimeRange(subscription.alignment.currentAlignedBillingPeriod.from, subscription.alignment.currentAlignedBillingPeriod.to) : "—"
2227
+ children: hasEnded ? formatDateTime(subscription.activeTo ?? "") : subscription.alignment?.currentAlignedBillingPeriod ? formatDateTimeRange(subscription.alignment.currentAlignedBillingPeriod.from, subscription.alignment.currentAlignedBillingPeriod.to) : "—"
2202
2228
  })] })
2203
2229
  ]
2204
2230
  }),
@@ -2236,48 +2262,185 @@ const SubscriptionPlanDetails = ({ subscription }) => {
2236
2262
  });
2237
2263
  };
2238
2264
  //#endregion
2265
+ //#region src/utils/priceIncludedUnits.ts
2266
+ const partAmount = (part) => {
2267
+ if (!part || part.amount === void 0) return 0;
2268
+ const amount = parseFloat(part.amount);
2269
+ return Number.isFinite(amount) ? amount : Number.POSITIVE_INFINITY;
2270
+ };
2271
+ const priceIncludedUnits = (price) => {
2272
+ if (!price) return 0;
2273
+ if (price.type === "flat" || price.type === "package" || price.type === "dynamic") return 0;
2274
+ if (price.tiers?.length) {
2275
+ if ((price.mode ?? "graduated") !== "graduated") return 0;
2276
+ const sorted = [...price.tiers].sort((a, b) => {
2277
+ if (a.upToAmount === void 0) return 1;
2278
+ if (b.upToAmount === void 0) return -1;
2279
+ return Number(a.upToAmount) - Number(b.upToAmount);
2280
+ });
2281
+ let free = 0;
2282
+ for (const [index, tier] of sorted.entries()) {
2283
+ if (partAmount(tier.unitPrice) > 0) break;
2284
+ if (index > 0 && partAmount(tier.flatPrice) > 0) break;
2285
+ if (tier.upToAmount === void 0) return Number.POSITIVE_INFINITY;
2286
+ const bound = Number(tier.upToAmount);
2287
+ if (!Number.isFinite(bound) || bound <= free) break;
2288
+ free = bound;
2289
+ }
2290
+ return free;
2291
+ }
2292
+ if (price.type === "unit") {
2293
+ const amount = parseFloat(price.amount ?? "");
2294
+ return Number.isFinite(amount) && amount === 0 ? Number.POSITIVE_INFINITY : 0;
2295
+ }
2296
+ return 0;
2297
+ };
2298
+ //#endregion
2299
+ //#region src/pages/subscriptions/deriveUsageView.ts
2300
+ const formatAmount = (amount) => {
2301
+ const value = parseFloat(amount ?? "");
2302
+ return Number.isFinite(value) ? `$${value.toFixed(2)}` : void 0;
2303
+ };
2304
+ const pluralizeUnit = (unitName) => unitName.endsWith("s") ? unitName : `${unitName}s`;
2305
+ /** The label for what additional usage costs, when the shape has one number. */
2306
+ const rateLabelFor = (price, unitName) => {
2307
+ if (!price) return void 0;
2308
+ if (price.type === "unit") {
2309
+ const amount = formatAmount(price.amount);
2310
+ return amount ? `${amount}/${unitName}` : void 0;
2311
+ }
2312
+ if (price.type === "tiered") {
2313
+ const amount = formatAmount((price.tiers?.find((t) => !t.upToAmount) ?? price.tiers?.at(-1))?.unitPrice?.amount);
2314
+ return amount ? `${amount}/${unitName}` : void 0;
2315
+ }
2316
+ if (price.type === "package") {
2317
+ const amount = formatAmount(price.amount);
2318
+ if (!amount) return void 0;
2319
+ const size = parseFloat(price.quantityPerUnit ?? "");
2320
+ return Number.isFinite(size) && size > 0 ? `${amount} per ${size.toLocaleString()} ${pluralizeUnit(unitName)}` : amount;
2321
+ }
2322
+ };
2323
+ const NO_CAP = "There is no usage cap.";
2324
+ const deriveUsageView = (meter, item, unitName = "unit") => {
2325
+ const quota = meter.balance + meter.usage - meter.overage;
2326
+ const isSoftLimit = item?.included?.entitlement?.isSoftLimit ?? true;
2327
+ const rateLabel = rateLabelFor(item?.price, unitName);
2328
+ if (!isSoftLimit) return {
2329
+ kind: "capped",
2330
+ usage: meter.usage,
2331
+ quota,
2332
+ remaining: meter.balance,
2333
+ atLimit: meter.usage >= quota,
2334
+ rateLabel
2335
+ };
2336
+ if (!item) return {
2337
+ kind: "meteredGeneric",
2338
+ usage: meter.usage,
2339
+ quota: quota > 0 ? quota : void 0,
2340
+ caption: "Usage is billed per your plan's pricing."
2341
+ };
2342
+ if (!item.price || item.price.type === "flat") return {
2343
+ kind: "meteredGeneric",
2344
+ usage: meter.usage,
2345
+ quota: quota > 0 ? quota : void 0,
2346
+ caption: `Usage doesn't change your bill. ${NO_CAP}`
2347
+ };
2348
+ const isGraduated = item.price.type === "tiered" && (item.price.mode ?? "graduated") === "graduated";
2349
+ const derivable = item.price.type === "unit" || isGraduated;
2350
+ const freeUnits = priceIncludedUnits(item.price);
2351
+ if (derivable) {
2352
+ if (freeUnits === 0) return {
2353
+ kind: "payAsYouGo",
2354
+ usage: meter.usage,
2355
+ caption: `Pay as you go — every ${unitName} is billed; there is no usage cap.`,
2356
+ rateLabel
2357
+ };
2358
+ if (freeUnits === Number.POSITIVE_INFINITY) return {
2359
+ kind: "meteredGeneric",
2360
+ usage: meter.usage,
2361
+ quota: quota > 0 ? quota : void 0,
2362
+ caption: `Included with your plan. ${NO_CAP}`,
2363
+ rateLabel
2364
+ };
2365
+ if (quota > 0) return {
2366
+ kind: "included",
2367
+ usage: meter.usage,
2368
+ included: quota,
2369
+ remaining: meter.balance,
2370
+ overage: meter.overage,
2371
+ rateLabel
2372
+ };
2373
+ return {
2374
+ kind: "meteredGeneric",
2375
+ usage: meter.usage,
2376
+ caption: `The first ${freeUnits.toLocaleString()} ${pluralizeUnit(unitName)} are included; additional usage is billed. ${NO_CAP}`,
2377
+ rateLabel
2378
+ };
2379
+ }
2380
+ return {
2381
+ kind: "meteredGeneric",
2382
+ usage: meter.usage,
2383
+ quota: quota > 0 ? quota : void 0,
2384
+ caption: `Usage is billed per your plan's pricing. ${NO_CAP}`,
2385
+ rateLabel
2386
+ };
2387
+ };
2388
+ //#endregion
2239
2389
  //#region src/pages/subscriptions/Usage.tsx
2240
2390
  const isMeteredEntitlement = (entitlement) => {
2241
2391
  return "balance" in entitlement;
2242
2392
  };
2243
- const UsageItem = ({ meter, item, subscription, featureKey }) => {
2393
+ const UsageItem = ({ meter, item, subscription, featureKey, pendingCredit }) => {
2394
+ const { pricing } = useMonetizationConfig();
2244
2395
  const cadence = item?.billingCadence ?? subscription?.billingCadence;
2245
2396
  const billingPeriod = cadence ? formatDurationAdjective(cadence) : "monthly";
2246
- const isSoftLimit = item?.included?.entitlement?.isSoftLimit ?? true;
2247
- const rate = (item?.price?.tiers?.find((t) => !t.upToAmount) ?? item?.price?.tiers?.at(-1))?.unitPrice?.amount;
2248
- const hasOverage = meter.overage > 0;
2249
- const limit = meter.balance + meter.usage - meter.overage;
2250
- const isAtLimit = !isSoftLimit && meter.usage >= limit;
2251
- const dangerZone = hasOverage || isAtLimit;
2397
+ const view = deriveUsageView(meter, item, pricing?.units?.[item?.key ?? ""] ?? pricing?.units?.[featureKey] ?? "unit");
2398
+ const atHardLimit = view.kind === "capped" && view.atLimit;
2399
+ const overIncluded = view.kind === "included" && view.overage > 0;
2400
+ const upgradeAction = (variant) => subscription && /* @__PURE__ */ jsx(AlertAction, { children: /* @__PURE__ */ jsx(SwitchPlanModal, {
2401
+ subscription,
2402
+ children: /* @__PURE__ */ jsxs(Button, {
2403
+ variant,
2404
+ size: "xs",
2405
+ children: [/* @__PURE__ */ jsx(ArrowUpIcon, {}), "Upgrade"]
2406
+ })
2407
+ }) });
2252
2408
  return /* @__PURE__ */ jsxs(Card, {
2253
- className: cn(dangerZone && "border-destructive bg-destructive/5"),
2409
+ className: cn(atHardLimit && "border-destructive bg-destructive/5"),
2254
2410
  children: [/* @__PURE__ */ jsxs(CardHeader, { children: [
2255
- hasOverage && isSoftLimit && /* @__PURE__ */ jsxs(Alert, {
2256
- variant: "destructive",
2411
+ pendingCredit && /* @__PURE__ */ jsxs(Alert, {
2257
2412
  className: "mb-4",
2258
2413
  children: [
2259
- /* @__PURE__ */ jsx(AlertTriangleIcon, { className: "size-4 text-red-600 shrink-0" }),
2414
+ /* @__PURE__ */ jsx(BadgePercentIcon, { className: "size-4 text-green-600 shrink-0" }),
2415
+ /* @__PURE__ */ jsx(AlertTitle, { children: "Usage credit applied" }),
2416
+ /* @__PURE__ */ jsxs(AlertDescription, { children: [
2417
+ "A credit of ",
2418
+ pendingCredit.units.toLocaleString(),
2419
+ " ",
2420
+ pendingCredit.units === 1 ? "unit" : "units",
2421
+ " applies to this billing period and will be deducted from your next invoice automatically."
2422
+ ] })
2423
+ ]
2424
+ }),
2425
+ overIncluded && /* @__PURE__ */ jsxs(Alert, {
2426
+ variant: "warning",
2427
+ className: "mb-4",
2428
+ children: [
2429
+ /* @__PURE__ */ jsx(AlertTriangleIcon, { className: "size-4 shrink-0" }),
2260
2430
  /* @__PURE__ */ jsxs(AlertTitle, { children: [
2261
- "You've exceeded your ",
2431
+ "You've used your included ",
2262
2432
  billingPeriod,
2263
- " quota"
2433
+ " usage"
2264
2434
  ] }),
2265
2435
  /* @__PURE__ */ jsxs(AlertDescription, { children: [
2266
- "Additional usage is being charged at the overage rate",
2267
- rate ? ` ($${Number(rate).toFixed(2)}/call)` : "",
2268
- ". Upgrade to a higher plan for more usage."
2436
+ "Additional usage is billed",
2437
+ view.rateLabel ? ` at ${view.rateLabel}` : "",
2438
+ ". Upgrade to a higher plan for more included usage."
2269
2439
  ] }),
2270
- subscription && /* @__PURE__ */ jsx(AlertAction, { children: /* @__PURE__ */ jsx(SwitchPlanModal, {
2271
- subscription,
2272
- children: /* @__PURE__ */ jsxs(Button, {
2273
- variant: "destructive",
2274
- size: "xs",
2275
- children: [/* @__PURE__ */ jsx(ArrowUpIcon, {}), "Upgrade"]
2276
- })
2277
- }) })
2440
+ upgradeAction("outline")
2278
2441
  ]
2279
2442
  }),
2280
- isAtLimit && !isSoftLimit && /* @__PURE__ */ jsxs(Alert, {
2443
+ atHardLimit && /* @__PURE__ */ jsxs(Alert, {
2281
2444
  variant: "destructive",
2282
2445
  className: "mb-4",
2283
2446
  children: [
@@ -2288,58 +2451,79 @@ const UsageItem = ({ meter, item, subscription, featureKey }) => {
2288
2451
  " limit"
2289
2452
  ] }),
2290
2453
  /* @__PURE__ */ jsx(AlertDescription, { children: "Requests beyond your quota are blocked. Upgrade to a higher plan for more usage." }),
2291
- subscription && /* @__PURE__ */ jsx(AlertAction, { children: /* @__PURE__ */ jsx(SwitchPlanModal, {
2292
- subscription,
2293
- children: /* @__PURE__ */ jsxs(Button, {
2294
- variant: "destructive",
2295
- size: "xs",
2296
- children: [/* @__PURE__ */ jsx(ArrowUpIcon, {}), "Upgrade"]
2297
- })
2298
- }) })
2454
+ upgradeAction("destructive")
2299
2455
  ]
2300
2456
  }),
2301
2457
  /* @__PURE__ */ jsx(CardTitle, { children: item?.name ?? featureKey })
2302
- ] }), /* @__PURE__ */ jsxs(CardContent, {
2458
+ ] }), /* @__PURE__ */ jsx(CardContent, {
2303
2459
  className: "space-y-2",
2304
- children: [
2460
+ children: view.kind === "capped" || view.kind === "included" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
2305
2461
  /* @__PURE__ */ jsxs("div", {
2306
2462
  className: "flex items-center justify-between text-sm",
2307
2463
  children: [/* @__PURE__ */ jsx("div", {
2308
2464
  className: "flex flex-col gap-2 mb-2",
2309
2465
  children: /* @__PURE__ */ jsxs("span", {
2310
- className: cn(dangerZone && "text-red-600 font-medium"),
2466
+ className: cn(atHardLimit && "text-red-600 font-medium"),
2311
2467
  children: [
2312
- meter.usage.toLocaleString(),
2468
+ view.usage.toLocaleString(),
2313
2469
  " used",
2314
- hasOverage && isSoftLimit && /* @__PURE__ */ jsxs("span", {
2470
+ view.kind === "included" && view.overage > 0 && /* @__PURE__ */ jsxs("span", {
2315
2471
  className: "ml-1 text-xs",
2316
2472
  children: [
2317
2473
  "(+",
2318
- meter.overage.toLocaleString(),
2474
+ view.overage.toLocaleString(),
2319
2475
  " overage)"
2320
2476
  ]
2321
2477
  })
2322
2478
  ]
2323
2479
  })
2324
- }), /* @__PURE__ */ jsxs("span", {
2480
+ }), /* @__PURE__ */ jsx("span", {
2325
2481
  className: "text-foreground font-medium",
2326
- children: [limit.toLocaleString(), " limit"]
2482
+ children: view.kind === "capped" ? `${view.quota.toLocaleString()} limit` : `${view.included.toLocaleString()} included`
2327
2483
  })]
2328
2484
  }),
2329
2485
  /* @__PURE__ */ jsx(Progress, {
2330
- value: Math.min(100, limit > 0 ? meter.usage / limit * 100 : 100),
2331
- className: cn("mb-3 h-2", dangerZone && "bg-destructive")
2486
+ value: Math.min(100, (view.kind === "capped" ? view.quota : view.included) > 0 ? view.usage / (view.kind === "capped" ? view.quota : view.included) * 100 : 100),
2487
+ className: cn("mb-3 h-2", atHardLimit && "bg-destructive")
2332
2488
  }),
2333
2489
  /* @__PURE__ */ jsxs("p", {
2334
2490
  className: "text-xs text-muted-foreground",
2335
- children: [meter.balance.toLocaleString(), " remaining this billing period"]
2491
+ children: [
2492
+ view.remaining.toLocaleString(),
2493
+ view.kind === "included" ? " included" : "",
2494
+ " remaining this billing period"
2495
+ ]
2336
2496
  })
2337
- ]
2497
+ ] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
2498
+ className: "flex items-center justify-between text-sm",
2499
+ children: [/* @__PURE__ */ jsxs("span", { children: [view.usage.toLocaleString(), " used this billing period"] }), view.kind === "meteredGeneric" && view.quota !== void 0 ? /* @__PURE__ */ jsxs("span", {
2500
+ className: "text-foreground font-medium",
2501
+ children: [view.quota.toLocaleString(), " quota"]
2502
+ }) : view.rateLabel && /* @__PURE__ */ jsx("span", {
2503
+ className: "text-muted-foreground",
2504
+ children: view.rateLabel
2505
+ })]
2506
+ }), /* @__PURE__ */ jsx("p", {
2507
+ className: "text-xs text-muted-foreground",
2508
+ children: view.caption
2509
+ })] })
2338
2510
  })]
2339
2511
  });
2340
2512
  };
2341
- const Usage = ({ usage, isFetching, currentItems, subscription, isPendingFirstPayment }) => {
2513
+ const Usage = ({ usage, isFetching, currentItems, subscription, isPendingFirstPayment, pendingCredits }) => {
2342
2514
  const hasUsage = Object.values(usage.entitlements).some((value) => isMeteredEntitlement(value));
2515
+ if (!!subscription?.activeTo && new Date(subscription.activeTo).getTime() < Date.now()) return /* @__PURE__ */ jsxs("div", {
2516
+ className: "space-y-4",
2517
+ children: [/* @__PURE__ */ jsx(Heading, {
2518
+ level: 3,
2519
+ children: "Usage"
2520
+ }), /* @__PURE__ */ jsxs(Alert, { children: [
2521
+ /* @__PURE__ */ jsx(HistoryIcon, { className: "size-4 shrink-0" }),
2522
+ /* @__PURE__ */ jsx(AlertTitle, { children: "This subscription has ended" }),
2523
+ /* @__PURE__ */ jsx(AlertDescription, { children: "Usage history isn't available yet." })
2524
+ ] })]
2525
+ });
2526
+ const creditByFeature = new Map((pendingCredits ?? []).map((credit) => [credit.featureKey, credit]));
2343
2527
  return /* @__PURE__ */ jsxs("div", {
2344
2528
  className: "space-y-4",
2345
2529
  children: [
@@ -2378,7 +2562,8 @@ const Usage = ({ usage, isFetching, currentItems, subscription, isPendingFirstPa
2378
2562
  featureKey: key,
2379
2563
  meter: { ...value },
2380
2564
  subscription,
2381
- item: currentItems?.find((item) => item.featureKey === key)
2565
+ item: currentItems?.find((item) => item.featureKey === key),
2566
+ pendingCredit: creditByFeature.get(key)
2382
2567
  }, key) : []) : !isFetching && !subscription?.annotations?.["subscription.previous.id"] ? /* @__PURE__ */ jsxs(Alert, {
2383
2568
  variant: "warning",
2384
2569
  children: [
@@ -2401,6 +2586,7 @@ const ActiveSubscription = ({ subscription, deploymentName }) => {
2401
2586
  refetchOnWindowFocus: true,
2402
2587
  meta: { context: zudoku }
2403
2588
  });
2589
+ const pendingCreditsQuery = usePendingCredits(deploymentName, subscription.id);
2404
2590
  const isPendingFirstPayment = usageQuery.data.paymentStatus.isFirstPayment === true && usageQuery.data.paymentStatus.status !== "paid" && usageQuery.data.paymentStatus.status !== "not_required";
2405
2591
  const activePhase = getActivePhase(subscription);
2406
2592
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [
@@ -2419,7 +2605,8 @@ const ActiveSubscription = ({ subscription, deploymentName }) => {
2419
2605
  usage: usageQuery.data,
2420
2606
  isFetching: usageQuery.isFetching,
2421
2607
  subscription,
2422
- isPendingFirstPayment
2608
+ isPendingFirstPayment,
2609
+ pendingCredits: pendingCreditsQuery.data?.pendingCredits
2423
2610
  }),
2424
2611
  subscription?.consumer?.apiKeys && /* @__PURE__ */ jsx(ApiKeysList, {
2425
2612
  isPendingFirstPayment,
@@ -2565,7 +2752,11 @@ const zuploMonetizationPlugin = createPlugin((options = {}) => ({
2565
2752
  queryClient.prefetchQuery(pricingPageQuery(context));
2566
2753
  if (context.getAuthState().isAuthenticated) queryClient.prefetchQuery(subscriptionsQuery(context));
2567
2754
  },
2755
+ events: { auth: ({ prev, next }) => {
2756
+ if (prev.isAuthenticated && !next.isAuthenticated) queryClient.removeQueries();
2757
+ } },
2568
2758
  getIdentities: async (context) => {
2759
+ if (!context.getAuthState().isAuthenticated) return [];
2569
2760
  return (await queryClient.fetchQuery(subscriptionsQuery(context))).items.flatMap((sub) => sub.status !== "active" ? [] : sub.consumer.apiKeys.flatMap((apiKey) => apiKey.expiresOn && new Date(apiKey.expiresOn) < /* @__PURE__ */ new Date() ? [] : {
2570
2761
  label: `${sub.name} (****${apiKey.key.slice(-5)})`,
2571
2762
  id: apiKey.id,
@@ -139,7 +139,7 @@ declare const FeatureItem: ({
139
139
  }: {
140
140
  feature: Feature;
141
141
  className?: string;
142
- }) => import("react/jsx-runtime").JSX.Element;
142
+ }) => import("react").JSX.Element;
143
143
  //#endregion
144
144
  //#region src/pricing-ui/PlanEntitlements.d.ts
145
145
  /**
@@ -161,7 +161,7 @@ declare const PlanEntitlements: ({
161
161
  billingCadence?: string;
162
162
  units?: Record<string, string>;
163
163
  itemClassName?: string;
164
- }) => import("react/jsx-runtime").JSX.Element;
164
+ }) => import("react").JSX.Element;
165
165
  //#endregion
166
166
  //#region src/utils/formatPlanPrice.d.ts
167
167
  type PlanPriceLabel = {
@@ -225,7 +225,7 @@ declare const PlanPriceSchedule: ({
225
225
  billingCadence?: string;
226
226
  size?: "sm" | "lg";
227
227
  className?: string;
228
- }) => import("react/jsx-runtime").JSX.Element;
228
+ }) => import("react").JSX.Element;
229
229
  //#endregion
230
230
  //#region src/pricing-ui/PlanPriceTag.d.ts
231
231
  /**
@@ -253,7 +253,7 @@ declare const PlanPriceTag: ({
253
253
  billingCadence?: string;
254
254
  description?: boolean;
255
255
  size?: "inline" | "lg";
256
- }) => import("react/jsx-runtime").JSX.Element;
256
+ }) => import("react").JSX.Element;
257
257
  //#endregion
258
258
  //#region src/pricing-ui/PricingCard.d.ts
259
259
  type PricingCardProps = {
@@ -269,7 +269,7 @@ declare const PricingCard: ({
269
269
  units,
270
270
  action,
271
271
  className
272
- }: PricingCardProps) => import("react/jsx-runtime").JSX.Element | null;
272
+ }: PricingCardProps) => import("react").JSX.Element | null;
273
273
  //#endregion
274
274
  //#region src/pricing-ui/PricingTable.d.ts
275
275
  type PricingTableProps = {
@@ -319,7 +319,7 @@ declare const PricingTable: ({
319
319
  showTaxLegend,
320
320
  className,
321
321
  cardClassName
322
- }: PricingTableProps) => import("react/jsx-runtime").JSX.Element;
322
+ }: PricingTableProps) => import("react").JSX.Element;
323
323
  //#endregion
324
324
  //#region src/pricing-ui/QuotaItem.d.ts
325
325
  declare const QuotaItem: ({
@@ -328,7 +328,7 @@ declare const QuotaItem: ({
328
328
  }: {
329
329
  quota: Quota;
330
330
  className?: string;
331
- }) => import("react/jsx-runtime").JSX.Element;
331
+ }) => import("react").JSX.Element;
332
332
  //#endregion
333
333
  //#region src/utils/categorizeRateCards.d.ts
334
334
  declare const categorizeRateCards: (rateCards: RateCard[], options?: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zuplo/zudoku-plugin-monetization",
3
- "version": "0.0.44",
3
+ "version": "0.0.46",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/zuplo/zudoku",
@@ -31,13 +31,14 @@
31
31
  "@testing-library/dom": "10.4.1",
32
32
  "@testing-library/jest-dom": "6.9.1",
33
33
  "@testing-library/react": "16.3.2",
34
- "@types/react": "19.2.14",
34
+ "@types/react": "19.2.17",
35
35
  "@types/react-dom": "19.2.3",
36
- "happy-dom": "20.9.0",
37
- "react": "19.2.5",
38
- "react-dom": "19.2.5",
36
+ "@typescript/native-preview": "7.0.0-dev.20260624.1",
37
+ "happy-dom": "20.10.2",
38
+ "react": "19.2.7",
39
+ "react-dom": "19.2.7",
39
40
  "tsdown": "0.22.0",
40
- "zudoku": "0.79.1"
41
+ "zudoku": "0.82.3"
41
42
  },
42
43
  "peerDependencies": {
43
44
  "react": ">=19.2.0",
@@ -52,6 +53,6 @@
52
53
  "scripts": {
53
54
  "build": "tsdown",
54
55
  "dev": "tsdown --watch",
55
- "typecheck": "tsc --project tsconfig.json"
56
+ "typecheck": "tsgo --project tsconfig.json"
56
57
  }
57
58
  }