@lukoweb/apitogo-plugin-dev-portal-billing 0.2.38 → 0.2.39

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.
@@ -1,8 +1,8 @@
1
- import { c as isActiveSubscription, n as useDevPortalPreviewMode, p as parseManifestPlans, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-CgygF2z5.mjs";
2
- import { t as PricingCard } from "./PricingCard-Wra2FUX9.mjs";
1
+ import { c as isActiveSubscription, n as useDevPortalPreviewMode, p as parseManifestPlans, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-Dk00H8xy.mjs";
2
+ import { t as PricingCard } from "./PricingCard-D-owYlf3.mjs";
3
3
  import { n as useDevPortalBillingConfig } from "./DevPortalBillingContext-CR83s4LO.mjs";
4
- import { t as useMySubscriptions } from "./useMySubscriptions-CLh3eFY4.mjs";
5
- import { t as usePlans } from "./usePlans-DUex2tzT.mjs";
4
+ import { t as useMySubscriptions } from "./useMySubscriptions-BFe0t1Ru.mjs";
5
+ import { t as usePlans } from "./usePlans-DVGt2Myv.mjs";
6
6
  import { Head } from "@lukoweb/apitogo/components";
7
7
  import { CreditCardIcon } from "@lukoweb/apitogo/icons";
8
8
  import { useSearchParams } from "@lukoweb/apitogo/router";
@@ -1,6 +1,6 @@
1
1
  import { o as PRICING_PATH, t as ACCOUNT_PLANS_PATH } from "./paths-ujy9wjVK.mjs";
2
- import { n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-CgygF2z5.mjs";
3
- import { t as FREE_CHECKOUT_MARKER } from "./billing-state-CIZ0pR37.mjs";
2
+ import { n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-Dk00H8xy.mjs";
3
+ import { t as FREE_CHECKOUT_MARKER } from "./billing-state-DjUXQYSr.mjs";
4
4
  import { ShieldIcon } from "@lukoweb/apitogo/icons";
5
5
  import { Link, useNavigate, useSearchParams } from "@lukoweb/apitogo/router";
6
6
  import { useEffect } from "react";
@@ -1,7 +1,7 @@
1
1
  import { o as PRICING_PATH } from "./paths-ujy9wjVK.mjs";
2
- import { n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl, u as isPlanCustomPriced } from "./useDevPortalApiBaseUrl-CgygF2z5.mjs";
2
+ import { n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl, u as isPlanCustomPriced } from "./useDevPortalApiBaseUrl-Dk00H8xy.mjs";
3
3
  import "./DevPortalBillingContext-CR83s4LO.mjs";
4
- import { t as usePlans } from "./usePlans-DUex2tzT.mjs";
4
+ import { t as usePlans } from "./usePlans-DVGt2Myv.mjs";
5
5
  import { Link, useSearchParams } from "@lukoweb/apitogo/router";
6
6
  import { useState } from "react";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -1,8 +1,8 @@
1
1
  import { t as ACCOUNT_PLANS_PATH } from "./paths-ujy9wjVK.mjs";
2
- import { n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-CgygF2z5.mjs";
2
+ import { n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-Dk00H8xy.mjs";
3
3
  import { n as useDevPortalBillingConfig } from "./DevPortalBillingContext-CR83s4LO.mjs";
4
4
  import { n as createDemoCredentials } from "./demo-session-Cez740bi.mjs";
5
- import { r as useApiKeys, t as ApiKeysPanel } from "./ApiKeysPanel-DrArkHXf.mjs";
5
+ import { a as useApiKeys, n as apiReferencePath, r as ApiKeysPanel } from "./account-links-CH6HyjOb.mjs";
6
6
  import { t as ChoosePlanNotice } from "./ChoosePlanNotice-DITJNrh1.mjs";
7
7
  import { Head } from "@lukoweb/apitogo/components";
8
8
  import { KeyRoundIcon } from "@lukoweb/apitogo/icons";
@@ -58,7 +58,8 @@ const CredentialsPage = () => {
58
58
  const isDemoSession = useIsDemoSession();
59
59
  const { data: credentials, isLoading, error } = useCredentials();
60
60
  const { data: apiKeys, isLoading: keysLoading, error: keysError } = useApiKeys();
61
- const { gatewayBaseUrl: configuredGatewayBaseUrl } = useDevPortalBillingConfig();
61
+ const { gatewayBaseUrl: configuredGatewayBaseUrl, links } = useDevPortalBillingConfig();
62
+ const apiReferenceLink = apiReferencePath(links);
62
63
  const gatewayBaseUrl = credentials?.gatewayBaseUrl?.trim() || apiKeys?.gatewayBaseUrl?.trim() || configuredGatewayBaseUrl?.trim() || zudokuConfig.__meta?.gatewayBaseUrl?.trim() || null;
63
64
  const header = apiKeys?.header ?? "Ocp-Apim-Subscription-Key";
64
65
  const plans = apiKeys?.plans ?? [];
@@ -160,14 +161,14 @@ const CredentialsPage = () => {
160
161
  to: "/account/subscriptions",
161
162
  children: "My subscriptions"
162
163
  })
163
- }), /* @__PURE__ */ jsx(Button$1, {
164
+ }), apiReferenceLink ? /* @__PURE__ */ jsx(Button$1, {
164
165
  variant: "outline",
165
166
  asChild: true,
166
167
  children: /* @__PURE__ */ jsx(Link$1, {
167
- to: "/api",
168
+ to: apiReferenceLink,
168
169
  children: "API reference"
169
170
  })
170
- })]
171
+ }) : null]
171
172
  })
