@lukoweb/apitogo-plugin-dev-portal-billing 0.2.50 → 0.2.52

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-DTVIVeRh.mjs → AccountPlansPage-CeUizdWk.mjs} +4 -4
  2. package/dist/{CheckoutPage-6nFq9GKO.mjs → CheckoutPage-CwSElFUk.mjs} +2 -2
  3. package/dist/{ContactSalesPage-Imh39E8z.mjs → ContactSalesPage-q7Dxxtx6.mjs} +2 -2
  4. package/dist/{CredentialsPage-BOVn3PpF.mjs → CredentialsPage-B2jPo6Go.mjs} +37 -27
  5. package/dist/{PricingCard-D-owYlf3.mjs → PricingCard-CKdq1oWn.mjs} +1 -1
  6. package/dist/{PricingPage-BTPQ-ntp.mjs → PricingPage-DnQQfy9L.mjs} +5 -5
  7. package/dist/{SubscriptionsPage-29Xs4XvM.mjs → SubscriptionsPage-msO31jv-.mjs} +26 -24
  8. package/dist/{UsagePage-U27bvmbF.mjs → UsagePage-BF4QgrTc.mjs} +4 -4
  9. package/dist/{account-links-BUeCO2Du.mjs → account-links-D_vS0GJn.mjs} +2 -164
  10. package/dist/{billing-state-DjUXQYSr.mjs → billing-state-DbU5Lpoz.mjs} +1 -1
  11. package/dist/{demo-session-B6WAunKV.mjs → demo-session-rN2Gj4lM.mjs} +2 -1
  12. package/dist/index.d.mts +19 -2
  13. package/dist/index.mjs +35 -17
  14. package/dist/{ChoosePlanNotice-DITJNrh1.mjs → key-plan-note-DTWeFhn2.mjs} +20 -1
  15. package/dist/useApiKeys-CVgXfUop.mjs +166 -0
  16. package/dist/{useDevPortalApiBaseUrl-Dk00H8xy.mjs → useDevPortalApiBaseUrl-BstWzITX.mjs} +1 -1
  17. package/dist/{useMySubscriptions-Bdo2HFrP.mjs → useMySubscriptions-BtxAXA1m.mjs} +2 -2
  18. package/dist/{usePlans-DVGt2Myv.mjs → usePlans-DPGFWcVo.mjs} +1 -1
  19. package/dist/{useUsage-DOUibue4.mjs → useUsage--5sZub3M.mjs} +1 -1
  20. package/package.json +3 -3
  21. /package/dist/{pricing-config-CfhRX9tk.mjs → pricing-config-02m4dyFf.mjs} +0 -0
@@ -1,8 +1,8 @@
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";
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-CKdq1oWn.mjs";
3
3
  import { n as useDevPortalBillingConfig } from "./DevPortalBillingContext-CR83s4LO.mjs";
