@lukoweb/apitogo-plugin-dev-portal-billing 0.2.54 → 0.2.56

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.
Files changed (21) hide show
  1. package/dist/{AccountPlansPage-0PYGy1aV.mjs → AccountPlansPage-BuM7fe82.mjs} +12 -6
  2. package/dist/{CheckoutPage-Bpa128i2.mjs → CheckoutPage-BSiADcI6.mjs} +7 -2
  3. package/dist/{ContactSalesPage-BslD3Yk4.mjs → ContactSalesPage-BqqDmorF.mjs} +2 -2
  4. package/dist/{CredentialsPage-C4qpBVrh.mjs → CredentialsPage-DjNvBBLb.mjs} +4 -4
  5. package/dist/{PricingCard-YJIEwydG.mjs → PricingCard-DFuQEw7Y.mjs} +9 -3
  6. package/dist/{PricingPage-DIhoFOd9.mjs → PricingPage-SpL81vpd.mjs} +14 -13
  7. package/dist/{SubscriptionsPage-D7hsYtTB.mjs → SubscriptionsPage-CwsBtNld.mjs} +16 -10
  8. package/dist/{UsagePage-Di9kQ_3Z.mjs → UsagePage-Dw5Uepg3.mjs} +4 -4
  9. package/dist/{account-links-9h91uCGj.mjs → account-links-Drofs-f-.mjs} +1 -1
  10. package/dist/{billing-state-Dgcc3_pT.mjs → billing-state-DBQR2tad.mjs} +1 -1
  11. package/dist/index.d.mts +3 -2
  12. package/dist/index.mjs +21 -19
  13. package/dist/pending-checkout-DpVaNnTu.mjs +45 -0
  14. package/dist/{useApiKeys-CEwMzqju.mjs → useApiKeys-BOY3vIOr.mjs} +1 -1
  15. package/dist/{useDevPortalApiBaseUrl-BstWzITX.mjs → useDevPortalApiBaseUrl-BiI2jFjU.mjs} +14 -14
  16. package/dist/{useMySubscriptions-DuVq2tXL.mjs → useMySubscriptions-BYVIIWbe.mjs} +1 -1
  17. package/dist/{usePlans-BF7Hy2qJ.mjs → usePlans-HyhIIuJZ.mjs} +1 -1
  18. package/dist/{useUsage-CJ9VIBc7.mjs → useUsage-B5x9YuKa.mjs} +1 -1
  19. package/package.json +3 -3
  20. /package/dist/{page-state-BB-72pbw.mjs → page-state-BLL2JJBc.mjs} +0 -0
  21. /package/dist/{pricing-config-C44AIqql.mjs → pricing-config-Bc6eWFUO.mjs} +0 -0