172
173
  ]
173
174
  });
@@ -1,5 +1,5 @@
1
1
  import { n as CHECKOUT_PATH, r as CONTACT_SALES_PATH } from "./paths-ujy9wjVK.mjs";
2
- import { a as formatPlanDisplayPrice, c as isActiveSubscription, d as isPlanFree, f as listPlanFeatureLines, s as formatPlanPriceUnit, u as isPlanCustomPriced } from "./useDevPortalApiBaseUrl-CgygF2z5.mjs";
2
+ import { a as formatPlanDisplayPrice, c as isActiveSubscription, d as isPlanFree, f as listPlanFeatureLines, s as formatPlanPriceUnit, u as isPlanCustomPriced } from "./useDevPortalApiBaseUrl-Dk00H8xy.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";
@@ -1,9 +1,9 @@
1
- import { h as pickFeaturedPlanSku, n as useDevPortalPreviewMode, p as parseManifestPlans } from "./useDevPortalApiBaseUrl-CgygF2z5.mjs";
2
- import { t as PricingCard } from "./PricingCard-Wra2FUX9.mjs";
1
+ import { g as pickFeaturedPlanSku, n as useDevPortalPreviewMode, p as parseManifestPlans } from "./useDevPortalApiBaseUrl-Dk00H8xy.mjs";
2
+ import { t as PricingCard } from "./PricingCard-D-owYlf3.mjs";
3
3
  import { n as useDevPortalBillingConfig } from "./DevPortalBillingContext-CR83s4LO.mjs";
4
- import { t as usePlans } from "./usePlans-DUex2tzT.mjs";
4
+ import { t as usePlans } from "./usePlans-DVGt2Myv.mjs";
5
5
  import { t as isPreviewPricingEnabled } from "./pricing-config-CfhRX9tk.mjs";
6
- import { a as isPricingLoading, n as buildCompareRows } from "./billing-state-CIZ0pR37.mjs";
6
+ import { a as isPricingLoading, n as buildCompareRows } from "./billing-state-DjUXQYSr.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";
@@ -1,10 +1,10 @@
1
1
  import { i as CREDENTIALS_PATH, t as ACCOUNT_PLANS_PATH } from "./paths-ujy9wjVK.mjs";