4
- import { t as useMySubscriptions } from "./useMySubscriptions-Bdo2HFrP.mjs";
5
- import { t as usePlans } from "./usePlans-DVGt2Myv.mjs";
4
+ import { t as useMySubscriptions } from "./useMySubscriptions-BtxAXA1m.mjs";
5
+ import { t as usePlans } from "./usePlans-DPGFWcVo.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-Dk00H8xy.mjs";
3
- import { t as FREE_CHECKOUT_MARKER } from "./billing-state-DjUXQYSr.mjs";
2
+ import { n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-BstWzITX.mjs";
3
+ import { t as FREE_CHECKOUT_MARKER } from "./billing-state-DbU5Lpoz.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-Dk00H8xy.mjs";
2
+ import { l as isPlanCustomPriced, n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-BstWzITX.mjs";
3
3
  import "./DevPortalBillingContext-CR83s4LO.mjs";
4
- import { t as usePlans } from "./usePlans-DVGt2Myv.mjs";
4
+ import { t as usePlans } from "./usePlans-DPGFWcVo.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,9 +1,10 @@
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-Dk00H8xy.mjs";
2
+ import { n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-BstWzITX.mjs";
3
3
  import { n as useDevPortalBillingConfig } from "./DevPortalBillingContext-CR83s4LO.mjs";
4
- import { r as createDemoCredentials } from "./demo-session-B6WAunKV.mjs";
5
- import { a as useApiKeys, n as apiReferencePath, r as ApiKeysPanel } from "./account-links-BUeCO2Du.mjs";
6
- import { t as ChoosePlanNotice } from "./ChoosePlanNotice-DITJNrh1.mjs";
4
+ import { r as createDemoCredentials, t as DEMO_GATEWAY_KEY_HEADER } from "./demo-session-rN2Gj4lM.mjs";
5
+ import { n as useApiKeys } from "./useApiKeys-CVgXfUop.mjs";
6
+ import { n as apiReferencePath, r as ApiKeysPanel } from "./account-links-D_vS0GJn.mjs";
7
+ import { n as ChoosePlanNotice, t as keyPlanNote } from "./key-plan-note-DTWeFhn2.mjs";
7
8
  import { Head } from "@lukoweb/apitogo/components";
8
9
  import { KeyRoundIcon } from "@lukoweb/apitogo/icons";
9
10
  import { Link as Link$1 } from "@lukoweb/apitogo/router";
@@ -58,11 +59,12 @@ const CredentialsPage = () => {
58
59
  const isDemoSession = useIsDemoSession();
59
60
  const { data: credentials, isLoading, error } = useCredentials();
60
61
  const { data: apiKeys, isLoading: keysLoading, error: keysError } = useApiKeys();
61
- const { gatewayBaseUrl: configuredGatewayBaseUrl, links } = useDevPortalBillingConfig();
62
+ const { gatewayBaseUrl: configuredGatewayBaseUrl, links, keyHeader: configuredKeyHeader } = useDevPortalBillingConfig();
62
63
  const apiReferenceLink = apiReferencePath(links);
63
64
  const gatewayBaseUrl = credentials?.gatewayBaseUrl?.trim() || apiKeys?.gatewayBaseUrl?.trim() || configuredGatewayBaseUrl?.trim() || zudokuConfig.__meta?.gatewayBaseUrl?.trim() || null;
64
- const header = apiKeys?.header ?? "Ocp-Apim-Subscription-Key";
65
+ const header = configuredKeyHeader?.trim() || apiKeys?.header || DEMO_GATEWAY_KEY_HEADER;
65
66
  const plans = apiKeys?.plans ?? [];
67
+ const planNote = isDemoSession ? null : keyPlanNote(plans);
66
68
  return /* @__PURE__ */ jsxs("div", {
67
69
  className: "mx-auto max-w-screen-lg px-4 pb-16 pt-(--padding-content-top) lg:px-8",
68
70
  children: [
@@ -128,29 +130,37 @@ const CredentialsPage = () => {
128
130
  }) : null,
129
131
  (isDemoSession || !isPreview) && auth.isAuthenticated && !error && !keysError && !isLoading && !keysLoading ? /* @__PURE__ */ jsxs("div", {
130
132
  className: "space-y-6",
131
- children: [plans.length === 0 ? /* @__PURE__ */ jsx(ChoosePlanNotice, {}) : plans.map((plan) => /* @__PURE__ */ jsx(ApiKeysPanel, {
132
- plan,
133
- header,
134
- readOnly: isDemoSession
135
- }, plan.planSku)), /* @__PURE__ */ jsxs("article", {
136
- className: "rounded-xl border bg-card/60 p-6 space-y-4",
137
- children: [/* @__PURE__ */ jsxs("div", {
138
- className: "space-y-1",
139
- children: [/* @__PURE__ */ jsx("h2", {
140
- className: "text-lg font-medium",
141
- children: "Gateway URL"
142
- }), /* @__PURE__ */ jsx("p", {
133
+ children: [
134
+ planNote ? /* @__PURE__ */ jsx("div", {
135
+ role: "status",
136
+ className: "rounded-xl border border-primary/30 bg-primary/5 p-4 text-sm",
137
+ children: planNote.text
138
+ }) : null,
139
+ plans.length === 0 ? /* @__PURE__ */ jsx(ChoosePlanNotice, {}) : plans.map((plan) => /* @__PURE__ */ jsx(ApiKeysPanel, {
140
+ plan,
141
+ header,
142
+ readOnly: isDemoSession
143
+ }, plan.planSku)),
144
+ /* @__PURE__ */ jsxs("article", {
145
+ className: "rounded-xl border bg-card/60 p-6 space-y-4",
146
+ children: [/* @__PURE__ */ jsxs("div", {
147
+ className: "space-y-1",
148
+ children: [/* @__PURE__ */ jsx("h2", {
149
+ className: "text-lg font-medium",
150
+ children: "Gateway URL"
151
+ }), /* @__PURE__ */ jsx("p", {
152
+ className: "text-sm text-muted-foreground",
153
+ children: "Base URL for API requests through the gateway."
154
+ })]
155
+ }), gatewayBaseUrl ? /* @__PURE__ */ jsx(Value, {
156
+ className: "max-w-full w-full",
157
+ value: gatewayBaseUrl
158
+ }) : /* @__PURE__ */ jsx("p", {
143
159
  className: "text-sm text-muted-foreground",
144
- children: "Base URL for API requests through the gateway."
160
+ children: "Available after your API is deployed to the gateway."
145
161
  })]
146
- }), gatewayBaseUrl ? /* @__PURE__ */ jsx(Value, {
147
- className: "max-w-full w-full",
148
- value: gatewayBaseUrl
149
- }) : /* @__PURE__ */ jsx("p", {
150
- className: "text-sm text-muted-foreground",
151
- children: "Available after your API is deployed to the gateway."
152
- })]
153
- })]
162
+ })
163
+ ]
154
164
  }) : null,
155
165
  /* @__PURE__ */ jsxs("div", {
156
166
  className: "mt-8 flex flex-wrap gap-3",
@@ -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-Dk00H8xy.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";
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 { g as pickFeaturedPlanSku, n as useDevPortalPreviewMode, p as parseManifestPlans } from "./useDevPortalApiBaseUrl-Dk00H8xy.mjs";
2
- import { t as PricingCard } from "./PricingCard-D-owYlf3.mjs";
1
+ import { f as parseManifestPlans, h as pickFeaturedPlanSku, n as useDevPortalPreviewMode } from "./useDevPortalApiBaseUrl-BstWzITX.mjs";
2
+ import { t as PricingCard } from "./PricingCard-CKdq1oWn.mjs";
3
3
  import { n as useDevPortalBillingConfig } from "./DevPortalBillingContext-CR83s4LO.mjs";
4
- import { t as usePlans } from "./usePlans-DVGt2Myv.mjs";
5
- import { t as isPreviewPricingEnabled } from "./pricing-config-CfhRX9tk.mjs";
6
- import { a as isPricingLoading, n as buildCompareRows } from "./billing-state-DjUXQYSr.mjs";
4
+ import { t as usePlans } from "./usePlans-DPGFWcVo.mjs";
5
+ import { t as isPreviewPricingEnabled } from "./pricing-config-02m4dyFf.mjs";
6
+ import { a as isPricingLoading, n as buildCompareRows } from "./billing-state-DbU5Lpoz.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,15 +1,16 @@
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-Dk00H8xy.mjs";
2
+ import { a as formatPlanPrice, p as parseMonthlyQuotaCalls, s as isActiveSubscription, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-BstWzITX.mjs";
3
3
  import { n as useDevPortalBillingConfig } from "./DevPortalBillingContext-CR83s4LO.mjs";
4
- import { t as useMySubscriptions } from "./useMySubscriptions-Bdo2HFrP.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
- import { t as ChoosePlanNotice } from "./ChoosePlanNotice-DITJNrh1.mjs";
4
+ import { t as useMySubscriptions } from "./useMySubscriptions-BtxAXA1m.mjs";
5
+ import { t as usePlans } from "./usePlans-DPGFWcVo.mjs";
6
+ import { n as useApiKeys } from "./useApiKeys-CVgXfUop.mjs";
7
+ import { t as useUsage } from "./useUsage--5sZub3M.mjs";
8
+ import { i as hasPaymentProvider, r as checkoutConfirmingText } from "./billing-state-DbU5Lpoz.mjs";
9
+ import { n as ChoosePlanNotice, t as keyPlanNote } from "./key-plan-note-DTWeFhn2.mjs";
9
10
  import { Head, Spinner } from "@lukoweb/apitogo/components";
10
11
  import { Link as Link$1, useSearchParams } from "@lukoweb/apitogo/router";
11
12
  import { useEffect } from "react";
12
- import { jsx, jsxs } from "react/jsx-runtime";
13
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
13
14
  import { useMutation } from "@lukoweb/apitogo/react-query";
14
15
  import { useAuth } from "@lukoweb/apitogo/hooks";
15
16
  import { Button as Button$1 } from "@lukoweb/apitogo/ui/Button.js";
@@ -47,6 +48,8 @@ const SubscriptionsPage = () => {
47
48
  const { data: catalog } = usePlans();
48
49
  const billingPortal = useBillingPortal();
49
50
  const { data: usage } = useUsage("30d");
51
+ const { data: apiKeys } = useApiKeys();
52
+ const planNote = keyPlanNote(apiKeys?.plans ?? []);
50
53
  const planBySku = new Map((catalog?.plans ?? []).map((plan) => [plan.sku, plan]));
51
54
  const subscriptions = me?.subscriptions ?? [];
52
55
  const activeSubscription = subscriptions.find((sub) => isActiveSubscription(sub.status));
@@ -81,9 +84,18 @@ const SubscriptionsPage = () => {
81
84
  className: "mb-6 rounded-xl border border-border bg-card p-4 text-sm",
82
85
  children: checkoutConfirmingText(checkoutPaid)
83
86
  }) : null,
84
- checkoutSuccess && activeSubscription ? /* @__PURE__ */ jsxs("div", {
87
+ checkoutSuccess && activeSubscription ? /* @__PURE__ */ jsx("div", {
85
88
  className: "mb-6 rounded-xl border border-green-500/30 bg-green-500/10 p-4 text-sm",
86
- children: [
89
+ children: planNote ? /* @__PURE__ */ jsxs(Fragment, { children: [
90
+ planNote.text,
91
+ " ",
92
+ /* @__PURE__ */ jsx(Link$1, {
93
+ to: CREDENTIALS_PATH,
94
+ className: "font-medium underline",
95
+ children: "Create the key"
96
+ }),
97
+ "."
98
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
87
99
  "Your subscription is active.",
88
100
  " ",
89
101
  /* @__PURE__ */ jsx(Link$1, {
@@ -92,7 +104,7 @@ const SubscriptionsPage = () => {
92
104
  children: "View your API credentials"
93
105
  }),
94
106
  "."
95
- ]
107
+ ] })
96
108
  }) : null,
97
109
  isLoading ? /* @__PURE__ */ jsx("p", {
98
110
  className: "text-muted-foreground",
@@ -109,14 +121,9 @@ const SubscriptionsPage = () => {
109
121
  className: "mt-2 text-2xl font-bold",
110
122
  children: activePlan?.displayName ?? activeSubscription.planSku
111
123
  }),
112
- activePlan ? /* @__PURE__ */ jsxs("p", {
124
+ activePlan ? /* @__PURE__ */ jsx("p", {
113
125
  className: "mt-1 text-sm text-muted-foreground",
114
- children: [
115
- formatPlanPrice(activePlan),
116
- " ·",
117
- " ",
118
- formatBillingPeriod(activePlan.billingPeriod)
119
- ]
126
+ children: formatPlanPrice(activePlan)
120
127
  }) : /* @__PURE__ */ jsx("p", {
121
128
  className: "mt-1 text-sm text-muted-foreground",
122
129
  children: "This plan is no longer offered. Your access continues until you cancel or change plan."
@@ -164,14 +171,9 @@ const SubscriptionsPage = () => {
164
171
  children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h3", {
165
172
  className: "font-semibold",
166
173
  children: plan?.displayName ?? subscription.planSku
167
- }), plan ? /* @__PURE__ */ jsxs("p", {
174
+ }), plan ? /* @__PURE__ */ jsx("p", {
168
175
  className: "mt-1 text-sm text-muted-foreground",
169
- children: [
170
- formatPlanPrice(plan),
171
- " ·",
172
- " ",
173
- formatBillingPeriod(plan.billingPeriod)
174
- ]
176
+ children: formatPlanPrice(plan)
175
177
  }) : null] }), /* @__PURE__ */ jsx("span", {
176
178
  className: "rounded-full bg-muted px-3 py-1 text-xs font-medium text-muted-foreground",
177
179
  children: subscription.status
@@ -1,8 +1,8 @@
1
- import { c as isActiveSubscription, h as parseQuotaPeriodDays, m as parseMonthlyQuotaCalls } from "./useDevPortalApiBaseUrl-Dk00H8xy.mjs";
1
+ import { m as parseQuotaPeriodDays, p as parseMonthlyQuotaCalls, s as isActiveSubscription } from "./useDevPortalApiBaseUrl-BstWzITX.mjs";
2
2
  import "./DevPortalBillingContext-CR83s4LO.mjs";
3
- import { t as useMySubscriptions } from "./useMySubscriptions-Bdo2HFrP.mjs";
4
- import { t as usePlans } from "./usePlans-DVGt2Myv.mjs";
5
- import { t as useUsage } from "./useUsage-DOUibue4.mjs";
3
+ import { t as useMySubscriptions } from "./useMySubscriptions-BtxAXA1m.mjs";
4
+ import { t as usePlans } from "./usePlans-DPGFWcVo.mjs";
5
+ import { t as useUsage } from "./useUsage--5sZub3M.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,176 +1,14 @@
1
- import { n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-Dk00H8xy.mjs";
2
- import { n as useDevPortalBillingConfig } from "./DevPortalBillingContext-CR83s4LO.mjs";
3
- import { n as createDemoApiKeys, t as DEMO_GATEWAY_KEY_HEADER } from "./demo-session-B6WAunKV.mjs";
1
+ import { i as useRevokeApiKey, r as useCreateApiKey, t as API_KEY_CAP_REACHED } from "./useApiKeys-CVgXfUop.mjs";
4
2
  import { Spinner } from "@lukoweb/apitogo/components";
5
3
  import { KeyRoundIcon, TrashIcon } from "@lukoweb/apitogo/icons";
6
4
  import { useState } from "react";
7
5
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
- import { useMutation, useQuery, useQueryClient } from "@lukoweb/apitogo/react-query";
9
- import { useAuth } from "@lukoweb/apitogo/hooks";
10
6
  import { Button as Button$1 } from "@lukoweb/apitogo/ui/Button.js";
11
7
  import { Alert, AlertTitle } from "@lukoweb/apitogo/ui/Alert.js";
12
8
  import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@lukoweb/apitogo/ui/Dialog.js";
13
9
  import { Input } from "@lukoweb/apitogo/ui/Input.js";
14
10
  import { Secret } from "@lukoweb/apitogo/ui/Secret.js";
15
11
 
16
- //#region src/utils/playground-key.ts
17
- const STORAGE_KEY = "apitogo-playground-api-key";
18
- const PLAYGROUND_KEY_IDENTITY_ID = "api-key";
19
- const storage = () => {
20
- try {
21
- return typeof localStorage === "undefined" ? void 0 : localStorage;
22
- } catch {
23
- return;
24
- }
25
- };
26
- /**
27
- * Remembers a key the moment it is created: the plans backend shows a key's secret only then, so this is the one
28
- * chance to offer it to the playground later. Only the last key is kept, in this browser only.
29
- */
30
- function rememberPlaygroundKey(entry) {
31
- if (!entry.key) return;
32
- const value = {
33
- id: entry.id,
34
- key: entry.key,
35
- header: entry.header || DEMO_GATEWAY_KEY_HEADER,
36
- keyPrefix: entry.keyPrefix
37
- };
38
- try {
39
- storage()?.setItem(STORAGE_KEY, JSON.stringify(value));
40
- } catch {}
41
- }
42
- function readPlaygroundKey() {
43
- try {
44
- const raw = storage()?.getItem(STORAGE_KEY);
45
- if (!raw) return void 0;
46
- const parsed = JSON.parse(raw);
47
- return parsed.id && parsed.key && parsed.header && parsed.keyPrefix ? parsed : void 0;
48
- } catch {
49
- return;
50
- }
51
- }
52
- /** A revoked key stops working, so it is no longer offered. */
53
- function forgetPlaygroundKey(keyId) {
54
- if (readPlaygroundKey()?.id !== keyId) return;
55
- try {
56
- storage()?.removeItem(STORAGE_KEY);
57
- } catch {}
58
- }
59
- /** The "Try it" identity for the remembered key, sent in the gateway's key header. */
60
- function playgroundKeyIdentities() {
61
- const remembered = readPlaygroundKey();
62
- if (!remembered) return [];
63
- return [{
64
- id: PLAYGROUND_KEY_IDENTITY_ID,
65
- label: `Your API key (${remembered.keyPrefix}…)`,
66
- authorizationFields: { headers: [remembered.header] },
67
- authorizeRequest: (request) => {
68
- request.headers.set(remembered.header, remembered.key);
69
- return request;
70
- }
71
- }];
72
- }
73
-
74
- //#endregion
75
- //#region src/hooks/useApiKeys.ts
76
- /** Thrown when the developer has hit their plan's per-developer key cap. */
77
- const API_KEY_CAP_REACHED = "API_KEY_CAP_REACHED";
78
- function apiKeysQueryKey(apiBaseUrl) {
79
- return ["dev-portal-api-keys", apiBaseUrl];
80
- }
81
- function useApiKeys() {
82
- const auth = useAuth();
83
- const apiBaseUrl = useDevPortalApiBaseUrl();
84
- const isPreview = useDevPortalPreviewMode();
85
- const isDemoSession = useIsDemoSession();
86
- const { previewPlans } = useDevPortalBillingConfig();
87
- const liveQuery = useQuery({
88
- queryKey: apiKeysQueryKey(apiBaseUrl),
89
- enabled: auth.isAuthenticated && !isPreview && !isDemoSession && Boolean(apiBaseUrl),
90
- queryFn: async () => {
91
- const response = await fetch(`${apiBaseUrl}/api/v1/api-keys`, {
92
- method: "GET",
93
- headers: { Accept: "application/json" },
94
- credentials: "include"
95
- });
96
- if (response.status === 404) throw new Error("NO_ENTITLEMENT");
97
- if (!response.ok) throw new Error("Failed to load API keys.");
98
- return await response.json();
99
- },
100
- retry: (failureCount, error) => error instanceof Error && error.message === "NO_ENTITLEMENT" ? false : failureCount < 2
101
- });
102
- if (isDemoSession && auth.isAuthenticated) return {
103
- ...liveQuery,
104
- data: createDemoApiKeys(previewPlans),
105
- error: null,
106
- isError: false,
107
- isFetching: false,
108
- isLoading: false,
109
- isPending: false,
110
- isSuccess: true,
111
- status: "success"
112
- };
113
- return liveQuery;
114
- }
115
- function useCreateApiKey() {
116
- const apiBaseUrl = useDevPortalApiBaseUrl();
117
- const queryClient = useQueryClient();
118
- return useMutation({
119
- mutationFn: async ({ name, planSku }) => {
120
- if (!apiBaseUrl) throw new Error("Dev portal API is not configured.");
121
- const response = await fetch(`${apiBaseUrl}/api/v1/api-keys`, {
122
- method: "POST",
123
- headers: {
124
- Accept: "application/json",
125
- "Content-Type": "application/json"
126
- },
127
- credentials: "include",
128
- body: JSON.stringify({
129
- name,
130
- planSku
131
- })
132
- });
133
- if (response.status === 409) throw new Error(API_KEY_CAP_REACHED);
134
- if (!response.ok) throw new Error(await readProblemDetail(response) ?? "Failed to create API key.");
135
- return await response.json();
136
- },
137
- onSuccess: async (created) => {
138
- rememberPlaygroundKey({
139
- ...created,
140
- header: queryClient.getQueryData(apiKeysQueryKey(apiBaseUrl))?.header
141
- });
142
- await queryClient.invalidateQueries({ queryKey: apiKeysQueryKey(apiBaseUrl) });
143
- }
144
- });
145
- }
146
- function useRevokeApiKey() {
147
- const apiBaseUrl = useDevPortalApiBaseUrl();
148
- const queryClient = useQueryClient();
149
- return useMutation({
150
- mutationFn: async (keyId) => {
151
- if (!apiBaseUrl) throw new Error("Dev portal API is not configured.");
152
- const response = await fetch(`${apiBaseUrl}/api/v1/api-keys/${keyId}`, {
153
- method: "DELETE",
154
- headers: { Accept: "application/json" },
155
- credentials: "include"
156
- });
157
- if (!response.ok && response.status !== 204) throw new Error(await readProblemDetail(response) ?? "Failed to revoke API key.");
158
- },
159
- onSuccess: async (_result, keyId) => {
160
- forgetPlaygroundKey(keyId);
161
- await queryClient.invalidateQueries({ queryKey: apiKeysQueryKey(apiBaseUrl) });
162
- }
163
- });
164
- }
165
- async function readProblemDetail(response) {
166
- try {
167
- return (await response.json()).detail?.trim() || null;
168
- } catch {
169
- return null;
170
- }
171
- }
172
-
173
- //#endregion
174
12
  //#region src/components/ApiKeysPanel.tsx
175
13
  /**
176
14
  * Lists gateway API keys for one plan with create and revoke. The plaintext key is returned by the
@@ -368,4 +206,4 @@ function apiReferencePath(links) {
368
206
  }
369
207
 
370
208
  //#endregion
371
- export { useApiKeys as a, playgroundKeyIdentities as c, API_KEY_CAP_REACHED as i, apiReferencePath as n, useCreateApiKey as o, ApiKeysPanel as r, useRevokeApiKey as s, accountLayoutProps as t };
209
+ export { apiReferencePath as n, ApiKeysPanel as r, accountLayoutProps as t };
@@ -1,4 +1,4 @@
1
- import { _ as quotaCell, v as rateLimitCell } from "./useDevPortalApiBaseUrl-Dk00H8xy.mjs";
1
+ import { _ as rateLimitCell, g as quotaCell } from "./useDevPortalApiBaseUrl-BstWzITX.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. */
@@ -1,6 +1,7 @@
1
1
  //#region src/utils/demo-session.ts
2
2
  const DEMO_SUBSCRIPTION_KEY = "demo-subscription-key";
3
- const DEMO_GATEWAY_KEY_HEADER = "Ocp-Apim-Subscription-Key";
3
+ /** The platform gateway's default key header (API-120). */
4
+ const DEMO_GATEWAY_KEY_HEADER = "X-API-Key";
4
5
  const DEMO_MAX_KEYS = 5;
5
6
  function resolveDemoPlans(plans) {
6
7
  if (plans && plans.length > 0) return plans.map((plan) => ({
package/dist/index.d.mts CHANGED
@@ -53,7 +53,12 @@ type DevPortalBillingConfig = {
53
53
  api?: string;
54
54
  }; /** Address of the plans backend; the portal's generated wrapper passes it. The plans request starts from it early. */
55
55
  apiBaseUrl?: string; /** Live gateway URL (publish bake or explicit plugin option). */
56
- gatewayBaseUrl?: string | null; /** Local preview plan catalog (from `apitogo.config.json` `plans`). */
56
+ gatewayBaseUrl?: string | null;
57
+ /**
58
+ * The header API users send their key in, as the portal build knows it from the gateway config. Wins over the plans
59
+ * backend's answer, which only knows the platform default.
60
+ */
61
+ keyHeader?: string; /** Local preview plan catalog (from `apitogo.config.json` `plans`). */
57
62
  previewPlans?: Array<{
58
63
  sku: string;
59
64
  displayName: string;
@@ -236,6 +241,18 @@ declare function useCreateApiKey(): _tanstack_react_query0.UseMutationResult<Dev
236
241
  }, unknown>;
237
242
  declare function useRevokeApiKey(): _tanstack_react_query0.UseMutationResult<void, Error, string, unknown>;
238
243
  //#endregion
244
+ //#region src/hooks/useGatewayCall.d.ts
245
+ /** How a caller reaches the API: the gateway address and the header the key goes in. */
246
+ type GatewayCall = {
247
+ /** The gateway's base URL, without a trailing slash; null before the gateway is deployed. */baseUrl: string | null; /** The header API users send their key in. */
248
+ keyHeader: string;
249
+ };
250
+ /**
251
+ * The real gateway address and key header, as the portal build knows them (API-120), so a page's code sample works
252
+ * when a visitor pastes it with their key.
253
+ */
254
+ declare function useGatewayCall(): GatewayCall;
255
+ //#endregion
239
256
  //#region src/hooks/usePlans.d.ts
240
257
  declare function usePlans(): UseQueryResult<DevPortalPlanCatalog>;
241
258
  //#endregion
@@ -258,4 +275,4 @@ declare const toPlansPreviewItems: (plans: DevPortalPlan[]) => PlansPreviewItem[
258
275
  */
259
276
  declare const usePlansPreview: () => PlansPreviewItem[];
260
277
  //#endregion
261
- export { ACCOUNT_PLANS_PATH, API_KEY_CAP_REACHED, AccountPlansPage, ApiKeysPanel, CHECKOUT_PATH, CONTACT_SALES_PATH, CREDENTIALS_PATH, type DevPortalPlan, type DevPortalPlanCatalog, OverviewDashboardPage, PRICING_PATH, type PlansPreviewItem, SUBSCRIPTIONS_PATH, USAGE_PATH, devPortalBillingPlugin, toPlansPreviewItems, useApiKeys, useCreateApiKey, usePlans, usePlansPreview, useRevokeApiKey };
278
+ export { ACCOUNT_PLANS_PATH, API_KEY_CAP_REACHED, AccountPlansPage, ApiKeysPanel, CHECKOUT_PATH, CONTACT_SALES_PATH, CREDENTIALS_PATH, type DevPortalPlan, type DevPortalPlanCatalog, type GatewayCall, OverviewDashboardPage, PRICING_PATH, type PlansPreviewItem, SUBSCRIPTIONS_PATH, USAGE_PATH, devPortalBillingPlugin, toPlansPreviewItems, useApiKeys, useCreateApiKey, useGatewayCall, usePlans, usePlansPreview, useRevokeApiKey };
package/dist/index.mjs CHANGED
@@ -1,18 +1,21 @@
1
- import { t as AccountPlansPage } from "./AccountPlansPage-DTVIVeRh.mjs";
1
+ import { t as AccountPlansPage } from "./AccountPlansPage-CeUizdWk.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, g as pickFeaturedPlanSku, p as parseManifestPlans, s as formatPlanPriceUnit, y as resolveDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-Dk00H8xy.mjs";
4
- import "./PricingCard-D-owYlf3.mjs";
5
- import { t as DevPortalBillingProvider } from "./DevPortalBillingContext-CR83s4LO.mjs";
6
- import { t as useMySubscriptions } from "./useMySubscriptions-Bdo2HFrP.mjs";
7
- import { n as startPlansPrefetch, t as usePlans } from "./usePlans-DVGt2Myv.mjs";
8
- import { a as useApiKeys, c as playgroundKeyIdentities, i as API_KEY_CAP_REACHED, o as useCreateApiKey, r as ApiKeysPanel, s as useRevokeApiKey, t as accountLayoutProps } from "./account-links-BUeCO2Du.mjs";
9
- import { n as useUsageRecent, t as useUsage } from "./useUsage-DOUibue4.mjs";
10
- import { t as isPreviewPricingEnabled } from "./pricing-config-CfhRX9tk.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-CKdq1oWn.mjs";
5
+ import { n as useDevPortalBillingConfig, t as DevPortalBillingProvider } from "./DevPortalBillingContext-CR83s4LO.mjs";
6
+ import { t as DEMO_GATEWAY_KEY_HEADER } from "./demo-session-rN2Gj4lM.mjs";
7
+ import { t as useMySubscriptions } from "./useMySubscriptions-BtxAXA1m.mjs";
8
+ import { n as startPlansPrefetch, t as usePlans } from "./usePlans-DPGFWcVo.mjs";
9
+ import { a as playgroundKeyIdentities, i as useRevokeApiKey, n as useApiKeys, r as useCreateApiKey, t as API_KEY_CAP_REACHED } from "./useApiKeys-CVgXfUop.mjs";
10
+ import { n as useUsageRecent, t as useUsage } from "./useUsage--5sZub3M.mjs";
11
+ import { r as ApiKeysPanel, t as accountLayoutProps } from "./account-links-D_vS0GJn.mjs";
12
+ import { t as isPreviewPricingEnabled } from "./pricing-config-02m4dyFf.mjs";
11
13
  import { Head, Link } from "@lukoweb/apitogo/components";
12
14
  import { ArrowRightIcon, KeyRoundIcon, StarsIcon } from "@lukoweb/apitogo/icons";
13
15
  import { Outlet } from "@lukoweb/apitogo/router";
14
16
  import { Suspense, lazy } from "react";
15
17
  import manifestCatalog from "virtual:apitogo-local-manifest";
18
+ import zudokuConfig from "virtual:zudoku-config";
16
19
  import { jsx, jsxs } from "react/jsx-runtime";
17
20
  import { useAuth } from "@lukoweb/apitogo/hooks";
18
21
  import { Button as Button$1 } from "@lukoweb/apitogo/ui/Button.js";
@@ -363,13 +366,13 @@ async function getSignedInIdentities(apiBaseUrl, fetchImpl = fetch) {
363
366
 
364
367
  //#endregion
365
368
  //#region src/DevPortalBillingPlugin.tsx
366
- const AccountPlansPage$1 = lazy(() => import("./AccountPlansPage-DTVIVeRh.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-BOVn3PpF.mjs"));
370
- const PricingPage = lazy(() => import("./PricingPage-BTPQ-ntp.mjs"));
371
- const SubscriptionsPage = lazy(() => import("./SubscriptionsPage-29Xs4XvM.mjs"));
372
- const UsagePage = lazy(() => import("./UsagePage-U27bvmbF.mjs"));
369
+ const AccountPlansPage$1 = lazy(() => import("./AccountPlansPage-CeUizdWk.mjs").then((n) => n.n));
370
+ const CheckoutPage = lazy(() => import("./CheckoutPage-CwSElFUk.mjs"));
371
+ const ContactSalesPage = lazy(() => import("./ContactSalesPage-q7Dxxtx6.mjs"));
372
+ const CredentialsPage = lazy(() => import("./CredentialsPage-B2jPo6Go.mjs"));
373
+ const PricingPage = lazy(() => import("./PricingPage-DnQQfy9L.mjs"));
374
+ const SubscriptionsPage = lazy(() => import("./SubscriptionsPage-msO31jv-.mjs"));
375
+ const UsagePage = lazy(() => import("./UsagePage-BF4QgrTc.mjs"));
373
376
  function resolvePricingEnabled(config) {
374
377
  if (config.__meta?.pricingEnabled !== void 0) return config.__meta.pricingEnabled;
375
378
  return isPreviewPricingEnabled();
@@ -474,6 +477,21 @@ const devPortalBillingPlugin = createPlugin((options = {}) => {
474
477
  };
475
478
  });
476
479
 
480
+ //#endregion
481
+ //#region src/hooks/useGatewayCall.ts
482
+ /**
483
+ * The real gateway address and key header, as the portal build knows them (API-120), so a page's code sample works
484
+ * when a visitor pastes it with their key.
485
+ */
486
+ function useGatewayCall() {
487
+ const { gatewayBaseUrl, keyHeader } = useDevPortalBillingConfig();
488
+ const baseUrl = gatewayBaseUrl?.trim() || zudokuConfig.__meta?.gatewayBaseUrl?.trim() || "";
489
+ return {
490
+ baseUrl: baseUrl ? baseUrl.replace(/\/+$/, "") : null,
491
+ keyHeader: keyHeader?.trim() || DEMO_GATEWAY_KEY_HEADER
492
+ };
493
+ }
494
+
477
495
  //#endregion
478
496
  //#region src/hooks/usePlansPreview.ts
479
497
  /** Maps plans to landing `PlansPreviewSection` cards, highlighting the same plan as /pricing. */
@@ -499,4 +517,4 @@ const usePlansPreview = () => {
499
517
  };
500
518
 
501
519
  //#endregion
502
- export { ACCOUNT_PLANS_PATH, API_KEY_CAP_REACHED, AccountPlansPage, ApiKeysPanel, CHECKOUT_PATH, CONTACT_SALES_PATH, CREDENTIALS_PATH, OverviewDashboardPage, PRICING_PATH, SUBSCRIPTIONS_PATH, USAGE_PATH, devPortalBillingPlugin, toPlansPreviewItems, useApiKeys, useCreateApiKey, usePlans, usePlansPreview, useRevokeApiKey };
520
+ export { ACCOUNT_PLANS_PATH, API_KEY_CAP_REACHED, AccountPlansPage, ApiKeysPanel, CHECKOUT_PATH, CONTACT_SALES_PATH, CREDENTIALS_PATH, OverviewDashboardPage, PRICING_PATH, SUBSCRIPTIONS_PATH, USAGE_PATH, devPortalBillingPlugin, toPlansPreviewItems, useApiKeys, useCreateApiKey, useGatewayCall, usePlans, usePlansPreview, useRevokeApiKey };
@@ -31,4 +31,23 @@ const ChoosePlanNotice = ({ className = "rounded-xl border border-dashed p-8 tex
31
31
  });
32
32
 
33
33
  //#endregion
34
- export { ChoosePlanNotice as t };
34
+ //#region src/utils/key-plan-note.ts
35
+ /**
36
+ * An API user may hold several plans at once, each with its own keys and limits (API-121). After buying a plan, their
37
+ * app still calls with the key of the plan they had, so it keeps that plan's limits. When a plan has no key yet while
38
+ * another plan they hold has one, this says so and what to do; otherwise null.
39
+ */
40
+ function keyPlanNote(plans) {
41
+ const withKeys = plans.find((plan) => plan.activeCount > 0);
42
+ const withoutKey = plans.find((plan) => plan.activeCount === 0 && plan.canCreate);
43
+ if (!withKeys || !withoutKey) return null;
44
+ const fresh = withoutKey.displayName || withoutKey.planSku;
45
+ const old = withKeys.displayName || withKeys.planSku;
46
+ return {
47
+ planSku: withoutKey.planSku,
48
+ text: `You're on ${fresh} now. Your ${old} key still has ${old} limits. Create a ${fresh} key and use it in your app to get ${fresh} limits.`
49
+ };
50
+ }
51
+
52
+ //#endregion
53
+ export { ChoosePlanNotice as n, keyPlanNote as t };
@@ -0,0 +1,166 @@
1
+ import { n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-BstWzITX.mjs";
2
+ import { n as useDevPortalBillingConfig } from "./DevPortalBillingContext-CR83s4LO.mjs";
3
+ import { n as createDemoApiKeys, t as DEMO_GATEWAY_KEY_HEADER } from "./demo-session-rN2Gj4lM.mjs";
4
+ import { useMutation, useQuery, useQueryClient } from "@lukoweb/apitogo/react-query";
5
+ import { useAuth } from "@lukoweb/apitogo/hooks";
6
+
7
+ //#region src/utils/playground-key.ts
8
+ const STORAGE_KEY = "apitogo-playground-api-key";
9
+ const PLAYGROUND_KEY_IDENTITY_ID = "api-key";
10
+ const storage = () => {
11
+ try {
12
+ return typeof localStorage === "undefined" ? void 0 : localStorage;
13
+ } catch {
14
+ return;
15
+ }
16
+ };
17
+ /**
18
+ * Remembers a key the moment it is created: the plans backend shows a key's secret only then, so this is the one
19
+ * chance to offer it to the playground later. Only the last key is kept, in this browser only.
20
+ */
21
+ function rememberPlaygroundKey(entry) {
22
+ if (!entry.key) return;
23
+ const value = {
24
+ id: entry.id,
25
+ key: entry.key,
26
+ header: entry.header || DEMO_GATEWAY_KEY_HEADER,
27
+ keyPrefix: entry.keyPrefix
28
+ };
29
+ try {
30
+ storage()?.setItem(STORAGE_KEY, JSON.stringify(value));
31
+ } catch {}
32
+ }
33
+ function readPlaygroundKey() {
34
+ try {
35
+ const raw = storage()?.getItem(STORAGE_KEY);
36
+ if (!raw) return void 0;
37
+ const parsed = JSON.parse(raw);
38
+ return parsed.id && parsed.key && parsed.header && parsed.keyPrefix ? parsed : void 0;
39
+ } catch {
40
+ return;
41
+ }
42
+ }
43
+ /** A revoked key stops working, so it is no longer offered. */
44
+ function forgetPlaygroundKey(keyId) {
45
+ if (readPlaygroundKey()?.id !== keyId) return;
46
+ try {
47
+ storage()?.removeItem(STORAGE_KEY);
48
+ } catch {}
49
+ }
50
+ /** The "Try it" identity for the remembered key, sent in the gateway's key header. */
51
+ function playgroundKeyIdentities() {
52
+ const remembered = readPlaygroundKey();
53
+ if (!remembered) return [];
54
+ return [{
55
+ id: PLAYGROUND_KEY_IDENTITY_ID,
56
+ label: `Your API key (${remembered.keyPrefix}…)`,
57
+ authorizationFields: { headers: [remembered.header] },
58
+ authorizeRequest: (request) => {
59
+ request.headers.set(remembered.header, remembered.key);
60
+ return request;
61
+ }
62
+ }];
63
+ }
64
+
65
+ //#endregion
66
+ //#region src/hooks/useApiKeys.ts
67
+ /** Thrown when the developer has hit their plan's per-developer key cap. */
68
+ const API_KEY_CAP_REACHED = "API_KEY_CAP_REACHED";
69
+ function apiKeysQueryKey(apiBaseUrl) {
70
+ return ["dev-portal-api-keys", apiBaseUrl];
71
+ }
72
+ function useApiKeys() {
73
+ const auth = useAuth();
74
+ const apiBaseUrl = useDevPortalApiBaseUrl();
75
+ const isPreview = useDevPortalPreviewMode();
76
+ const isDemoSession = useIsDemoSession();
77
+ const { previewPlans } = useDevPortalBillingConfig();
78
+ const liveQuery = useQuery({
79
+ queryKey: apiKeysQueryKey(apiBaseUrl),
80
+ enabled: auth.isAuthenticated && !isPreview && !isDemoSession && Boolean(apiBaseUrl),
81
+ queryFn: async () => {
82
+ const response = await fetch(`${apiBaseUrl}/api/v1/api-keys`, {
83
+ method: "GET",
84
+ headers: { Accept: "application/json" },
85
+ credentials: "include"
86
+ });
87
+ if (response.status === 404) throw new Error("NO_ENTITLEMENT");
88
+ if (!response.ok) throw new Error("Failed to load API keys.");
89
+ return await response.json();
90
+ },
91
+ retry: (failureCount, error) => error instanceof Error && error.message === "NO_ENTITLEMENT" ? false : failureCount < 2
92
+ });
93
+ if (isDemoSession && auth.isAuthenticated) return {
94
+ ...liveQuery,
95
+ data: createDemoApiKeys(previewPlans),
96
+ error: null,
97
+ isError: false,
98
+ isFetching: false,
99
+ isLoading: false,
100
+ isPending: false,
101
+ isSuccess: true,
102
+ status: "success"
103
+ };
104
+ return liveQuery;
105
+ }
106
+ function useCreateApiKey() {
107
+ const apiBaseUrl = useDevPortalApiBaseUrl();
108
+ const queryClient = useQueryClient();
109
+ const { keyHeader } = useDevPortalBillingConfig();
110
+ return useMutation({
111
+ mutationFn: async ({ name, planSku }) => {
112
+ if (!apiBaseUrl) throw new Error("Dev portal API is not configured.");
113
+ const response = await fetch(`${apiBaseUrl}/api/v1/api-keys`, {
114
+ method: "POST",
115
+ headers: {
116
+ Accept: "application/json",
117
+ "Content-Type": "application/json"
118
+ },
119
+ credentials: "include",
120
+ body: JSON.stringify({
121
+ name,
122
+ planSku
123
+ })
124
+ });
125
+ if (response.status === 409) throw new Error(API_KEY_CAP_REACHED);
126
+ if (!response.ok) throw new Error(await readProblemDetail(response) ?? "Failed to create API key.");
127
+ return await response.json();
128
+ },
129
+ onSuccess: async (created) => {
130
+ rememberPlaygroundKey({
131
+ ...created,
132
+ header: keyHeader?.trim() || queryClient.getQueryData(apiKeysQueryKey(apiBaseUrl))?.header
133
+ });
134
+ await queryClient.invalidateQueries({ queryKey: apiKeysQueryKey(apiBaseUrl) });
135
+ }
136
+ });
137
+ }
138
+ function useRevokeApiKey() {
139
+ const apiBaseUrl = useDevPortalApiBaseUrl();
140
+ const queryClient = useQueryClient();
141
+ return useMutation({
142
+ mutationFn: async (keyId) => {
143
+ if (!apiBaseUrl) throw new Error("Dev portal API is not configured.");
144
+ const response = await fetch(`${apiBaseUrl}/api/v1/api-keys/${keyId}`, {
145
+ method: "DELETE",
146
+ headers: { Accept: "application/json" },
147
+ credentials: "include"
148
+ });
149
+ if (!response.ok && response.status !== 204) throw new Error(await readProblemDetail(response) ?? "Failed to revoke API key.");
150
+ },
151
+ onSuccess: async (_result, keyId) => {
152
+ forgetPlaygroundKey(keyId);
153
+ await queryClient.invalidateQueries({ queryKey: apiKeysQueryKey(apiBaseUrl) });
154
+ }
155
+ });
156
+ }
157
+ async function readProblemDetail(response) {
158
+ try {
159
+ return (await response.json()).detail?.trim() || null;
160
+ } catch {
161
+ return null;
162
+ }
163
+ }
164
+
165
+ //#endregion
166
+ export { playgroundKeyIdentities as a, useRevokeApiKey as i, useApiKeys as n, useCreateApiKey as r, API_KEY_CAP_REACHED as t };
@@ -250,4 +250,4 @@ function useDevPortalPreviewMode() {
250
250
  }
251
251
 
252
252
  //#endregion
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 };
253
+ export { rateLimitCell as _, formatPlanPrice as a, isPlaceholderDevPortalApiUrl as c, listPlanFeatureLines as d, parseManifestPlans as f, quotaCell as g, pickFeaturedPlanSku as h, formatPlanDisplayPrice as i, isPlanCustomPriced as l, parseQuotaPeriodDays as m, useDevPortalPreviewMode as n, formatPlanPriceUnit as o, parseMonthlyQuotaCalls as p, useIsDemoSession as r, isActiveSubscription as s, useDevPortalApiBaseUrl as t, isPlanFree as u, resolveDevPortalApiBaseUrl as v };
@@ -1,6 +1,6 @@
1
- import { n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-Dk00H8xy.mjs";
1
+ import { n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-BstWzITX.mjs";
2
2
  import { n as useDevPortalBillingConfig } from "./DevPortalBillingContext-CR83s4LO.mjs";
3
- import { i as createDemoMeResponse } from "./demo-session-B6WAunKV.mjs";
3
+ import { i as createDemoMeResponse } from "./demo-session-rN2Gj4lM.mjs";
4
4
  import { useQuery } from "@lukoweb/apitogo/react-query";
5
5
  import { useAuth } from "@lukoweb/apitogo/hooks";
6
6
 
@@ -1,4 +1,4 @@
1
- import { l as isPlaceholderDevPortalApiUrl, n as useDevPortalPreviewMode, p as parseManifestPlans, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-Dk00H8xy.mjs";
1
+ import { c as isPlaceholderDevPortalApiUrl, f as parseManifestPlans, n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-BstWzITX.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-Dk00H8xy.mjs";
1
+ import { n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-BstWzITX.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.50",
3
+ "version": "0.2.52",
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.50",
50
- "@lukoweb/apitogo-module-user-panel": "0.2.50"
49
+ "@lukoweb/apitogo": "0.2.52",
50
+ "@lukoweb/apitogo-module-user-panel": "0.2.52"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "react": ">=19.2.0",