@@ -1,13 +1,14 @@
1
- import { f as parseManifestPlans, n as useDevPortalPreviewMode, r as useIsDemoSession, s as isActiveSubscription, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-BstWzITX.mjs";
2
- import { t as PricingCard } from "./PricingCard-YJIEwydG.mjs";
1
+ import { f as parseManifestPlans, n as useDevPortalPreviewMode, r as useIsDemoSession, s as isActiveSubscription, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-BiI2jFjU.mjs";
2
+ import { t as PricingCard } from "./PricingCard-DFuQEw7Y.mjs";
3
3
  import { n as useDevPortalBillingConfig } from "./DevPortalBillingContext-CR83s4LO.mjs";
4
4
  import { n as readProblemDetail } from "./problem-details-n20-dy3w.mjs";
5
- import { t as useMySubscriptions } from "./useMySubscriptions-DuVq2tXL.mjs";
6
- import { t as usePlans } from "./usePlans-BF7Hy2qJ.mjs";
5
+ import { t as useMySubscriptions } from "./useMySubscriptions-BYVIIWbe.mjs";
6
+ import { t as usePlans } from "./usePlans-HyhIIuJZ.mjs";
7
+ import { n as readPendingCheckout, t as clearPendingCheckout } from "./pending-checkout-DpVaNnTu.mjs";
7
8
  import { Head } from "@lukoweb/apitogo/components";
8
9
  import { CreditCardIcon } from "@lukoweb/apitogo/icons";
9
10
  import { useSearchParams } from "@lukoweb/apitogo/router";
10
- import { useState } from "react";
11
+ import { useEffect, useState } from "react";
11
12
  import { jsx, jsxs } from "react/jsx-runtime";
12
13
  import { useMutation, useQueryClient } from "@lukoweb/apitogo/react-query";
13
14
 
@@ -70,6 +71,10 @@ const AccountPlansPage = () => {
70
71
  const cancelMutation = useCancelSubscription();
71
72
  const [cancelingSku, setCancelingSku] = useState(null);
72
73
  const subscriptionBySku = new Map((me?.subscriptions ?? []).map((sub) => [sub.planSku, sub]));
74
+ useEffect(() => {
75
+ if (checkoutCancelled) clearPendingCheckout();
76
+ }, [checkoutCancelled]);
77
+ const pendingSku = checkoutCancelled ? void 0 : readPendingCheckout();
73
78
  const handleUnsubscribe = async (planSku) => {
74
79
  setCancelingSku(planSku);
75
80
  try {
@@ -133,7 +138,8 @@ const AccountPlansPage = () => {
133
138
  isSubscribed,
134
139
  onUnsubscribe: isPreview || isDemoSession ? void 0 : handleUnsubscribe,
135
140
  isCanceling: cancelingSku === plan.sku,
136
- cancelDisabled: isPreview || isDemoSession
141
+ cancelDisabled: isPreview || isDemoSession,
142
+ isConfirming: !isSubscribed && pendingSku === plan.sku
137
143
  }, plan.sku);
138
144
  })
139
145
  }) : !isLoading && !isError ? /* @__PURE__ */ jsx("div", {
@@ -1,7 +1,8 @@
1
1
  import { n as ALREADY_SUBSCRIBED_PARAM, s as PRICING_PATH, t as ACCOUNT_PLANS_PATH } from "./paths-DaVyTIDJ.mjs";
2
- import { n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-BstWzITX.mjs";
2
+ import { n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-BiI2jFjU.mjs";
3
3
  import { n as readProblemDetail, t as isAlreadySubscribedMessage } from "./problem-details-n20-dy3w.mjs";
4
- import { t as FREE_CHECKOUT_MARKER } from "./billing-state-Dgcc3_pT.mjs";
4
+ import { r as rememberPendingCheckout } from "./pending-checkout-DpVaNnTu.mjs";
5
+ import { t as FREE_CHECKOUT_MARKER } from "./billing-state-DBQR2tad.mjs";
5
6
  import { ShieldIcon } from "@lukoweb/apitogo/icons";
6
7
  import { Link, useNavigate, useSearchParams } from "@lukoweb/apitogo/router";
7
8
  import { useEffect } from "react";
@@ -87,6 +88,10 @@ const CheckoutPage = () => {
87
88
  }
88
89
  });
89
90
  const freeProvisioned = checkoutQuery.data?.checkoutUrl == null && checkoutQuery.data?.entitlementProvisioned === true;
91
+ const checkoutUrl = checkoutQuery.data?.checkoutUrl;
92
+ useEffect(() => {
93
+ if (checkoutUrl) rememberPendingCheckout(planSku);
94
+ }, [checkoutUrl, planSku]);
90
95
  const alreadySubscribed = checkoutQuery.isError && isAlreadySubscribedMessage(checkoutQuery.error.message);
91
96
  useEffect(() => {
92
97
  if (alreadySubscribed) navigate(`/account/subscriptions?${ALREADY_SUBSCRIBED_PARAM}=${encodeURIComponent(planSku)}`, { replace: true });
@@ -1,8 +1,8 @@
1
1
  import { s as PRICING_PATH } from "./paths-DaVyTIDJ.mjs";
2
- import { l as isPlanCustomPriced, n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-BstWzITX.mjs";
2
+ import { l as isPlanCustomPriced, n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-BiI2jFjU.mjs";
3
3
  import "./DevPortalBillingContext-CR83s4LO.mjs";
4
4
  import { n as readProblemDetail } from "./problem-details-n20-dy3w.mjs";
5
- import { t as usePlans } from "./usePlans-BF7Hy2qJ.mjs";
5
+ import { t as usePlans } from "./usePlans-HyhIIuJZ.mjs";
6
6
  import { Link, useSearchParams } from "@lukoweb/apitogo/router";
7
7
  import { useState } from "react";
8
8
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -1,10 +1,10 @@
1
1
  import { t as ACCOUNT_PLANS_PATH } from "./paths-DaVyTIDJ.mjs";
2
- import { n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-BstWzITX.mjs";
2
+ import { n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-BiI2jFjU.mjs";
3
3
  import { n as useDevPortalBillingConfig } from "./DevPortalBillingContext-CR83s4LO.mjs";
4
4
  import { r as createDemoCredentials, t as DEMO_GATEWAY_KEY_HEADER } from "./demo-session-yK9qEqV-.mjs";
5
- import { n as useApiKeys } from "./useApiKeys-CEwMzqju.mjs";
6
- import { n as apiReferencePath, r as ApiKeysPanel } from "./account-links-9h91uCGj.mjs";
7
- import { i as ChoosePlanNotice, r as keyPlanNote, t as apiKeysPageState } from "./page-state-BB-72pbw.mjs";
5
+ import { n as useApiKeys } from "./useApiKeys-BOY3vIOr.mjs";
6
+ import { n as apiReferencePath, r as ApiKeysPanel } from "./account-links-Drofs-f-.mjs";
7
+ import { i as ChoosePlanNotice, r as keyPlanNote, t as apiKeysPageState } from "./page-state-BLL2JJBc.mjs";
8
8
  import { Head } from "@lukoweb/apitogo/components";
9
9
  import { KeyRoundIcon } from "@lukoweb/apitogo/icons";
10
10
  import { Link as Link$1 } from "@lukoweb/apitogo/router";
@@ -1,5 +1,5 @@
1
1
  import { i as CONTACT_SALES_PATH, r as CHECKOUT_PATH } from "./paths-DaVyTIDJ.mjs";
2
- import { d as listPlanFeatureLines, i as formatPlanDisplayPrice, l as isPlanCustomPriced, o as formatPlanPriceUnit, s as isActiveSubscription, u as isPlanFree } from "./useDevPortalApiBaseUrl-BstWzITX.mjs";
2
+ import { d as listPlanFeatureLines, i as formatPlanDisplayPrice, l as isPlanCustomPriced, o as formatPlanPriceUnit, s as isActiveSubscription, u as isPlanFree } from "./useDevPortalApiBaseUrl-BiI2jFjU.mjs";
3
3
  import { Button } from "@lukoweb/apitogo/components";
4
4
  import { Link as Link$1 } from "@lukoweb/apitogo/router";
5
5
  import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@lukoweb/apitogo/ui/AlertDialog.js";
@@ -18,7 +18,7 @@ const accountCtaLabel = (plan) => {
18
18
  };
19
19
  /** Custom-priced plans skip checkout and open the contact-sales form. */
20
20
  const planCtaPath = (plan) => `${isPlanCustomPriced(plan) ? CONTACT_SALES_PATH : CHECKOUT_PATH}?planSku=${encodeURIComponent(plan.sku)}`;
21
- const PricingCard = ({ plan, mode = "public", subscriptionStatus, isSubscribed = false, onUnsubscribe, onSwitch, isSwitching = false, isCanceling = false, cancelDisabled = false, featured = false }) => {
21
+ const PricingCard = ({ plan, mode = "public", subscriptionStatus, isSubscribed = false, onUnsubscribe, onSwitch, isSwitching = false, isCanceling = false, cancelDisabled = false, featured = false, isConfirming = false }) => {
22
22
  const isActive = isSubscribed || isActiveSubscription(subscriptionStatus ?? "");
23
23
  const features = listPlanFeatureLines(plan);
24
24
  const displayPrice = formatPlanDisplayPrice(plan);
@@ -83,7 +83,13 @@ const PricingCard = ({ plan, mode = "public", subscriptionStatus, isSubscribed =
83
83
  className: "text-center text-xs text-muted-foreground",
84
84
  children: "Unsubscribe is available after the portal is published."
85
85
  }) : null
86
- ] }) : onSwitch && !isPlanCustomPriced(plan) ? /* @__PURE__ */ jsxs(AlertDialog, { children: [/* @__PURE__ */ jsx(AlertDialogTrigger, {
86
+ ] }) : isConfirming ? /* @__PURE__ */ jsx(Button, {
87
+ variant: "secondary",
88
+ className: "w-full",
89
+ disabled: true,
90
+ "data-testid": `confirming-${plan.sku}`,
91
+ children: "Confirming payment…"
92
+ }) : onSwitch && !isPlanCustomPriced(plan) ? /* @__PURE__ */ jsxs(AlertDialog, { children: [/* @__PURE__ */ jsx(AlertDialogTrigger, {
87
93
  asChild: true,
88
94
  children: /* @__PURE__ */ jsx(Button, {
89
95
  variant: featured ? "default" : "outline",
@@ -1,9 +1,9 @@
1
- import { f as parseManifestPlans, h as pickFeaturedPlanSku, n as useDevPortalPreviewMode } from "./useDevPortalApiBaseUrl-BstWzITX.mjs";
2
- import { t as PricingCard } from "./PricingCard-YJIEwydG.mjs";
1
+ import { f as parseManifestPlans, h as pickFeaturedPlanSku, n as useDevPortalPreviewMode } from "./useDevPortalApiBaseUrl-BiI2jFjU.mjs";
2
+ import { t as PricingCard } from "./PricingCard-DFuQEw7Y.mjs";
3
3
  import { n as useDevPortalBillingConfig } from "./DevPortalBillingContext-CR83s4LO.mjs";
4
- import { t as usePlans } from "./usePlans-BF7Hy2qJ.mjs";
5
- import { t as isPreviewPricingEnabled } from "./pricing-config-C44AIqql.mjs";
6
- import { a as isPricingLoading, n as buildCompareRows } from "./billing-state-Dgcc3_pT.mjs";
4
+ import { t as usePlans } from "./usePlans-HyhIIuJZ.mjs";
5
+ import { t as isPreviewPricingEnabled } from "./pricing-config-Bc6eWFUO.mjs";
6
+ import { a as isPricingLoading, n as buildCompareRows } from "./billing-state-DBQR2tad.mjs";
7
7
  import { Head, Heading } from "@lukoweb/apitogo/components";
8
8
  import { Navigate } from "@lukoweb/apitogo/router";
9
9
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -66,15 +66,16 @@ const DEFAULT_COMPARE_ROWS = [
66
66
  ]
67
67
  }
68
68
  ];
69
+ /** Only what the platform itself does, so the defaults never promise anything for the tenant (API-124). */
69
70
  const DEFAULT_FAQS = [{
70
- question: "What happens if I exceed my quota?",
71
- answer: "Requests over your monthly quota return a 429 response so you can test rate-limit handling instead of being cut off unexpectedly."
71
+ question: "What happens if I go over my plan's limits?",
72
+ answer: "Calls over the rate limit or the quota are refused with a 429 response, and its Retry-After header says when to try again."
72
73
  }, {
73
- question: "Can I change plans later?",
74
- answer: "Yes, upgrade or downgrade any time from your billing page. Changes apply on your next billing cycle."
74
+ question: "Can I have more than one plan?",
75
+ answer: "Yes. Each plan has its own API keys and limits: create a key for a plan on the API keys page and use it in your app."
75
76
  }];
76
77
  const PricingPage = () => {
77
- const { pricing, previewPlans } = useDevPortalBillingConfig();
78
+ const { pricing, previewPlans, featuredPlanSku } = useDevPortalBillingConfig();
78
79
  const isPreview = useDevPortalPreviewMode();
79
80
  const pricingEnabled = isPreviewPricingEnabled() || isPreview;
80
81
  const livePlans = usePlans();
@@ -92,10 +93,10 @@ const PricingPage = () => {
92
93
  to: "/",
93
94
  replace: true
94
95
  });
95
- const title = pricing?.title ?? "Pay for what you call";
96
- const subtitle = pricing?.subtitle ?? "Every plan includes the full endpoint catalog. Higher tiers raise your request quota, rate limit, and support level.";
96
+ const title = pricing?.title ?? "Plans and pricing";
97
+ const subtitle = pricing?.subtitle ?? "Choose the plan that fits how you use the API. Each plan lists its limits and what it includes.";
97
98
  const plans = catalog?.plans ?? [];
98
- const featuredSku = pickFeaturedPlanSku(plans);
99
+ const featuredSku = pickFeaturedPlanSku(plans, featuredPlanSku);
99
100
  const compareRows = buildCompareRows({
100
101
  plans,
101
102
  configuredRows: pricing?.compareRows,
@@ -1,12 +1,13 @@
1
1
  import { a as CREDENTIALS_PATH, n as ALREADY_SUBSCRIBED_PARAM, t as ACCOUNT_PLANS_PATH } from "./paths-DaVyTIDJ.mjs";
2
- import { a as formatPlanPrice, p as parseMonthlyQuotaCalls, s as isActiveSubscription, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-BstWzITX.mjs";
2
+ import { a as formatPlanPrice, p as parseMonthlyQuotaCalls, s as isActiveSubscription, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-BiI2jFjU.mjs";
3
3
  import { n as useDevPortalBillingConfig } from "./DevPortalBillingContext-CR83s4LO.mjs";
4
- import { t as useMySubscriptions } from "./useMySubscriptions-DuVq2tXL.mjs";
5
- import { t as usePlans } from "./usePlans-BF7Hy2qJ.mjs";
6
- import { n as useApiKeys } from "./useApiKeys-CEwMzqju.mjs";
7
- import { t as useUsage } from "./useUsage-CJ9VIBc7.mjs";
8
- import { i as hasPaymentProvider, r as checkoutConfirmingText } from "./billing-state-Dgcc3_pT.mjs";
9
- import { i as ChoosePlanNotice, n as billingPageState, r as keyPlanNote } from "./page-state-BB-72pbw.mjs";
4
+ import { t as useMySubscriptions } from "./useMySubscriptions-BYVIIWbe.mjs";
5
+ import { t as usePlans } from "./usePlans-HyhIIuJZ.mjs";
6
+ import { n as readPendingCheckout, t as clearPendingCheckout } from "./pending-checkout-DpVaNnTu.mjs";
7
+ import { n as useApiKeys } from "./useApiKeys-BOY3vIOr.mjs";
8
+ import { t as useUsage } from "./useUsage-B5x9YuKa.mjs";
9
+ import { i as hasPaymentProvider, r as checkoutConfirmingText } from "./billing-state-DBQR2tad.mjs";
10
+ import { i as ChoosePlanNotice, n as billingPageState, r as keyPlanNote } from "./page-state-BLL2JJBc.mjs";
10
11
  import { Head, Spinner } from "@lukoweb/apitogo/components";
11
12
  import { Link as Link$1, useSearchParams } from "@lukoweb/apitogo/router";
12
13
  import { useEffect } from "react";
@@ -71,7 +72,12 @@ const SubscriptionsPage = () => {
71
72
  const earlierSubscriptions = subscriptions.filter((sub) => !isActiveSubscription(sub.status));
72
73
  const hasActivePlan = activeSubscriptions.length > 0;
73
74
  const singlePlan = activeSubscriptions.length === 1 ? planBySku.get(activeSubscriptions[0].planSku) : void 0;
74
- const confirmingPayment = checkoutSuccess && !isLoading && !hasActivePlan;
75
+ const pendingSku = checkoutSuccess ? readPendingCheckout() : void 0;
76
+ const pendingActive = pendingSku !== void 0 && activeSubscriptions.some((sub) => sub.planSku === pendingSku);
77
+ const confirmingPayment = checkoutSuccess && !isLoading && (pendingSku !== void 0 ? !pendingActive : !hasActivePlan);
78
+ useEffect(() => {
79
+ if (pendingActive) clearPendingCheckout();
80
+ }, [pendingActive]);
75
81
  useEffect(() => {
76
82
  if (!confirmingPayment) return;
77
83
  let attempts = 0;
@@ -120,7 +126,7 @@ const SubscriptionsPage = () => {
120
126
  className: "mb-6 rounded-xl border border-border bg-card p-4 text-sm",
121
127
  children: checkoutConfirmingText(checkoutPaid)
122
128
  }) : null,
123
- checkoutSuccess && hasActivePlan ? /* @__PURE__ */ jsx("div", {
129
+ checkoutSuccess && hasActivePlan && !confirmingPayment ? /* @__PURE__ */ jsx("div", {
124
130
  className: "mb-6 rounded-xl border border-green-500/30 bg-green-500/10 p-4 text-sm",
125
131
  children: planNote ? /* @__PURE__ */ jsxs(Fragment, { children: [
126
132
  planNote.text,
@@ -207,7 +213,7 @@ const SubscriptionsPage = () => {
207
213
  })
208
214
  })]
209
215
  }) : null,
210
- billingState === "empty" ? /* @__PURE__ */ jsx(ChoosePlanNotice, {
216
+ billingState === "empty" && !confirmingPayment ? /* @__PURE__ */ jsx(ChoosePlanNotice, {
211
217
  className: "mb-8 rounded-xl border border-dashed p-8 text-center",
212
218
  body: "You do not have a plan yet. Choose one to start using the API; your billing details appear here afterwards."
213
219
  }) : null,
@@ -1,8 +1,8 @@
1
- import { m as parseQuotaPeriodDays, p as parseMonthlyQuotaCalls, s as isActiveSubscription } from "./useDevPortalApiBaseUrl-BstWzITX.mjs";
1
+ import { m as parseQuotaPeriodDays, p as parseMonthlyQuotaCalls, s as isActiveSubscription } from "./useDevPortalApiBaseUrl-BiI2jFjU.mjs";
2
2
  import "./DevPortalBillingContext-CR83s4LO.mjs";
3
- import { t as useMySubscriptions } from "./useMySubscriptions-DuVq2tXL.mjs";
4
- import { t as usePlans } from "./usePlans-BF7Hy2qJ.mjs";
5
- import { n as useUsageRecent, t as useUsage } from "./useUsage-CJ9VIBc7.mjs";
3
+ import { t as useMySubscriptions } from "./useMySubscriptions-BYVIIWbe.mjs";
4
+ import { t as usePlans } from "./usePlans-HyhIIuJZ.mjs";
5
+ import { n as useUsageRecent, t as useUsage } from "./useUsage-B5x9YuKa.mjs";
6
6
  import { Head } from "@lukoweb/apitogo/components";
7
7
  import { Link as Link$1 } from "@lukoweb/apitogo/router";
8
8
  import { useState } from "react";
@@ -1,4 +1,4 @@
1
- import { i as useRevokeApiKey, r as useCreateApiKey, t as API_KEY_CAP_REACHED } from "./useApiKeys-CEwMzqju.mjs";
1
+ import { i as useRevokeApiKey, r as useCreateApiKey, t as API_KEY_CAP_REACHED } from "./useApiKeys-BOY3vIOr.mjs";
2
2
  import { Spinner } from "@lukoweb/apitogo/components";
3
3
  import { KeyRoundIcon, TrashIcon } from "@lukoweb/apitogo/icons";
4
4
  import { useState } from "react";
@@ -1,4 +1,4 @@
1
- import { _ as rateLimitCell, g as quotaCell } from "./useDevPortalApiBaseUrl-BstWzITX.mjs";
1
+ import { _ as rateLimitCell, g as quotaCell } from "./useDevPortalApiBaseUrl-BiI2jFjU.mjs";
2
2
 
3
3
  //#region src/utils/billing-state.ts
4
4
  /** Whether the pricing page has no plans to show yet because the answer is still on its way. */
package/dist/index.d.mts CHANGED
@@ -58,7 +58,8 @@ type DevPortalBillingConfig = {
58
58
  * The header API users send their key in, as the portal build knows it from the gateway config. Wins over the plans
59
59
  * backend's answer, which only knows the platform default.
60
60
  */
61
- keyHeader?: string; /** Local preview plan catalog (from `apitogo.config.json` `plans`). */
61
+ keyHeader?: string; /** The plan shown as "Popular"; none unless the tenant marked one (API-124). */
62
+ featuredPlanSku?: string; /** Local preview plan catalog (from `apitogo.config.json` `plans`). */
62
63
  previewPlans?: Array<{
63
64
  sku: string;
64
65
  displayName: string;
@@ -266,7 +267,7 @@ type PlansPreviewItem = {
266
267
  featured?: boolean;
267
268
  };
268
269
  /** Maps plans to landing `PlansPreviewSection` cards, highlighting the same plan as /pricing. */
269
- declare const toPlansPreviewItems: (plans: DevPortalPlan[]) => PlansPreviewItem[];
270
+ declare const toPlansPreviewItems: (plans: DevPortalPlan[], featuredPlanSku?: string) => PlansPreviewItem[];
270
271
  /**
271
272
  * Plan cards for the landing `PlansPreviewSection`, sourced like the /pricing page: the dev
272
273
  * portal backend when live, `modules.userPanel.plans.items` from `apitogo.config.json` in
package/dist/index.mjs CHANGED
@@ -1,15 +1,16 @@
1
- import { t as AccountPlansPage } from "./AccountPlansPage-0PYGy1aV.mjs";
1
+ import { t as AccountPlansPage } from "./AccountPlansPage-BuM7fe82.mjs";
2
2
  import { a as CREDENTIALS_PATH, c as SUBSCRIPTIONS_PATH, i as CONTACT_SALES_PATH, l as USAGE_PATH, o as LOGIN_NAV_PATH, r as CHECKOUT_PATH, s as PRICING_PATH, t as ACCOUNT_PLANS_PATH } from "./paths-DaVyTIDJ.mjs";
3
- import { d as listPlanFeatureLines, f as parseManifestPlans, h as pickFeaturedPlanSku, i as formatPlanDisplayPrice, o as formatPlanPriceUnit, s as isActiveSubscription, v as resolveDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-BstWzITX.mjs";
4
- import "./PricingCard-YJIEwydG.mjs";
3
+ import { d as listPlanFeatureLines, f as parseManifestPlans, h as pickFeaturedPlanSku, i as formatPlanDisplayPrice, o as formatPlanPriceUnit, s as isActiveSubscription, v as resolveDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-BiI2jFjU.mjs";
4
+ import "./PricingCard-DFuQEw7Y.mjs";
5
5
  import { n as useDevPortalBillingConfig, t as DevPortalBillingProvider } from "./DevPortalBillingContext-CR83s4LO.mjs";
6
6
  import { t as DEMO_GATEWAY_KEY_HEADER } from "./demo-session-yK9qEqV-.mjs";
7
- import { t as useMySubscriptions } from "./useMySubscriptions-DuVq2tXL.mjs";
8
- import { n as startPlansPrefetch, t as usePlans } from "./usePlans-BF7Hy2qJ.mjs";
9
- import { a as playgroundKeyIdentities, i as useRevokeApiKey, n as useApiKeys, r as useCreateApiKey, t as API_KEY_CAP_REACHED } from "./useApiKeys-CEwMzqju.mjs";
10
- import { n as useUsageRecent, t as useUsage } from "./useUsage-CJ9VIBc7.mjs";
11
- import { r as ApiKeysPanel, t as accountLayoutProps } from "./account-links-9h91uCGj.mjs";
12
- import { t as isPreviewPricingEnabled } from "./pricing-config-C44AIqql.mjs";
7
+ import { t as useMySubscriptions } from "./useMySubscriptions-BYVIIWbe.mjs";
8
+ import { n as startPlansPrefetch, t as usePlans } from "./usePlans-HyhIIuJZ.mjs";
9
+ import "./pending-checkout-DpVaNnTu.mjs";
10
+ import { a as playgroundKeyIdentities, i as useRevokeApiKey, n as useApiKeys, r as useCreateApiKey, t as API_KEY_CAP_REACHED } from "./useApiKeys-BOY3vIOr.mjs";
11
+ import { n as useUsageRecent, t as useUsage } from "./useUsage-B5x9YuKa.mjs";
12
+ import { r as ApiKeysPanel, t as accountLayoutProps } from "./account-links-Drofs-f-.mjs";
13
+ import { t as isPreviewPricingEnabled } from "./pricing-config-Bc6eWFUO.mjs";
13
14
  import { Head, Link } from "@lukoweb/apitogo/components";
14
15
  import { ArrowRightIcon, KeyRoundIcon, StarsIcon } from "@lukoweb/apitogo/icons";
15
16
  import { Outlet } from "@lukoweb/apitogo/router";
@@ -366,13 +367,13 @@ async function getSignedInIdentities(apiBaseUrl, fetchImpl = fetch) {
366
367
 
367
368
  //#endregion
368
369
  //#region src/DevPortalBillingPlugin.tsx
369
- const AccountPlansPage$1 = lazy(() => import("./AccountPlansPage-0PYGy1aV.mjs").then((n) => n.n));
370
- const CheckoutPage = lazy(() => import("./CheckoutPage-Bpa128i2.mjs"));
371
- const ContactSalesPage = lazy(() => import("./ContactSalesPage-BslD3Yk4.mjs"));
372
- const CredentialsPage = lazy(() => import("./CredentialsPage-C4qpBVrh.mjs"));
373
- const PricingPage = lazy(() => import("./PricingPage-DIhoFOd9.mjs"));
374
- const SubscriptionsPage = lazy(() => import("./SubscriptionsPage-D7hsYtTB.mjs"));
375
- const UsagePage = lazy(() => import("./UsagePage-Di9kQ_3Z.mjs"));
370
+ const AccountPlansPage$1 = lazy(() => import("./AccountPlansPage-BuM7fe82.mjs").then((n) => n.n));
371
+ const CheckoutPage = lazy(() => import("./CheckoutPage-BSiADcI6.mjs"));
372
+ const ContactSalesPage = lazy(() => import("./ContactSalesPage-BqqDmorF.mjs"));
373
+ const CredentialsPage = lazy(() => import("./CredentialsPage-DjNvBBLb.mjs"));
374
+ const PricingPage = lazy(() => import("./PricingPage-SpL81vpd.mjs"));
375
+ const SubscriptionsPage = lazy(() => import("./SubscriptionsPage-CwsBtNld.mjs"));
376
+ const UsagePage = lazy(() => import("./UsagePage-Dw5Uepg3.mjs"));
376
377
  function resolvePricingEnabled(config) {
377
378
  if (config.__meta?.pricingEnabled !== void 0) return config.__meta.pricingEnabled;
378
379
  return isPreviewPricingEnabled();
@@ -495,8 +496,8 @@ function useGatewayCall() {
495
496
  //#endregion
496
497
  //#region src/hooks/usePlansPreview.ts
497
498
  /** Maps plans to landing `PlansPreviewSection` cards, highlighting the same plan as /pricing. */
498
- const toPlansPreviewItems = (plans) => {
499
- const featuredSku = pickFeaturedPlanSku(plans);
499
+ const toPlansPreviewItems = (plans, featuredPlanSku) => {
500
+ const featuredSku = pickFeaturedPlanSku(plans, featuredPlanSku);
500
501
  return plans.map((plan) => ({
501
502
  name: plan.displayName,
502
503
  price: formatPlanDisplayPrice(plan),
@@ -513,7 +514,8 @@ const toPlansPreviewItems = (plans) => {
513
514
  */
514
515
  const usePlansPreview = () => {
515
516
  const { data } = usePlans();
516
- return toPlansPreviewItems(data?.plans ?? parseManifestPlans().plans);
517
+ const { featuredPlanSku } = useDevPortalBillingConfig();
518
+ return toPlansPreviewItems(data?.plans ?? parseManifestPlans().plans, featuredPlanSku);
517
519
  };
518
520
 
519
521
  //#endregion
@@ -0,0 +1,45 @@
1
+ //#region src/utils/pending-checkout.ts
2
+ const STORAGE_KEY = "apitogo-pending-checkout";
3
+ /** How long a checkout counts as waiting for its payment to be confirmed. Stripe usually confirms within seconds. */
4
+ const PENDING_FOR_MS = 600 * 1e3;
5
+ const storage = () => {
6
+ try {
7
+ return typeof sessionStorage === "undefined" ? void 0 : sessionStorage;
8
+ } catch {
9
+ return;
10
+ }
11
+ };
12
+ /**
13
+ * Remembers the plan the customer is about to pay for, as checkout sends them to Stripe. Back in the portal the payment
14
+ * is confirmed a few seconds later; until then the pages must not offer that plan again (API-128), even when the
15
+ * customer already holds another plan. Only this browser tab knows it.
16
+ */
17
+ function rememberPendingCheckout(sku, now = Date.now()) {
18
+ try {
19
+ storage()?.setItem(STORAGE_KEY, JSON.stringify({
20
+ sku,
21
+ at: now
22
+ }));
23
+ } catch {}
24
+ }
25
+ /** The plan still waiting for its payment to be confirmed, or undefined when none is (or it is too old). */
26
+ function readPendingCheckout(now = Date.now()) {
27
+ try {
28
+ const raw = storage()?.getItem(STORAGE_KEY);
29
+ if (!raw) return void 0;
30
+ const pending = JSON.parse(raw);
31
+ if (typeof pending.sku !== "string" || typeof pending.at !== "number") return void 0;
32
+ return now - pending.at <= PENDING_FOR_MS ? pending.sku : void 0;
33
+ } catch {
34
+ return;
35
+ }
36
+ }
37
+ /** The payment was confirmed (the plan is active) or given up on. */
38
+ function clearPendingCheckout() {
39
+ try {
40
+ storage()?.removeItem(STORAGE_KEY);
41
+ } catch {}
42
+ }
43
+
44
+ //#endregion
45
+ export { readPendingCheckout as n, rememberPendingCheckout as r, clearPendingCheckout as t };
@@ -1,4 +1,4 @@
1
- import { n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-BstWzITX.mjs";
1
+ import { n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-BiI2jFjU.mjs";
2
2
  import { n as useDevPortalBillingConfig } from "./DevPortalBillingContext-CR83s4LO.mjs";
3
3
  import { n as readProblemDetail } from "./problem-details-n20-dy3w.mjs";
4
4
  import { n as createDemoApiKeys, t as DEMO_GATEWAY_KEY_HEADER } from "./demo-session-yK9qEqV-.mjs";
@@ -115,9 +115,14 @@ function formatPlanDisplayPrice(plan) {
115
115
  function formatPlanPriceUnit(plan) {
116
116
  return isPlanCustomPriced(plan) ? "talk to us" : formatBillingPeriod(plan.billingPeriod);
117
117
  }
118
- /** SKU of the plan to highlight: `developer` if present, else the first low-priced paid plan. */
119
- function pickFeaturedPlanSku(plans) {
120
- return plans.find((p) => p.sku === "developer")?.sku ?? plans.find((p) => p.priceAmount > 0 && p.priceAmount < 50)?.sku ?? plans.find((p) => p.priceAmount > 0)?.sku;
118
+ /**
119
+ * SKU of the plan to highlight: the one the tenant marked `featured`, when the catalog has it. It used to be guessed
120
+ * (a plan called "developer", or the first cheap paid one), which put "Popular" on a plan the tenant never chose
121
+ * (API-124).
122
+ */
123
+ function pickFeaturedPlanSku(plans, featuredPlanSku) {
124
+ const wanted = featuredPlanSku?.trim().toLowerCase();
125
+ return wanted ? plans.find((p) => p.sku.toLowerCase() === wanted)?.sku : void 0;
121
126
  }
122
127
  function formatRateLimit(rate) {
123
128
  if (!rate || typeof rate !== "object") return null;
@@ -172,17 +177,12 @@ function parseLimits(limitsJson) {
172
177
  }
173
178
  }
174
179
  /** Marketing bullets keyed by common sample SKUs when limitsJson is sparse. */
175
- const SAMPLE_PLAN_FEATURE_EXTRAS = {
176
- free: ["All endpoints"],
177
- developer: ["Webhooks included", "Email support"],
178
- team: ["5 team seats", "Priority support"],
179
- enterprise: [
180
- "Unlimited requests",
181
- "Custom rate limits",
182
- "SSO & audit logs",
183
- "Dedicated SLA"
184
- ]
185
- };
180
+ /**
181
+ * Lines a plan gets from its name alone. Empty on purpose: "SSO & audit logs" or "Dedicated SLA" under a plan called
182
+ * Enterprise were promises the tenant never made (API-124). A plan shows its limits, its endpoints and what the
183
+ * tenant wrote.
184
+ */
185
+ const SAMPLE_PLAN_FEATURE_EXTRAS = {};
186
186
  function listPlanFeatureLines(plan) {
187
187
  if (plan.features && plan.features.length > 0) return plan.features;
188
188
  const lines = [];
@@ -1,4 +1,4 @@
1
- import { n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-BstWzITX.mjs";
1
+ import { n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-BiI2jFjU.mjs";
2
2
  import { n as useDevPortalBillingConfig } from "./DevPortalBillingContext-CR83s4LO.mjs";
3
3
  import { i as createDemoMeResponse } from "./demo-session-yK9qEqV-.mjs";
4
4
  import { useQuery } from "@lukoweb/apitogo/react-query";
@@ -1,4 +1,4 @@
1
- import { c as isPlaceholderDevPortalApiUrl, f as parseManifestPlans, n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-BstWzITX.mjs";
1
+ import { c as isPlaceholderDevPortalApiUrl, f as parseManifestPlans, n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-BiI2jFjU.mjs";
2
2
  import { n as useDevPortalBillingConfig } from "./DevPortalBillingContext-CR83s4LO.mjs";
3
3
  import { useQuery } from "@lukoweb/apitogo/react-query";
4
4
 
@@ -1,4 +1,4 @@
1
- import { n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-BstWzITX.mjs";
1
+ import { n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-BiI2jFjU.mjs";
2
2
  import { useQuery } from "@lukoweb/apitogo/react-query";
3
3
  import { useAuth } from "@lukoweb/apitogo/hooks";
4
4
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lukoweb/apitogo-plugin-dev-portal-billing",
3
- "version": "0.2.54",
3
+ "version": "0.2.56",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/zuplo/zudoku",
@@ -46,8 +46,8 @@
46
46
  "react": "19.2.4",
47
47
  "react-dom": "19.2.4",
48
48
  "tsdown": "0.20.3",
49
- "@lukoweb/apitogo": "0.2.54",
50
- "@lukoweb/apitogo-module-user-panel": "0.2.54"
49
+ "@lukoweb/apitogo": "0.2.56",
50
+ "@lukoweb/apitogo-module-user-panel": "0.2.56"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "react": ">=19.2.0",