@lukoweb/apitogo-plugin-dev-portal-billing 0.2.51 → 0.2.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{AccountPlansPage-BEDZbyAS.mjs → AccountPlansPage-DvRFe3di.mjs} +5 -63
- package/dist/{CheckoutPage-6nFq9GKO.mjs → CheckoutPage-CwSElFUk.mjs} +2 -2
- package/dist/{ContactSalesPage-Imh39E8z.mjs → ContactSalesPage-q7Dxxtx6.mjs} +2 -2
- package/dist/{CredentialsPage-D9voniJR.mjs → CredentialsPage-B2jPo6Go.mjs} +34 -24
- package/dist/{PricingCard-D-owYlf3.mjs → PricingCard-CKdq1oWn.mjs} +1 -1
- package/dist/{PricingPage-BTPQ-ntp.mjs → PricingPage-DnQQfy9L.mjs} +5 -5
- package/dist/{SubscriptionsPage-Bo5D3K1x.mjs → SubscriptionsPage-msO31jv-.mjs} +26 -24
- package/dist/{UsagePage-CK6p01Ut.mjs → UsagePage-BF4QgrTc.mjs} +4 -4
- package/dist/{account-links-Dg4ugnGW.mjs → account-links-D_vS0GJn.mjs} +2 -165
- package/dist/{billing-state-DjUXQYSr.mjs → billing-state-DbU5Lpoz.mjs} +1 -1
- package/dist/index.mjs +16 -15
- package/dist/{ChoosePlanNotice-DITJNrh1.mjs → key-plan-note-DTWeFhn2.mjs} +20 -1
- package/dist/useApiKeys-CVgXfUop.mjs +166 -0
- package/dist/{useDevPortalApiBaseUrl-Dk00H8xy.mjs → useDevPortalApiBaseUrl-BstWzITX.mjs} +1 -1
- package/dist/{useMySubscriptions-B4iV7zFs.mjs → useMySubscriptions-BtxAXA1m.mjs} +1 -1
- package/dist/{usePlans-DVGt2Myv.mjs → usePlans-DPGFWcVo.mjs} +1 -1
- package/dist/{useUsage-DOUibue4.mjs → useUsage--5sZub3M.mjs} +1 -1
- package/package.json +3 -3
- /package/dist/{pricing-config-CfhRX9tk.mjs → pricing-config-02m4dyFf.mjs} +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as PricingCard } from "./PricingCard-
|
|
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-
|
|
5
|
-
import { t as usePlans } from "./usePlans-
|
|
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";
|
|
@@ -55,42 +55,6 @@ function useCancelSubscription() {
|
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
//#endregion
|
|
59
|
-
//#region src/hooks/useChangePlan.ts
|
|
60
|
-
function useChangePlan() {
|
|
61
|
-
const apiBaseUrl = useDevPortalApiBaseUrl();
|
|
62
|
-
const queryClient = useQueryClient();
|
|
63
|
-
return useMutation({
|
|
64
|
-
mutationFn: async ({ fromPlanSku, toPlanSku }) => {
|
|
65
|
-
if (!apiBaseUrl) throw new Error("Dev portal API is not configured.");
|
|
66
|
-
const response = await fetch(`${apiBaseUrl}/api/v1/subscriptions/change`, {
|
|
67
|
-
method: "POST",
|
|
68
|
-
headers: {
|
|
69
|
-
Accept: "application/json",
|
|
70
|
-
"Content-Type": "application/json"
|
|
71
|
-
},
|
|
72
|
-
credentials: "include",
|
|
73
|
-
body: JSON.stringify({
|
|
74
|
-
fromPlanSku,
|
|
75
|
-
toPlanSku
|
|
76
|
-
})
|
|
77
|
-
});
|
|
78
|
-
if (!response.ok) {
|
|
79
|
-
const detail = await response.text();
|
|
80
|
-
throw new Error(detail || "Failed to change plan.");
|
|
81
|
-
}
|
|
82
|
-
return await response.json();
|
|
83
|
-
},
|
|
84
|
-
onSuccess: async (result) => {
|
|
85
|
-
if (result.checkoutUrl) {
|
|
86
|
-
window.location.assign(result.checkoutUrl);
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
await queryClient.invalidateQueries({ queryKey: ["dev-portal-me", apiBaseUrl] });
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
|
|
94
58
|
//#endregion
|
|
95
59
|
//#region src/pages/AccountPlansPage.tsx
|
|
96
60
|
var AccountPlansPage_exports = /* @__PURE__ */ __exportAll({ default: () => AccountPlansPage });
|
|
@@ -106,24 +70,8 @@ const AccountPlansPage = () => {
|
|
|
106
70
|
const isError = isPreview ? false : livePlans.isError;
|
|
107
71
|
const { data: me } = useMySubscriptions();
|
|
108
72
|
const cancelMutation = useCancelSubscription();
|
|
109
|
-
const changeMutation = useChangePlan();
|
|
110
73
|
const [cancelingSku, setCancelingSku] = useState(null);
|
|
111
|
-
const [switchingSku, setSwitchingSku] = useState(null);
|
|
112
74
|
const subscriptionBySku = new Map((me?.subscriptions ?? []).map((sub) => [sub.planSku, sub]));
|
|
113
|
-
const activePlanSkus = (me?.subscriptions ?? []).filter((sub) => isActiveSubscription(sub.status)).map((sub) => sub.planSku);
|
|
114
|
-
const currentPlanSku = activePlanSkus.length === 1 ? activePlanSkus[0] : null;
|
|
115
|
-
const handleSwitch = async (toPlanSku) => {
|
|
116
|
-
if (!currentPlanSku) return;
|
|
117
|
-
setSwitchingSku(toPlanSku);
|
|
118
|
-
try {
|
|
119
|
-
await changeMutation.mutateAsync({
|
|
120
|
-
fromPlanSku: currentPlanSku,
|
|
121
|
-
toPlanSku
|
|
122
|
-
});
|
|
123
|
-
} finally {
|
|
124
|
-
setSwitchingSku(null);
|
|
125
|
-
}
|
|
126
|
-
};
|
|
127
75
|
const handleUnsubscribe = async (planSku) => {
|
|
128
76
|
setCancelingSku(planSku);
|
|
129
77
|
try {
|
|
@@ -150,7 +98,7 @@ const AccountPlansPage = () => {
|
|
|
150
98
|
}),
|
|
151
99
|
/* @__PURE__ */ jsx("p", {
|
|
152
100
|
className: "max-w-2xl text-muted-foreground",
|
|
153
|
-
children: "Compare plans
|
|
101
|
+
children: "Compare plans and manage your subscriptions. You can hold several plans at once, and each has its own API keys and limits."
|
|
154
102
|
}),
|
|
155
103
|
checkoutCancelled ? /* @__PURE__ */ jsx("p", {
|
|
156
104
|
className: "max-w-2xl text-sm text-muted-foreground",
|
|
@@ -167,10 +115,6 @@ const AccountPlansPage = () => {
|
|
|
167
115
|
className: "mb-6 rounded-xl border border-destructive/30 bg-destructive/10 p-4 text-sm text-destructive",
|
|
168
116
|
children: cancelMutation.error instanceof Error ? cancelMutation.error.message : "Failed to cancel subscription."
|
|
169
117
|
}) : null,
|
|
170
|
-
changeMutation.isError ? /* @__PURE__ */ jsx("div", {
|
|
171
|
-
className: "mb-6 rounded-xl border border-destructive/30 bg-destructive/10 p-4 text-sm text-destructive",
|
|
172
|
-
children: changeMutation.error instanceof Error ? changeMutation.error.message : "Failed to change plan."
|
|
173
|
-
}) : null,
|
|
174
118
|
isLoading ? /* @__PURE__ */ jsx("p", {
|
|
175
119
|
className: "text-muted-foreground",
|
|
176
120
|
children: "Loading plans…"
|
|
@@ -190,8 +134,6 @@ const AccountPlansPage = () => {
|
|
|
190
134
|
subscriptionStatus: subscription?.status,
|
|
191
135
|
isSubscribed,
|
|
192
136
|
onUnsubscribe: isPreview || isDemoSession ? void 0 : handleUnsubscribe,
|
|
193
|
-
onSwitch: currentPlanSku && !isPreview && !isDemoSession ? handleSwitch : void 0,
|
|
194
|
-
isSwitching: switchingSku === plan.sku,
|
|
195
137
|
isCanceling: cancelingSku === plan.sku,
|
|
196
138
|
cancelDisabled: isPreview || isDemoSession
|
|
197
139
|
}, plan.sku);
|
|
@@ -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-
|
|
3
|
-
import { t as FREE_CHECKOUT_MARKER } from "./billing-state-
|
|
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
|
|
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-
|
|
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-
|
|
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
4
|
import { r as createDemoCredentials, t as DEMO_GATEWAY_KEY_HEADER } from "./demo-session-rN2Gj4lM.mjs";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
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";
|
|
@@ -63,6 +64,7 @@ const CredentialsPage = () => {
|
|
|
63
64
|
const gatewayBaseUrl = credentials?.gatewayBaseUrl?.trim() || apiKeys?.gatewayBaseUrl?.trim() || configuredGatewayBaseUrl?.trim() || zudokuConfig.__meta?.gatewayBaseUrl?.trim() || null;
|
|
64
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: [
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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: "
|
|
160
|
+
children: "Available after your API is deployed to the gateway."
|
|
145
161
|
})]
|
|
146
|
-
})
|
|
147
|
-
|
|
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 {
|
|
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 {
|
|
2
|
-
import { t as PricingCard } from "./PricingCard-
|
|
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-
|
|
5
|
-
import { t as isPreviewPricingEnabled } from "./pricing-config-
|
|
6
|
-
import { a as isPricingLoading, n as buildCompareRows } from "./billing-state-
|
|
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 {
|
|
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-
|
|
5
|
-
import { t as usePlans } from "./usePlans-
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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 {
|
|
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-
|
|
4
|
-
import { t as usePlans } from "./usePlans-
|
|
5
|
-
import { t as useUsage } from "./useUsage
|
|
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,177 +1,14 @@
|
|
|
1
|
-
import {
|
|
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";
|
|
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
|
-
const { keyHeader } = useDevPortalBillingConfig();
|
|
119
|
-
return useMutation({
|
|
120
|
-
mutationFn: async ({ name, planSku }) => {
|
|
121
|
-
if (!apiBaseUrl) throw new Error("Dev portal API is not configured.");
|
|
122
|
-
const response = await fetch(`${apiBaseUrl}/api/v1/api-keys`, {
|
|
123
|
-
method: "POST",
|
|
124
|
-
headers: {
|
|
125
|
-
Accept: "application/json",
|
|
126
|
-
"Content-Type": "application/json"
|
|
127
|
-
},
|
|
128
|
-
credentials: "include",
|
|
129
|
-
body: JSON.stringify({
|
|
130
|
-
name,
|
|
131
|
-
planSku
|
|
132
|
-
})
|
|
133
|
-
});
|
|
134
|
-
if (response.status === 409) throw new Error(API_KEY_CAP_REACHED);
|
|
135
|
-
if (!response.ok) throw new Error(await readProblemDetail(response) ?? "Failed to create API key.");
|
|
136
|
-
return await response.json();
|
|
137
|
-
},
|
|
138
|
-
onSuccess: async (created) => {
|
|
139
|
-
rememberPlaygroundKey({
|
|
140
|
-
...created,
|
|
141
|
-
header: keyHeader?.trim() || queryClient.getQueryData(apiKeysQueryKey(apiBaseUrl))?.header
|
|
142
|
-
});
|
|
143
|
-
await queryClient.invalidateQueries({ queryKey: apiKeysQueryKey(apiBaseUrl) });
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
function useRevokeApiKey() {
|
|
148
|
-
const apiBaseUrl = useDevPortalApiBaseUrl();
|
|
149
|
-
const queryClient = useQueryClient();
|
|
150
|
-
return useMutation({
|
|
151
|
-
mutationFn: async (keyId) => {
|
|
152
|
-
if (!apiBaseUrl) throw new Error("Dev portal API is not configured.");
|
|
153
|
-
const response = await fetch(`${apiBaseUrl}/api/v1/api-keys/${keyId}`, {
|
|
154
|
-
method: "DELETE",
|
|
155
|
-
headers: { Accept: "application/json" },
|
|
156
|
-
credentials: "include"
|
|
157
|
-
});
|
|
158
|
-
if (!response.ok && response.status !== 204) throw new Error(await readProblemDetail(response) ?? "Failed to revoke API key.");
|
|
159
|
-
},
|
|
160
|
-
onSuccess: async (_result, keyId) => {
|
|
161
|
-
forgetPlaygroundKey(keyId);
|
|
162
|
-
await queryClient.invalidateQueries({ queryKey: apiKeysQueryKey(apiBaseUrl) });
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
async function readProblemDetail(response) {
|
|
167
|
-
try {
|
|
168
|
-
return (await response.json()).detail?.trim() || null;
|
|
169
|
-
} catch {
|
|
170
|
-
return null;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
//#endregion
|
|
175
12
|
//#region src/components/ApiKeysPanel.tsx
|
|
176
13
|
/**
|
|
177
14
|
* Lists gateway API keys for one plan with create and revoke. The plaintext key is returned by the
|
|
@@ -369,4 +206,4 @@ function apiReferencePath(links) {
|
|
|
369
206
|
}
|
|
370
207
|
|
|
371
208
|
//#endregion
|
|
372
|
-
export {
|
|
209
|
+
export { apiReferencePath as n, ApiKeysPanel as r, accountLayoutProps as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _ as
|
|
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. */
|
package/dist/index.mjs
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { t as AccountPlansPage } from "./AccountPlansPage-
|
|
1
|
+
import { t as AccountPlansPage } from "./AccountPlansPage-DvRFe3di.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 {
|
|
4
|
-
import "./PricingCard-
|
|
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
5
|
import { n as useDevPortalBillingConfig, t as DevPortalBillingProvider } from "./DevPortalBillingContext-CR83s4LO.mjs";
|
|
6
6
|
import { t as DEMO_GATEWAY_KEY_HEADER } from "./demo-session-rN2Gj4lM.mjs";
|
|
7
|
-
import { t as useMySubscriptions } from "./useMySubscriptions-
|
|
8
|
-
import { n as startPlansPrefetch, t as usePlans } from "./usePlans-
|
|
9
|
-
import { a as
|
|
10
|
-
import { n as useUsageRecent, t as useUsage } from "./useUsage
|
|
11
|
-
import { t as
|
|
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";
|
|
12
13
|
import { Head, Link } from "@lukoweb/apitogo/components";
|
|
13
14
|
import { ArrowRightIcon, KeyRoundIcon, StarsIcon } from "@lukoweb/apitogo/icons";
|
|
14
15
|
import { Outlet } from "@lukoweb/apitogo/router";
|
|
@@ -365,13 +366,13 @@ async function getSignedInIdentities(apiBaseUrl, fetchImpl = fetch) {
|
|
|
365
366
|
|
|
366
367
|
//#endregion
|
|
367
368
|
//#region src/DevPortalBillingPlugin.tsx
|
|
368
|
-
const AccountPlansPage$1 = lazy(() => import("./AccountPlansPage-
|
|
369
|
-
const CheckoutPage = lazy(() => import("./CheckoutPage-
|
|
370
|
-
const ContactSalesPage = lazy(() => import("./ContactSalesPage-
|
|
371
|
-
const CredentialsPage = lazy(() => import("./CredentialsPage-
|
|
372
|
-
const PricingPage = lazy(() => import("./PricingPage-
|
|
373
|
-
const SubscriptionsPage = lazy(() => import("./SubscriptionsPage-
|
|
374
|
-
const UsagePage = lazy(() => import("./UsagePage-
|
|
369
|
+
const AccountPlansPage$1 = lazy(() => import("./AccountPlansPage-DvRFe3di.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"));
|
|
375
376
|
function resolvePricingEnabled(config) {
|
|
376
377
|
if (config.__meta?.pricingEnabled !== void 0) return config.__meta.pricingEnabled;
|
|
377
378
|
return isPreviewPricingEnabled();
|
|
@@ -31,4 +31,23 @@ const ChoosePlanNotice = ({ className = "rounded-xl border border-dashed p-8 tex
|
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
//#endregion
|
|
34
|
-
|
|
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 {
|
|
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,4 +1,4 @@
|
|
|
1
|
-
import { n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-
|
|
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
3
|
import { i as createDemoMeResponse } from "./demo-session-rN2Gj4lM.mjs";
|
|
4
4
|
import { useQuery } from "@lukoweb/apitogo/react-query";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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-
|
|
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.
|
|
3
|
+
"version": "0.2.53",
|
|
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
|
-
"@lukoweb/apitogo-module-user-panel": "0.2.
|
|
49
|
+
"@lukoweb/apitogo": "0.2.53",
|
|
50
|
+
"@lukoweb/apitogo-module-user-panel": "0.2.53"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"react": ">=19.2.0",
|
|
File without changes
|