2
- import { c as isActiveSubscription, i as formatBillingPeriod, m as parseMonthlyQuotaCalls, o as formatPlanPrice, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-CgygF2z5.mjs";
2
+ import { c as isActiveSubscription, i as formatBillingPeriod, m as parseMonthlyQuotaCalls, o as formatPlanPrice, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-Dk00H8xy.mjs";
3
3
  import { n as useDevPortalBillingConfig } from "./DevPortalBillingContext-CR83s4LO.mjs";
4
- import { t as useMySubscriptions } from "./useMySubscriptions-CLh3eFY4.mjs";
5
- import { t as usePlans } from "./usePlans-DUex2tzT.mjs";
6
- import { t as useUsage } from "./useUsage-Cx2GB3wg.mjs";
7
- import { i as hasPaymentProvider, r as checkoutConfirmingText } from "./billing-state-CIZ0pR37.mjs";
4
+ import { t as useMySubscriptions } from "./useMySubscriptions-BFe0t1Ru.mjs";
5
+ import { t as usePlans } from "./usePlans-DVGt2Myv.mjs";
6
+ import { t as useUsage } from "./useUsage-DOUibue4.mjs";
7
+ import { i as hasPaymentProvider, r as checkoutConfirmingText } from "./billing-state-DjUXQYSr.mjs";
8
8
  import { t as ChoosePlanNotice } from "./ChoosePlanNotice-DITJNrh1.mjs";
9
9
  import { Head, Spinner } from "@lukoweb/apitogo/components";
10
10
  import { Link as Link$1, useSearchParams } from "@lukoweb/apitogo/router";
@@ -46,7 +46,7 @@ const SubscriptionsPage = () => {
46
46
  const { data: me, isLoading, refetch } = useMySubscriptions();
47
47
  const { data: catalog } = usePlans();
48
48
  const billingPortal = useBillingPortal();
49
- const { data: usage } = useUsage("mtd");
49
+ const { data: usage } = useUsage("30d");
50
50
  const planBySku = new Map((catalog?.plans ?? []).map((plan) => [plan.sku, plan]));
51
51
  const subscriptions = me?.subscriptions ?? [];
52
52
  const activeSubscription = subscriptions.find((sub) => isActiveSubscription(sub.status));
@@ -1,5 +1,8 @@
1
- import "./useDevPortalApiBaseUrl-CgygF2z5.mjs";
2
- import { t as useUsage } from "./useUsage-Cx2GB3wg.mjs";
1
+ import { c as isActiveSubscription, h as parseQuotaPeriodDays, m as parseMonthlyQuotaCalls } from "./useDevPortalApiBaseUrl-Dk00H8xy.mjs";
2
+ import "./DevPortalBillingContext-CR83s4LO.mjs";
3
+ import { t as useMySubscriptions } from "./useMySubscriptions-BFe0t1Ru.mjs";
4
+ import { t as usePlans } from "./usePlans-DVGt2Myv.mjs";
5
+ import { t as useUsage } from "./useUsage-DOUibue4.mjs";
3
6
  import { Head } from "@lukoweb/apitogo/components";
4
7
  import { Link as Link$1 } from "@lukoweb/apitogo/router";
5
8
  import { useState } from "react";
@@ -7,6 +10,32 @@ import { jsx, jsxs } from "react/jsx-runtime";
7
10
  import { useAuth } from "@lukoweb/apitogo/hooks";
8
11
  import { Button as Button$1 } from "@lukoweb/apitogo/ui/Button.js";
9
12
 
13
+ //#region src/utils/quota-summary.ts
14
+ /**
15
+ * What the usage page says about the plan's quota: how much is used, how much is left, and when it starts over.
16
+ *
17
+ * The gateway counts the quota over a window of `periodDays` days that starts with the first counted call, so there is
18
+ * no calendar date to promise: the card says "N days after the first call of the period" (the `Retry-After` header of a
19
+ * refused call is the exact wait). A subscription that knows its period end shows that date.
20
+ */
21
+ function quotaSummary(used, limit, periodDays, periodEnd) {
22
+ const end = periodEnd ? new Date(periodEnd) : void 0;
23
+ const resetsOn = end && !Number.isNaN(end.getTime()) ? end.toLocaleDateString(void 0, {
24
+ day: "numeric",
25
+ month: "short",
26
+ year: "numeric",
27
+ timeZone: "UTC"
28
+ }) : `${periodDays} days after the first call of the period`;
29
+ return {
30
+ used,
31
+ limit,
32
+ remaining: Math.max(0, limit - used),
33
+ exhausted: used >= limit,
34
+ resetsOn
35
+ };
36
+ }
37
+
38
+ //#endregion
10
39
  //#region src/pages/UsagePage.tsx
11
40
  const RANGES = [
12
41
  {
@@ -36,6 +65,14 @@ const UsagePage = () => {
36
65
  const auth = useAuth();
37
66
  const [range, setRange] = useState("30d");
38
67
  const { data, isLoading, isError } = useUsage(range);
68
+ const { data: quotaUsage } = useUsage("30d");
69
+ const { data: mine } = useMySubscriptions();
70
+ const { data: catalog } = usePlans();
71
+ const subscription = mine?.subscriptions.find((sub) => isActiveSubscription(sub.status));
72
+ const plan = subscription ? catalog?.plans.find((item) => item.sku === subscription.planSku) : void 0;
73
+ const quota = plan ? parseMonthlyQuotaCalls(plan.limitsJson) : null;
74
+ const periodDays = plan ? parseQuotaPeriodDays(plan.limitsJson) : null;
75
+ const summary = quota != null && periodDays != null ? quotaSummary(quotaUsage?.requestCount ?? 0, quota, periodDays, subscription?.currentPeriodEnd) : null;
39
76
  const maxBar = Math.max(1, ...data?.series.map((point) => point.requestCount) ?? [1]);
40
77
  const endpointTotal = data?.byEndpoint.reduce((sum, item) => sum + item.count, 0) ?? 0;
41
78
  if (!auth.isAuthenticated) return /* @__PURE__ */ jsxs("div", {
@@ -122,6 +159,33 @@ const UsagePage = () => {
122
159
  })
123
160
  ]
124
161
  }),
162
+ summary ? /* @__PURE__ */ jsxs("div", {
163
+ className: "mb-5 rounded-xl border border-border bg-card p-4",
164
+ children: [
165
+ /* @__PURE__ */ jsx("div", {
166
+ className: "text-xs uppercase tracking-[0.05em] text-muted-foreground",
167
+ children: "Plan quota"
168
+ }),
169
+ /* @__PURE__ */ jsxs("div", {
170
+ className: "mt-1.5 text-2xl font-bold",
171
+ children: [
172
+ formatNumber(summary.used),
173
+ " of ",
174
+ formatNumber(summary.limit),
175
+ " used"
176
+ ]
177
+ }),
178
+ /* @__PURE__ */ jsxs("div", {
179
+ className: "mt-1 text-sm text-muted-foreground",
180
+ children: [
181
+ summary.exhausted ? "The quota is used up. Calls are refused (429); the Retry-After header says how long to wait." : `${formatNumber(summary.remaining)} left`,
182
+ " · ",
183
+ "resets ",
184
+ summary.resetsOn
185
+ ]
186
+ })
187
+ ]
188
+ }) : null,
125
189
  /* @__PURE__ */ jsxs("div", {
126
190
  className: "mb-5 rounded-xl border border-border bg-card p-6",
127
191
  children: [/* @__PURE__ */ jsxs("h2", {
@@ -1,4 +1,4 @@
1
- import { n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-CgygF2z5.mjs";
1
+ import { n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-Dk00H8xy.mjs";
2
2
  import { n as useDevPortalBillingConfig } from "./DevPortalBillingContext-CR83s4LO.mjs";
3
3
  import { t as createDemoApiKeys } from "./demo-session-Cez740bi.mjs";
4
4
  import { Spinner } from "@lukoweb/apitogo/components";
@@ -282,4 +282,22 @@ const ApiKeysPanel = ({ plan, header, readOnly = false }) => {
282
282
  };
283
283
 
284
284
  //#endregion
285
- export { useRevokeApiKey as a, useCreateApiKey as i, API_KEY_CAP_REACHED as n, useApiKeys as r, ApiKeysPanel as t };
285
+ //#region src/utils/account-links.ts
286
+ /**
287
+ * What the account pages' side menu and links use, from the places the portal really has. The generated wrapper passes
288
+ * `links`; a portal built with an older wrapper has none, and keeps the old defaults (`/introduction` and `/api`).
289
+ */
290
+ function accountLayoutProps(options) {
291
+ return {
292
+ includeBillingNav: true,
293
+ includeOverview: false,
294
+ ...options.links ? { docsPath: options.links.docs ?? null } : {}
295
+ };
296
+ }
297
+ /** The API reference link of the credentials page; undefined when the portal has no such page. */
298
+ function apiReferencePath(links) {
299
+ return links ? links.api : "/api";
300
+ }
301
+
302
+ //#endregion
303
+ export { useApiKeys as a, API_KEY_CAP_REACHED as i, apiReferencePath as n, useCreateApiKey as o, ApiKeysPanel as r, useRevokeApiKey as s, accountLayoutProps as t };
@@ -1,4 +1,4 @@
1
- import { _ as rateLimitCell, g as quotaCell } from "./useDevPortalApiBaseUrl-CgygF2z5.mjs";
1
+ import { _ as quotaCell, v as rateLimitCell } from "./useDevPortalApiBaseUrl-Dk00H8xy.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
@@ -42,7 +42,16 @@ type DevPortalBillingConfig = {
42
42
  compareRows?: DevPortalPricingCompareRow[];
43
43
  faqs?: DevPortalPricingFaq[];
44
44
  }; /** Billing provider of the plans service (`none`, `stripe`, ...). `none`: no payment is offered, so no payment page. */
45
- billing?: string; /** Address of the plans backend; the portal's generated wrapper passes it. The plans request starts from it early. */
45
+ billing?: string;
46
+ /**
47
+ * Where the account pages link to, as the portal really has it: the docs (a path or an external address) and the API
48
+ * reference page. A missing entry means the portal has none, so no link is shown. Absent altogether (an older wrapper):
49
+ * the old defaults, `/introduction` and `/api`.
50
+ */
51
+ links?: {
52
+ docs?: string;
53
+ api?: string;
54
+ }; /** Address of the plans backend; the portal's generated wrapper passes it. The plans request starts from it early. */
46
55
  apiBaseUrl?: string; /** Live gateway URL (publish bake or explicit plugin option). */
47
56
  gatewayBaseUrl?: string | null; /** Local preview plan catalog (from `apitogo.config.json` `plans`). */
48
57
  previewPlans?: Array<{
package/dist/index.mjs CHANGED
@@ -1,12 +1,12 @@
1
- import { t as AccountPlansPage } from "./AccountPlansPage-CXOfTwlX.mjs";
1
+ import { t as AccountPlansPage } from "./AccountPlansPage-wrL7BtXu.mjs";
2
2
  import { a as LOGIN_NAV_PATH, c as USAGE_PATH, i as CREDENTIALS_PATH, n as CHECKOUT_PATH, o as PRICING_PATH, r as CONTACT_SALES_PATH, s as SUBSCRIPTIONS_PATH, t as ACCOUNT_PLANS_PATH } from "./paths-ujy9wjVK.mjs";
3
- import { a as formatPlanDisplayPrice, c as isActiveSubscription, f as listPlanFeatureLines, h as pickFeaturedPlanSku, p as parseManifestPlans, s as formatPlanPriceUnit, v as resolveDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-CgygF2z5.mjs";
4
- import "./PricingCard-Wra2FUX9.mjs";
3
+ import { a as formatPlanDisplayPrice, c as isActiveSubscription, f as listPlanFeatureLines, g as pickFeaturedPlanSku, p as parseManifestPlans, s as formatPlanPriceUnit, y as resolveDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-Dk00H8xy.mjs";
4
+ import "./PricingCard-D-owYlf3.mjs";
5
5
  import { t as DevPortalBillingProvider } from "./DevPortalBillingContext-CR83s4LO.mjs";
6
- import { t as useMySubscriptions } from "./useMySubscriptions-CLh3eFY4.mjs";
7
- import { n as startPlansPrefetch, t as usePlans } from "./usePlans-DUex2tzT.mjs";
8
- import { a as useRevokeApiKey, i as useCreateApiKey, n as API_KEY_CAP_REACHED, r as useApiKeys, t as ApiKeysPanel } from "./ApiKeysPanel-DrArkHXf.mjs";
9
- import { n as useUsageRecent, t as useUsage } from "./useUsage-Cx2GB3wg.mjs";
6
+ import { t as useMySubscriptions } from "./useMySubscriptions-BFe0t1Ru.mjs";
7
+ import { n as startPlansPrefetch, t as usePlans } from "./usePlans-DVGt2Myv.mjs";
8
+ import { a as useApiKeys, i as API_KEY_CAP_REACHED, o as useCreateApiKey, r as ApiKeysPanel, s as useRevokeApiKey, t as accountLayoutProps } from "./account-links-CH6HyjOb.mjs";
9
+ import { n as useUsageRecent, t as useUsage } from "./useUsage-DOUibue4.mjs";
10
10
  import { t as isPreviewPricingEnabled } from "./pricing-config-CfhRX9tk.mjs";
11
11
  import { Head, Link } from "@lukoweb/apitogo/components";
12
12
  import { ArrowRightIcon, KeyRoundIcon, StarsIcon } from "@lukoweb/apitogo/icons";
@@ -363,19 +363,20 @@ async function getSignedInIdentities(apiBaseUrl, fetchImpl = fetch) {
363
363
 
364
364
  //#endregion
365
365
  //#region src/DevPortalBillingPlugin.tsx
366
- const AccountPlansPage$1 = lazy(() => import("./AccountPlansPage-CXOfTwlX.mjs").then((n) => n.n));
367
- const CheckoutPage = lazy(() => import("./CheckoutPage-DiYurW2O.mjs"));
368
- const ContactSalesPage = lazy(() => import("./ContactSalesPage-Vjzm-RxO.mjs"));
369
- const CredentialsPage = lazy(() => import("./CredentialsPage-Ir8Gf62T.mjs"));
370
- const PricingPage = lazy(() => import("./PricingPage-DodoDd3h.mjs"));
371
- const SubscriptionsPage = lazy(() => import("./SubscriptionsPage-vF7jYwOC.mjs"));
372
- const UsagePage = lazy(() => import("./UsagePage-C4K_arXh.mjs"));
366
+ const AccountPlansPage$1 = lazy(() => import("./AccountPlansPage-wrL7BtXu.mjs").then((n) => n.n));
367
+ const CheckoutPage = lazy(() => import("./CheckoutPage-6nFq9GKO.mjs"));
368
+ const ContactSalesPage = lazy(() => import("./ContactSalesPage-Imh39E8z.mjs"));
369
+ const CredentialsPage = lazy(() => import("./CredentialsPage-BdSCmTR9.mjs"));
370
+ const PricingPage = lazy(() => import("./PricingPage-BTPQ-ntp.mjs"));
371
+ const SubscriptionsPage = lazy(() => import("./SubscriptionsPage-CIiPzK64.mjs"));
372
+ const UsagePage = lazy(() => import("./UsagePage-Xz2KEk2B.mjs"));
373
373
  function resolvePricingEnabled(config) {
374
374
  if (config.__meta?.pricingEnabled !== void 0) return config.__meta.pricingEnabled;
375
375
  return isPreviewPricingEnabled();
376
376
  }
377
377
  const devPortalBillingPlugin = createPlugin((options = {}) => {
378
378
  if (typeof window !== "undefined" && window.location.pathname.replace(/\/+$/, "") === PRICING_PATH) startPlansPrefetch(resolveDevPortalApiBaseUrl(options.apiBaseUrl));
379
+ const accountLayoutProps$1 = accountLayoutProps(options);
379
380
  return {
380
381
  transformConfig: async ({ config, merge }) => {
381
382
  const pricingEnabled = resolvePricingEnabled(config);
@@ -433,7 +434,7 @@ const devPortalBillingPlugin = createPlugin((options = {}) => {
433
434
  path: ACCOUNT_PLANS_PATH,
434
435
  handle: { layout: "default" },
435
436
  element: /* @__PURE__ */ jsx(AccountLayout, {
436
- includeBillingNav: true,
437
+ ...accountLayoutProps$1,
437
438
  children: /* @__PURE__ */ jsx(AccountPlansPage$1, {})
438
439
  })
439
440
  },
@@ -441,7 +442,7 @@ const devPortalBillingPlugin = createPlugin((options = {}) => {
441
442
  path: SUBSCRIPTIONS_PATH,
442
443
  handle: { layout: "default" },
443
444
  element: /* @__PURE__ */ jsx(AccountLayout, {
444
- includeBillingNav: true,
445
+ ...accountLayoutProps$1,
445
446
  children: /* @__PURE__ */ jsx(SubscriptionsPage, {})
446
447
  })
447
448
  },
@@ -449,7 +450,7 @@ const devPortalBillingPlugin = createPlugin((options = {}) => {
449
450
  path: CREDENTIALS_PATH,
450
451
  handle: { layout: "default" },
451
452
  element: /* @__PURE__ */ jsx(AccountLayout, {
452
- includeBillingNav: true,
453
+ ...accountLayoutProps$1,
453
454
  children: /* @__PURE__ */ jsx(CredentialsPage, {})
454
455
  })
455
456
  },
@@ -457,7 +458,7 @@ const devPortalBillingPlugin = createPlugin((options = {}) => {
457
458
  path: USAGE_PATH,
458
459
  handle: { layout: "default" },
459
460
  element: /* @__PURE__ */ jsx(AccountLayout, {
460
- includeBillingNav: true,
461
+ ...accountLayoutProps$1,
461
462
  children: /* @__PURE__ */ jsx(UsagePage, {})
462
463
  })
463
464
  }
@@ -204,6 +204,18 @@ function listPlanFeatureLines(plan) {
204
204
  function isActiveSubscription(status) {
205
205
  return status.toLowerCase() === "active";
206
206
  }
207
+ /** The length in days of the plan's quota period (30 when the plan does not say), or null when the plan has no quota. */
208
+ function parseQuotaPeriodDays(limitsJson) {
209
+ if (!limitsJson?.trim()) return null;
210
+ try {
211
+ const quota = JSON.parse(limitsJson).quota;
212
+ if (!quota || typeof quota !== "object") return null;
213
+ const { renewalPeriodDays } = quota;
214
+ return renewalPeriodDays != null && renewalPeriodDays > 0 ? renewalPeriodDays : 30;
215
+ } catch {
216
+ return null;
217
+ }
218
+ }
207
219
  /** Returns monthly call quota from plan limitsJson, or null when unknown/unlimited. */
208
220
  function parseMonthlyQuotaCalls(limitsJson) {
209
221
  if (!limitsJson?.trim()) return null;
@@ -238,4 +250,4 @@ function useDevPortalPreviewMode() {
238
250
  }
239
251
 
240
252
  //#endregion
241
- export { rateLimitCell as _, formatPlanDisplayPrice as a, isActiveSubscription as c, isPlanFree as d, listPlanFeatureLines as f, quotaCell as g, pickFeaturedPlanSku as h, formatBillingPeriod as i, isPlaceholderDevPortalApiUrl as l, parseMonthlyQuotaCalls as m, useDevPortalPreviewMode as n, formatPlanPrice as o, parseManifestPlans as p, useIsDemoSession as r, formatPlanPriceUnit as s, useDevPortalApiBaseUrl as t, isPlanCustomPriced as u, resolveDevPortalApiBaseUrl as v };
253
+ export { quotaCell as _, formatPlanDisplayPrice as a, isActiveSubscription as c, isPlanFree as d, listPlanFeatureLines as f, pickFeaturedPlanSku as g, parseQuotaPeriodDays as h, formatBillingPeriod as i, isPlaceholderDevPortalApiUrl as l, parseMonthlyQuotaCalls as m, useDevPortalPreviewMode as n, formatPlanPrice as o, parseManifestPlans as p, useIsDemoSession as r, formatPlanPriceUnit as s, useDevPortalApiBaseUrl as t, isPlanCustomPriced as u, rateLimitCell as v, resolveDevPortalApiBaseUrl as y };
@@ -1,4 +1,4 @@
1
- import { n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-CgygF2z5.mjs";
1
+ import { n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-Dk00H8xy.mjs";
2
2
  import { n as useDevPortalBillingConfig } from "./DevPortalBillingContext-CR83s4LO.mjs";
3
3
  import { r as createDemoMeResponse } from "./demo-session-Cez740bi.mjs";
4
4
  import { useQuery } from "@lukoweb/apitogo/react-query";
@@ -1,4 +1,4 @@
1
- import { l as isPlaceholderDevPortalApiUrl, n as useDevPortalPreviewMode, p as parseManifestPlans, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-CgygF2z5.mjs";
1
+ import { l as isPlaceholderDevPortalApiUrl, n as useDevPortalPreviewMode, p as parseManifestPlans, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-Dk00H8xy.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-CgygF2z5.mjs";
1
+ import { n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-Dk00H8xy.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.38",
3
+ "version": "0.2.39",
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.38",
50
- "@lukoweb/apitogo-module-user-panel": "0.2.38"
49
+ "@lukoweb/apitogo": "0.2.39",
50
+ "@lukoweb/apitogo-module-user-panel": "0.2.39"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "react": ">=19.2.0",