@lukoweb/apitogo-plugin-dev-portal-billing 0.2.53 → 0.2.54
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-DvRFe3di.mjs → AccountPlansPage-0PYGy1aV.mjs} +5 -7
- package/dist/{CheckoutPage-CwSElFUk.mjs → CheckoutPage-Bpa128i2.mjs} +26 -14
- package/dist/{ContactSalesPage-q7Dxxtx6.mjs → ContactSalesPage-BslD3Yk4.mjs} +3 -10
- package/dist/{CredentialsPage-B2jPo6Go.mjs → CredentialsPage-C4qpBVrh.mjs} +27 -18
- package/dist/{PricingCard-CKdq1oWn.mjs → PricingCard-YJIEwydG.mjs} +1 -1
- package/dist/{PricingPage-DnQQfy9L.mjs → PricingPage-DIhoFOd9.mjs} +4 -4
- package/dist/SubscriptionsPage-D7hsYtTB.mjs +268 -0
- package/dist/{UsagePage-BF4QgrTc.mjs → UsagePage-Di9kQ_3Z.mjs} +60 -18
- package/dist/{account-links-D_vS0GJn.mjs → account-links-9h91uCGj.mjs} +1 -1
- package/dist/index.mjs +19 -19
- package/dist/{key-plan-note-DTWeFhn2.mjs → page-state-BB-72pbw.mjs} +22 -2
- package/dist/paths-DaVyTIDJ.mjs +14 -0
- package/dist/problem-details-n20-dy3w.mjs +18 -0
- package/dist/{useApiKeys-CVgXfUop.mjs → useApiKeys-CEwMzqju.mjs} +2 -8
- package/dist/{useMySubscriptions-BtxAXA1m.mjs → useMySubscriptions-DuVq2tXL.mjs} +1 -1
- package/package.json +3 -3
- package/dist/SubscriptionsPage-msO31jv-.mjs +0 -223
- package/dist/paths-ujy9wjVK.mjs +0 -12
- /package/dist/{billing-state-DbU5Lpoz.mjs → billing-state-Dgcc3_pT.mjs} +0 -0
- /package/dist/{demo-session-rN2Gj4lM.mjs → demo-session-yK9qEqV-.mjs} +0 -0
- /package/dist/{pricing-config-02m4dyFf.mjs → pricing-config-C44AIqql.mjs} +0 -0
- /package/dist/{usePlans-DPGFWcVo.mjs → usePlans-BF7Hy2qJ.mjs} +0 -0
- /package/dist/{useUsage--5sZub3M.mjs → useUsage-CJ9VIBc7.mjs} +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
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-
|
|
2
|
+
import { t as PricingCard } from "./PricingCard-YJIEwydG.mjs";
|
|
3
3
|
import { n as useDevPortalBillingConfig } from "./DevPortalBillingContext-CR83s4LO.mjs";
|
|
4
|
-
import {
|
|
5
|
-
import { t as
|
|
4
|
+
import { n as readProblemDetail } from "./problem-details-n20-dy3w.mjs";
|
|
5
|
+
import { t as useMySubscriptions } from "./useMySubscriptions-DuVq2tXL.mjs";
|
|
6
|
+
import { t as usePlans } from "./usePlans-BF7Hy2qJ.mjs";
|
|
6
7
|
import { Head } from "@lukoweb/apitogo/components";
|
|
7
8
|
import { CreditCardIcon } from "@lukoweb/apitogo/icons";
|
|
8
9
|
import { useSearchParams } from "@lukoweb/apitogo/router";
|
|
@@ -43,10 +44,7 @@ function useCancelSubscription() {
|
|
|
43
44
|
credentials: "include",
|
|
44
45
|
body: JSON.stringify({ planSku })
|
|
45
46
|
});
|
|
46
|
-
if (!response.ok)
|
|
47
|
-
const detail = await response.text();
|
|
48
|
-
throw new Error(detail || "Failed to cancel subscription.");
|
|
49
|
-
}
|
|
47
|
+
if (!response.ok) throw new Error(await readProblemDetail(response) ?? "Failed to cancel subscription.");
|
|
50
48
|
return await response.json();
|
|
51
49
|
},
|
|
52
50
|
onSuccess: async () => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as ALREADY_SUBSCRIBED_PARAM, s as PRICING_PATH, t as ACCOUNT_PLANS_PATH } from "./paths-DaVyTIDJ.mjs";
|
|
2
2
|
import { n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-BstWzITX.mjs";
|
|
3
|
-
import { t as
|
|
3
|
+
import { n as readProblemDetail, t as isAlreadySubscribedMessage } from "./problem-details-n20-dy3w.mjs";
|
|
4
|
+
import { t as FREE_CHECKOUT_MARKER } from "./billing-state-Dgcc3_pT.mjs";
|
|
4
5
|
import { ShieldIcon } from "@lukoweb/apitogo/icons";
|
|
5
6
|
import { Link, useNavigate, useSearchParams } from "@lukoweb/apitogo/router";
|
|
6
7
|
import { useEffect } from "react";
|
|
@@ -81,14 +82,19 @@ const CheckoutPage = () => {
|
|
|
81
82
|
credentials: "include",
|
|
82
83
|
body: JSON.stringify({ planSku })
|
|
83
84
|
});
|
|
84
|
-
if (!response.ok)
|
|
85
|
-
const detail = await response.text();
|
|
86
|
-
throw new Error(detail || "Checkout failed.");
|
|
87
|
-
}
|
|
85
|
+
if (!response.ok) throw new Error(await readProblemDetail(response) ?? "Checkout failed.");
|
|
88
86
|
return await response.json();
|
|
89
87
|
}
|
|
90
88
|
});
|
|
91
89
|
const freeProvisioned = checkoutQuery.data?.checkoutUrl == null && checkoutQuery.data?.entitlementProvisioned === true;
|
|
90
|
+
const alreadySubscribed = checkoutQuery.isError && isAlreadySubscribedMessage(checkoutQuery.error.message);
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
if (alreadySubscribed) navigate(`/account/subscriptions?${ALREADY_SUBSCRIBED_PARAM}=${encodeURIComponent(planSku)}`, { replace: true });
|
|
93
|
+
}, [
|
|
94
|
+
alreadySubscribed,
|
|
95
|
+
navigate,
|
|
96
|
+
planSku
|
|
97
|
+
]);
|
|
92
98
|
useEffect(() => {
|
|
93
99
|
if (freeProvisioned) navigate(`/account/subscriptions?checkout=success&${FREE_CHECKOUT_MARKER}`, { replace: true });
|
|
94
100
|
}, [freeProvisioned, navigate]);
|
|
@@ -146,13 +152,13 @@ const CheckoutPage = () => {
|
|
|
146
152
|
})
|
|
147
153
|
]
|
|
148
154
|
});
|
|
149
|
-
if (freeProvisioned) return null;
|
|
150
|
-
return /* @__PURE__ */
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
155
|
+
if (freeProvisioned || alreadySubscribed) return null;
|
|
156
|
+
if (checkoutQuery.isError) return /* @__PURE__ */ jsxs("div", {
|
|
157
|
+
className: "mx-auto flex min-h-[50vh] max-w-lg flex-col items-center justify-center gap-4 px-4 text-center",
|
|
158
|
+
children: [/* @__PURE__ */ jsx("h1", {
|
|
159
|
+
className: "text-2xl font-semibold tracking-tight",
|
|
160
|
+
children: "Checkout could not start"
|
|
161
|
+
}), /* @__PURE__ */ jsxs(Alert, {
|
|
156
162
|
variant: "destructive",
|
|
157
163
|
children: [/* @__PURE__ */ jsx(AlertDescription, {
|
|
158
164
|
className: "first-letter:uppercase",
|
|
@@ -166,7 +172,13 @@ const CheckoutPage = () => {
|
|
|
166
172
|
children: "Back to plans"
|
|
167
173
|
})
|
|
168
174
|
}) })]
|
|
169
|
-
})
|
|
175
|
+
})]
|
|
176
|
+
});
|
|
177
|
+
return /* @__PURE__ */ jsx(RedirectPage, {
|
|
178
|
+
icon: ShieldIcon,
|
|
179
|
+
title: "Establishing secure checkout…",
|
|
180
|
+
description: "Redirecting you to complete your subscription.",
|
|
181
|
+
url: checkoutQuery.data?.checkoutUrl ?? void 0
|
|
170
182
|
});
|
|
171
183
|
};
|
|
172
184
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { s as PRICING_PATH } from "./paths-DaVyTIDJ.mjs";
|
|
2
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 {
|
|
4
|
+
import { n as readProblemDetail } from "./problem-details-n20-dy3w.mjs";
|
|
5
|
+
import { t as usePlans } from "./usePlans-BF7Hy2qJ.mjs";
|
|
5
6
|
import { Link, useSearchParams } from "@lukoweb/apitogo/router";
|
|
6
7
|
import { useState } from "react";
|
|
7
8
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -32,14 +33,6 @@ function useSubmitPlanInquiry() {
|
|
|
32
33
|
if (!response.ok) throw new Error(await readProblemDetail(response) ?? "Your message could not be sent. Please try again later.");
|
|
33
34
|
} });
|
|
34
35
|
}
|
|
35
|
-
async function readProblemDetail(response) {
|
|
36
|
-
try {
|
|
37
|
-
const body = await response.json();
|
|
38
|
-
return (body.errors ? Object.values(body.errors).flat()[0] : void 0) ?? body.detail ?? body.title ?? null;
|
|
39
|
-
} catch {
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
36
|
|
|
44
37
|
//#endregion
|
|
45
38
|
//#region src/pages/ContactSalesPage.tsx
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { t as ACCOUNT_PLANS_PATH } from "./paths-
|
|
1
|
+
import { t as ACCOUNT_PLANS_PATH } from "./paths-DaVyTIDJ.mjs";
|
|
2
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, t as DEMO_GATEWAY_KEY_HEADER } from "./demo-session-
|
|
5
|
-
import { n as useApiKeys } from "./useApiKeys-
|
|
6
|
-
import { n as apiReferencePath, r as ApiKeysPanel } from "./account-links-
|
|
7
|
-
import {
|
|
4
|
+
import { r as createDemoCredentials, t as DEMO_GATEWAY_KEY_HEADER } from "./demo-session-yK9qEqV-.mjs";
|
|
5
|
+
import { n as useApiKeys } from "./useApiKeys-CEwMzqju.mjs";
|
|
6
|
+
import { n as apiReferencePath, r as ApiKeysPanel } from "./account-links-9h91uCGj.mjs";
|
|
7
|
+
import { i as ChoosePlanNotice, r as keyPlanNote, t as apiKeysPageState } from "./page-state-BB-72pbw.mjs";
|
|
8
8
|
import { Head } from "@lukoweb/apitogo/components";
|
|
9
9
|
import { KeyRoundIcon } from "@lukoweb/apitogo/icons";
|
|
10
10
|
import { Link as Link$1 } from "@lukoweb/apitogo/router";
|
|
@@ -57,18 +57,23 @@ const CredentialsPage = () => {
|
|
|
57
57
|
const auth = useAuth();
|
|
58
58
|
const isPreview = useDevPortalPreviewMode();
|
|
59
59
|
const isDemoSession = useIsDemoSession();
|
|
60
|
-
const { data: credentials
|
|
61
|
-
const { data: apiKeys,
|
|
60
|
+
const { data: credentials } = useCredentials();
|
|
61
|
+
const { data: apiKeys, error: keysError } = useApiKeys();
|
|
62
62
|
const { gatewayBaseUrl: configuredGatewayBaseUrl, links, keyHeader: configuredKeyHeader } = useDevPortalBillingConfig();
|
|
63
63
|
const apiReferenceLink = apiReferencePath(links);
|
|
64
64
|
const gatewayBaseUrl = credentials?.gatewayBaseUrl?.trim() || apiKeys?.gatewayBaseUrl?.trim() || configuredGatewayBaseUrl?.trim() || zudokuConfig.__meta?.gatewayBaseUrl?.trim() || null;
|
|
65
65
|
const header = configuredKeyHeader?.trim() || apiKeys?.header || DEMO_GATEWAY_KEY_HEADER;
|
|
66
66
|
const plans = apiKeys?.plans ?? [];
|
|
67
67
|
const planNote = isDemoSession ? null : keyPlanNote(plans);
|
|
68
|
+
const pageState = apiKeysPageState({
|
|
69
|
+
data: apiKeys,
|
|
70
|
+
error: keysError
|
|
71
|
+
});
|
|
72
|
+
const showsLiveData = auth.isAuthenticated && (isDemoSession || !isPreview);
|
|
68
73
|
return /* @__PURE__ */ jsxs("div", {
|
|
69
74
|
className: "mx-auto max-w-screen-lg px-4 pb-16 pt-(--padding-content-top) lg:px-8",
|
|
70
75
|
children: [
|
|
71
|
-
/* @__PURE__ */ jsx(Head, { children: /* @__PURE__ */ jsx("title", { children: "API
|
|
76
|
+
/* @__PURE__ */ jsx(Head, { children: /* @__PURE__ */ jsx("title", { children: "API keys" }) }),
|
|
72
77
|
/* @__PURE__ */ jsxs("div", {
|
|
73
78
|
className: "mb-8 flex items-start gap-4",
|
|
74
79
|
children: [/* @__PURE__ */ jsx("div", {
|
|
@@ -78,7 +83,7 @@ const CredentialsPage = () => {
|
|
|
78
83
|
className: "space-y-1",
|
|
79
84
|
children: [/* @__PURE__ */ jsx("h1", {
|
|
80
85
|
className: "text-3xl font-semibold tracking-tight",
|
|
81
|
-
children: "API
|
|
86
|
+
children: "API keys"
|
|
82
87
|
}), /* @__PURE__ */ jsx("p", {
|
|
83
88
|
className: "text-muted-foreground",
|
|
84
89
|
children: "Create API keys for each subscribed plan and use them to authenticate requests to the API gateway."
|
|
@@ -93,7 +98,7 @@ const CredentialsPage = () => {
|
|
|
93
98
|
className: "rounded-xl border border-dashed p-8 text-center",
|
|
94
99
|
children: /* @__PURE__ */ jsx("p", {
|
|
95
100
|
className: "text-muted-foreground",
|
|
96
|
-
children: "API
|
|
101
|
+
children: "API keys are available after the developer portal is published."
|
|
97
102
|
})
|
|
98
103
|
}) : null,
|
|
99
104
|
!isPreview && !isDemoSession && !auth.isAuthenticated ? /* @__PURE__ */ jsxs("div", {
|
|
@@ -110,15 +115,18 @@ const CredentialsPage = () => {
|
|
|
110
115
|
})
|
|
111
116
|
})]
|
|
112
117
|
}) : null,
|
|
113
|
-
|
|
114
|
-
className: "
|
|
115
|
-
|
|
118
|
+
showsLiveData && pageState === "loading" ? /* @__PURE__ */ jsx("div", {
|
|
119
|
+
className: "space-y-6",
|
|
120
|
+
role: "status",
|
|
121
|
+
"aria-busy": "true",
|
|
122
|
+
"aria-label": "Loading API keys",
|
|
123
|
+
children: [0, 1].map((i) => /* @__PURE__ */ jsx("div", { className: "h-40 animate-pulse rounded-xl border bg-muted/40" }, i))
|
|
116
124
|
}) : null,
|
|
117
|
-
|
|
125
|
+
showsLiveData && pageState === "failed" ? /* @__PURE__ */ jsxs("div", {
|
|
118
126
|
className: "rounded-xl border border-dashed p-8 text-center",
|
|
119
127
|
children: [/* @__PURE__ */ jsx("p", {
|
|
120
128
|
className: "text-muted-foreground",
|
|
121
|
-
children: "Unable to load API
|
|
129
|
+
children: "Unable to load your API keys. Try again in a moment."
|
|
122
130
|
}), /* @__PURE__ */ jsx(Button$1, {
|
|
123
131
|
className: "mt-4",
|
|
124
132
|
asChild: true,
|
|
@@ -128,7 +136,8 @@ const CredentialsPage = () => {
|
|
|
128
136
|
})
|
|
129
137
|
})]
|
|
130
138
|
}) : null,
|
|
131
|
-
|
|
139
|
+
showsLiveData && pageState === "empty" ? /* @__PURE__ */ jsx(ChoosePlanNotice, {}) : null,
|
|
140
|
+
showsLiveData && pageState === "ready" ? /* @__PURE__ */ jsxs("div", {
|
|
132
141
|
className: "space-y-6",
|
|
133
142
|
children: [
|
|
134
143
|
planNote ? /* @__PURE__ */ jsx("div", {
|
|
@@ -136,7 +145,7 @@ const CredentialsPage = () => {
|
|
|
136
145
|
className: "rounded-xl border border-primary/30 bg-primary/5 p-4 text-sm",
|
|
137
146
|
children: planNote.text
|
|
138
147
|
}) : null,
|
|
139
|
-
plans.
|
|
148
|
+
plans.map((plan) => /* @__PURE__ */ jsx(ApiKeysPanel, {
|
|
140
149
|
plan,
|
|
141
150
|
header,
|
|
142
151
|
readOnly: isDemoSession
|
|
@@ -169,7 +178,7 @@ const CredentialsPage = () => {
|
|
|
169
178
|
asChild: true,
|
|
170
179
|
children: /* @__PURE__ */ jsx(Link$1, {
|
|
171
180
|
to: "/account/subscriptions",
|
|
172
|
-
children: "
|
|
181
|
+
children: "Billing"
|
|
173
182
|
})
|
|
174
183
|
}), apiReferenceLink ? /* @__PURE__ */ jsx(Button$1, {
|
|
175
184
|
variant: "outline",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as CONTACT_SALES_PATH, r as CHECKOUT_PATH } from "./paths-DaVyTIDJ.mjs";
|
|
2
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";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { f as parseManifestPlans, h as pickFeaturedPlanSku, n as useDevPortalPreviewMode } from "./useDevPortalApiBaseUrl-BstWzITX.mjs";
|
|
2
|
-
import { t as PricingCard } from "./PricingCard-
|
|
2
|
+
import { t as PricingCard } from "./PricingCard-YJIEwydG.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-BF7Hy2qJ.mjs";
|
|
5
|
+
import { t as isPreviewPricingEnabled } from "./pricing-config-C44AIqql.mjs";
|
|
6
|
+
import { a as isPricingLoading, n as buildCompareRows } from "./billing-state-Dgcc3_pT.mjs";
|
|
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";
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { a as CREDENTIALS_PATH, n as ALREADY_SUBSCRIBED_PARAM, t as ACCOUNT_PLANS_PATH } from "./paths-DaVyTIDJ.mjs";
|
|
2
|
+
import { a as formatPlanPrice, p as parseMonthlyQuotaCalls, s as isActiveSubscription, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-BstWzITX.mjs";
|
|
3
|
+
import { n as useDevPortalBillingConfig } from "./DevPortalBillingContext-CR83s4LO.mjs";
|
|
4
|
+
import { t as useMySubscriptions } from "./useMySubscriptions-DuVq2tXL.mjs";
|
|
5
|
+
import { t as usePlans } from "./usePlans-BF7Hy2qJ.mjs";
|
|
6
|
+
import { n as useApiKeys } from "./useApiKeys-CEwMzqju.mjs";
|
|
7
|
+
import { t as useUsage } from "./useUsage-CJ9VIBc7.mjs";
|
|
8
|
+
import { i as hasPaymentProvider, r as checkoutConfirmingText } from "./billing-state-Dgcc3_pT.mjs";
|
|
9
|
+
import { i as ChoosePlanNotice, n as billingPageState, r as keyPlanNote } from "./page-state-BB-72pbw.mjs";
|
|
10
|
+
import { Head, Spinner } from "@lukoweb/apitogo/components";
|
|
11
|
+
import { Link as Link$1, useSearchParams } from "@lukoweb/apitogo/router";
|
|
12
|
+
import { useEffect } from "react";
|
|
13
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
14
|
+
import { useMutation } from "@lukoweb/apitogo/react-query";
|
|
15
|
+
import { useAuth } from "@lukoweb/apitogo/hooks";
|
|
16
|
+
import { Button as Button$1 } from "@lukoweb/apitogo/ui/Button.js";
|
|
17
|
+
|
|
18
|
+
//#region src/hooks/useBillingPortal.ts
|
|
19
|
+
function useBillingPortal() {
|
|
20
|
+
const apiBaseUrl = useDevPortalApiBaseUrl();
|
|
21
|
+
return useMutation({ mutationFn: async (returnUrl) => {
|
|
22
|
+
if (!apiBaseUrl) throw new Error("Dev portal API is not configured.");
|
|
23
|
+
const response = await fetch(`${apiBaseUrl}/api/v1/subscriptions/billing-portal`, {
|
|
24
|
+
method: "POST",
|
|
25
|
+
headers: {
|
|
26
|
+
Accept: "application/json",
|
|
27
|
+
"Content-Type": "application/json"
|
|
28
|
+
},
|
|
29
|
+
credentials: "include",
|
|
30
|
+
body: JSON.stringify({ returnUrl: returnUrl ?? `${window.location.origin}/account/subscriptions` })
|
|
31
|
+
});
|
|
32
|
+
if (!response.ok) throw new Error("Unable to open billing portal.");
|
|
33
|
+
const body = await response.json();
|
|
34
|
+
if (!body.url) throw new Error("Billing portal URL was not returned.");
|
|
35
|
+
window.location.assign(body.url);
|
|
36
|
+
} });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/pages/SubscriptionsPage.tsx
|
|
41
|
+
/** How an earlier plan ended, in words a customer reads. */
|
|
42
|
+
function subscriptionStatusLabel(status) {
|
|
43
|
+
switch (status.toLowerCase().replace(/_/g, "")) {
|
|
44
|
+
case "canceled": return "Ended";
|
|
45
|
+
case "pastdue": return "Payment due";
|
|
46
|
+
case "incomplete": return "Not started";
|
|
47
|
+
default: return status.charAt(0).toUpperCase() + status.slice(1).toLowerCase();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const SubscriptionsPage = () => {
|
|
51
|
+
const { billing } = useDevPortalBillingConfig();
|
|
52
|
+
const auth = useAuth();
|
|
53
|
+
const [searchParams] = useSearchParams();
|
|
54
|
+
const checkoutSuccess = searchParams.get("checkout") === "success";
|
|
55
|
+
const alreadySubscribedSku = searchParams.get(ALREADY_SUBSCRIBED_PARAM);
|
|
56
|
+
const checkoutPaid = searchParams.get("paid") !== "0";
|
|
57
|
+
const { data: me, error: meError, refetch } = useMySubscriptions();
|
|
58
|
+
const billingState = billingPageState({
|
|
59
|
+
data: me,
|
|
60
|
+
error: meError
|
|
61
|
+
});
|
|
62
|
+
const isLoading = billingState === "loading";
|
|
63
|
+
const { data: catalog } = usePlans();
|
|
64
|
+
const billingPortal = useBillingPortal();
|
|
65
|
+
const { data: usage } = useUsage("30d");
|
|
66
|
+
const { data: apiKeys } = useApiKeys();
|
|
67
|
+
const planNote = keyPlanNote(apiKeys?.plans ?? []);
|
|
68
|
+
const planBySku = new Map((catalog?.plans ?? []).map((plan) => [plan.sku, plan]));
|
|
69
|
+
const subscriptions = me?.subscriptions ?? [];
|
|
70
|
+
const activeSubscriptions = subscriptions.filter((sub) => isActiveSubscription(sub.status));
|
|
71
|
+
const earlierSubscriptions = subscriptions.filter((sub) => !isActiveSubscription(sub.status));
|
|
72
|
+
const hasActivePlan = activeSubscriptions.length > 0;
|
|
73
|
+
const singlePlan = activeSubscriptions.length === 1 ? planBySku.get(activeSubscriptions[0].planSku) : void 0;
|
|
74
|
+
const confirmingPayment = checkoutSuccess && !isLoading && !hasActivePlan;
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (!confirmingPayment) return;
|
|
77
|
+
let attempts = 0;
|
|
78
|
+
const timer = setInterval(() => {
|
|
79
|
+
attempts += 1;
|
|
80
|
+
refetch();
|
|
81
|
+
if (attempts >= 30) clearInterval(timer);
|
|
82
|
+
}, 2e3);
|
|
83
|
+
return () => clearInterval(timer);
|
|
84
|
+
}, [confirmingPayment, refetch]);
|
|
85
|
+
const monthlyQuota = singlePlan ? parseMonthlyQuotaCalls(singlePlan.limitsJson) : null;
|
|
86
|
+
const usedRequests = usage?.requestCount ?? 0;
|
|
87
|
+
const quotaPct = monthlyQuota && monthlyQuota > 0 ? Math.min(100, Math.round(usedRequests / monthlyQuota * 100)) : null;
|
|
88
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
89
|
+
className: "px-8 py-7",
|
|
90
|
+
children: [
|
|
91
|
+
/* @__PURE__ */ jsx(Head, { children: /* @__PURE__ */ jsx("title", { children: "Billing" }) }),
|
|
92
|
+
/* @__PURE__ */ jsx("h1", {
|
|
93
|
+
className: "mb-2 text-[1.6rem] font-bold tracking-[-0.01em]",
|
|
94
|
+
children: "Billing"
|
|
95
|
+
}),
|
|
96
|
+
/* @__PURE__ */ jsx("p", {
|
|
97
|
+
className: "mb-8 text-muted-foreground",
|
|
98
|
+
children: "Manage your plans, payment method, and invoices."
|
|
99
|
+
}),
|
|
100
|
+
alreadySubscribedSku ? /* @__PURE__ */ jsxs("div", {
|
|
101
|
+
role: "status",
|
|
102
|
+
className: "mb-6 rounded-xl border border-primary/30 bg-primary/5 p-4 text-sm",
|
|
103
|
+
children: [
|
|
104
|
+
"You're already on the",
|
|
105
|
+
" ",
|
|
106
|
+
planBySku.get(alreadySubscribedSku)?.displayName ?? alreadySubscribedSku,
|
|
107
|
+
" ",
|
|
108
|
+
"plan. Go to",
|
|
109
|
+
" ",
|
|
110
|
+
/* @__PURE__ */ jsx(Link$1, {
|
|
111
|
+
to: CREDENTIALS_PATH,
|
|
112
|
+
className: "font-medium underline",
|
|
113
|
+
children: "API keys"
|
|
114
|
+
}),
|
|
115
|
+
" ",
|
|
116
|
+
"to create a key."
|
|
117
|
+
]
|
|
118
|
+
}) : null,
|
|
119
|
+
confirmingPayment ? /* @__PURE__ */ jsx("div", {
|
|
120
|
+
className: "mb-6 rounded-xl border border-border bg-card p-4 text-sm",
|
|
121
|
+
children: checkoutConfirmingText(checkoutPaid)
|
|
122
|
+
}) : null,
|
|
123
|
+
checkoutSuccess && hasActivePlan ? /* @__PURE__ */ jsx("div", {
|
|
124
|
+
className: "mb-6 rounded-xl border border-green-500/30 bg-green-500/10 p-4 text-sm",
|
|
125
|
+
children: planNote ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
126
|
+
planNote.text,
|
|
127
|
+
" ",
|
|
128
|
+
/* @__PURE__ */ jsx(Link$1, {
|
|
129
|
+
to: CREDENTIALS_PATH,
|
|
130
|
+
className: "font-medium underline",
|
|
131
|
+
children: "Create the key"
|
|
132
|
+
}),
|
|
133
|
+
"."
|
|
134
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
135
|
+
"Your subscription is active.",
|
|
136
|
+
" ",
|
|
137
|
+
/* @__PURE__ */ jsx(Link$1, {
|
|
138
|
+
to: CREDENTIALS_PATH,
|
|
139
|
+
className: "font-medium underline",
|
|
140
|
+
children: "View your API keys"
|
|
141
|
+
}),
|
|
142
|
+
"."
|
|
143
|
+
] })
|
|
144
|
+
}) : null,
|
|
145
|
+
isLoading ? /* @__PURE__ */ jsx("div", {
|
|
146
|
+
className: "mb-8 h-40 animate-pulse rounded-xl border bg-muted/40",
|
|
147
|
+
role: "status",
|
|
148
|
+
"aria-busy": "true",
|
|
149
|
+
"aria-label": "Loading billing details"
|
|
150
|
+
}) : null,
|
|
151
|
+
billingState === "failed" ? /* @__PURE__ */ jsx("p", {
|
|
152
|
+
className: "mb-8 text-muted-foreground",
|
|
153
|
+
children: "Unable to load your plans. Try again in a moment."
|
|
154
|
+
}) : null,
|
|
155
|
+
!isLoading && hasActivePlan ? /* @__PURE__ */ jsxs("section", {
|
|
156
|
+
className: "mb-8",
|
|
157
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
158
|
+
className: "mb-3 text-xs font-medium uppercase tracking-[0.05em] text-muted-foreground",
|
|
159
|
+
children: activeSubscriptions.length > 1 ? "Your plans" : "Current plan"
|
|
160
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
161
|
+
className: "space-y-4",
|
|
162
|
+
children: activeSubscriptions.map((subscription) => {
|
|
163
|
+
const plan = planBySku.get(subscription.planSku);
|
|
164
|
+
return /* @__PURE__ */ jsxs("article", {
|
|
165
|
+
className: "rounded-xl border border-border bg-card p-6",
|
|
166
|
+
"data-testid": `active-plan-${subscription.planSku}`,
|
|
167
|
+
children: [
|
|
168
|
+
/* @__PURE__ */ jsx("h2", {
|
|
169
|
+
className: "text-2xl font-bold",
|
|
170
|
+
children: plan?.displayName ?? subscription.planSku
|
|
171
|
+
}),
|
|
172
|
+
plan ? /* @__PURE__ */ jsx("p", {
|
|
173
|
+
className: "mt-1 text-sm text-muted-foreground",
|
|
174
|
+
children: formatPlanPrice(plan)
|
|
175
|
+
}) : /* @__PURE__ */ jsx("p", {
|
|
176
|
+
className: "mt-1 text-sm text-muted-foreground",
|
|
177
|
+
children: "This plan is no longer offered. Your access continues until you cancel it."
|
|
178
|
+
}),
|
|
179
|
+
subscription.currentPeriodEnd ? /* @__PURE__ */ jsxs("p", {
|
|
180
|
+
className: "mt-3 text-sm text-muted-foreground",
|
|
181
|
+
children: [
|
|
182
|
+
"Renews",
|
|
183
|
+
" ",
|
|
184
|
+
new Date(subscription.currentPeriodEnd).toLocaleDateString()
|
|
185
|
+
]
|
|
186
|
+
}) : null,
|
|
187
|
+
singlePlan && quotaPct != null && monthlyQuota != null ? /* @__PURE__ */ jsxs("div", {
|
|
188
|
+
className: "mt-5",
|
|
189
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
190
|
+
className: "mb-1.5 flex justify-between text-xs text-muted-foreground",
|
|
191
|
+
children: [/* @__PURE__ */ jsx("span", { children: "Monthly requests" }), /* @__PURE__ */ jsxs("span", { children: [
|
|
192
|
+
usedRequests.toLocaleString(),
|
|
193
|
+
" /",
|
|
194
|
+
" ",
|
|
195
|
+
monthlyQuota.toLocaleString()
|
|
196
|
+
] })]
|
|
197
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
198
|
+
className: "h-2 rounded-full bg-muted",
|
|
199
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
200
|
+
className: "h-full rounded-full bg-primary transition-[width]",
|
|
201
|
+
style: { width: `${quotaPct}%` }
|
|
202
|
+
})
|
|
203
|
+
})]
|
|
204
|
+
}) : null
|
|
205
|
+
]
|
|
206
|
+
}, subscription.planSku);
|
|
207
|
+
})
|
|
208
|
+
})]
|
|
209
|
+
}) : null,
|
|
210
|
+
billingState === "empty" ? /* @__PURE__ */ jsx(ChoosePlanNotice, {
|
|
211
|
+
className: "mb-8 rounded-xl border border-dashed p-8 text-center",
|
|
212
|
+
body: "You do not have a plan yet. Choose one to start using the API; your billing details appear here afterwards."
|
|
213
|
+
}) : null,
|
|
214
|
+
!isLoading && earlierSubscriptions.length > 0 ? /* @__PURE__ */ jsxs("section", {
|
|
215
|
+
className: "mb-8",
|
|
216
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
217
|
+
className: "mb-2 text-xs font-medium uppercase tracking-[0.05em] text-muted-foreground",
|
|
218
|
+
children: "Earlier plans"
|
|
219
|
+
}), /* @__PURE__ */ jsx("ul", {
|
|
220
|
+
className: "divide-y rounded-xl border bg-card text-sm",
|
|
221
|
+
children: earlierSubscriptions.map((subscription) => /* @__PURE__ */ jsxs("li", {
|
|
222
|
+
className: "flex items-center justify-between gap-4 px-5 py-3",
|
|
223
|
+
children: [/* @__PURE__ */ jsx("span", { children: planBySku.get(subscription.planSku)?.displayName ?? subscription.planSku }), /* @__PURE__ */ jsx("span", {
|
|
224
|
+
className: "text-xs text-muted-foreground",
|
|
225
|
+
children: subscriptionStatusLabel(subscription.status)
|
|
226
|
+
})]
|
|
227
|
+
}, `${subscription.planSku}-${subscription.status}`))
|
|
228
|
+
})]
|
|
229
|
+
}) : null,
|
|
230
|
+
/* @__PURE__ */ jsxs("div", {
|
|
231
|
+
className: "flex flex-wrap gap-3",
|
|
232
|
+
children: [
|
|
233
|
+
/* @__PURE__ */ jsx(Button$1, {
|
|
234
|
+
variant: "outline",
|
|
235
|
+
asChild: true,
|
|
236
|
+
children: /* @__PURE__ */ jsx(Link$1, {
|
|
237
|
+
to: ACCOUNT_PLANS_PATH,
|
|
238
|
+
children: "Manage plans"
|
|
239
|
+
})
|
|
240
|
+
}),
|
|
241
|
+
auth.isAuthenticated && hasPaymentProvider(billing) ? /* @__PURE__ */ jsx(Button$1, {
|
|
242
|
+
variant: "default",
|
|
243
|
+
disabled: billingPortal.isPending,
|
|
244
|
+
onClick: () => {
|
|
245
|
+
billingPortal.mutate();
|
|
246
|
+
},
|
|
247
|
+
children: billingPortal.isPending ? /* @__PURE__ */ jsx(Spinner, {}) : "Manage payment & invoices"
|
|
248
|
+
}) : null,
|
|
249
|
+
auth.isAuthenticated ? /* @__PURE__ */ jsx(Button$1, {
|
|
250
|
+
variant: "secondary",
|
|
251
|
+
asChild: true,
|
|
252
|
+
children: /* @__PURE__ */ jsx(Link$1, {
|
|
253
|
+
to: CREDENTIALS_PATH,
|
|
254
|
+
children: "API keys"
|
|
255
|
+
})
|
|
256
|
+
}) : null
|
|
257
|
+
]
|
|
258
|
+
}),
|
|
259
|
+
billingPortal.isError ? /* @__PURE__ */ jsx("p", {
|
|
260
|
+
className: "mt-4 text-sm text-destructive",
|
|
261
|
+
children: billingPortal.error instanceof Error ? billingPortal.error.message : "Unable to open billing portal."
|
|
262
|
+
}) : null
|
|
263
|
+
]
|
|
264
|
+
});
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
//#endregion
|
|
268
|
+
export { SubscriptionsPage as default, subscriptionStatusLabel };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
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-DuVq2tXL.mjs";
|
|
4
|
+
import { t as usePlans } from "./usePlans-BF7Hy2qJ.mjs";
|
|
5
|
+
import { n as useUsageRecent, t as useUsage } from "./useUsage-CJ9VIBc7.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";
|
|
@@ -35,6 +35,27 @@ function quotaSummary(used, limit, periodDays, periodEnd) {
|
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/utils/usage-display.ts
|
|
40
|
+
/**
|
|
41
|
+
* How the usage page shows its numbers (API-126).
|
|
42
|
+
*/
|
|
43
|
+
/** "GET /p<project id>/hello" becomes "GET /hello": the shared gateway's project prefix means nothing to an API user. */
|
|
44
|
+
function endpointLabel(label) {
|
|
45
|
+
return label.replace(/(^|\s)\/p[0-9a-f]{32}(\/|$)/i, "$1/");
|
|
46
|
+
}
|
|
47
|
+
/** Calls the gateway refused because the plan's rate limit or quota was reached (counted as "429"). */
|
|
48
|
+
function blockedCount(byStatus) {
|
|
49
|
+
return byStatus.filter((item) => item.label === "429").reduce((sum, item) => sum + item.count, 0);
|
|
50
|
+
}
|
|
51
|
+
/** The middle latency of the given calls, or null without any. A slow outlier moves it less than an average. */
|
|
52
|
+
function medianLatency(latencies) {
|
|
53
|
+
const values = latencies.filter((value) => typeof value === "number").sort((a, b) => a - b);
|
|
54
|
+
if (values.length === 0) return null;
|
|
55
|
+
const middle = Math.floor(values.length / 2);
|
|
56
|
+
return values.length % 2 === 1 ? values[middle] : Math.round((values[middle - 1] + values[middle]) / 2);
|
|
57
|
+
}
|
|
58
|
+
|
|
38
59
|
//#endregion
|
|
39
60
|
//#region src/pages/UsagePage.tsx
|
|
40
61
|
const RANGES = [
|
|
@@ -65,6 +86,9 @@ const UsagePage = () => {
|
|
|
65
86
|
const auth = useAuth();
|
|
66
87
|
const [range, setRange] = useState("30d");
|
|
67
88
|
const { data, isLoading, isError } = useUsage(range);
|
|
89
|
+
const { data: recent } = useUsageRecent(50);
|
|
90
|
+
const median = medianLatency((recent?.items ?? []).map((item) => item.latencyMs));
|
|
91
|
+
const blocked = data ? blockedCount(data.byStatus) : 0;
|
|
68
92
|
const { data: quotaUsage } = useUsage("30d");
|
|
69
93
|
const { data: mine } = useMySubscriptions();
|
|
70
94
|
const { data: catalog } = usePlans();
|
|
@@ -129,23 +153,41 @@ const UsagePage = () => {
|
|
|
129
153
|
children: [
|
|
130
154
|
/* @__PURE__ */ jsxs("div", {
|
|
131
155
|
className: "rounded-xl border border-border bg-card p-4",
|
|
132
|
-
children: [
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
156
|
+
children: [
|
|
157
|
+
/* @__PURE__ */ jsx("div", {
|
|
158
|
+
className: "text-xs uppercase tracking-[0.05em] text-muted-foreground",
|
|
159
|
+
children: "Requests"
|
|
160
|
+
}),
|
|
161
|
+
/* @__PURE__ */ jsx("div", {
|
|
162
|
+
className: "mt-1.5 text-2xl font-bold",
|
|
163
|
+
children: data ? formatNumber(data.requestCount) : "—"
|
|
164
|
+
}),
|
|
165
|
+
blocked > 0 ? /* @__PURE__ */ jsxs("div", {
|
|
166
|
+
className: "mt-1 text-xs text-muted-foreground",
|
|
167
|
+
children: ["Blocked by rate limit: ", formatNumber(blocked)]
|
|
168
|
+
}) : null
|
|
169
|
+
]
|
|
139
170
|
}),
|
|
140
171
|
/* @__PURE__ */ jsxs("div", {
|
|
141
172
|
className: "rounded-xl border border-border bg-card p-4",
|
|
142
|
-
children: [
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
173
|
+
children: [
|
|
174
|
+
/* @__PURE__ */ jsx("div", {
|
|
175
|
+
className: "text-xs uppercase tracking-[0.05em] text-muted-foreground",
|
|
176
|
+
children: median != null ? "Median latency" : "Avg latency"
|
|
177
|
+
}),
|
|
178
|
+
/* @__PURE__ */ jsx("div", {
|
|
179
|
+
className: "mt-1.5 text-2xl font-bold",
|
|
180
|
+
children: median != null ? `${median} ms` : data?.avgLatencyMs != null ? `${data.avgLatencyMs} ms` : "—"
|
|
181
|
+
}),
|
|
182
|
+
median != null ? /* @__PURE__ */ jsxs("div", {
|
|
183
|
+
className: "mt-1 text-xs text-muted-foreground",
|
|
184
|
+
children: [
|
|
185
|
+
"Of the latest ",
|
|
186
|
+
recent?.items.length ?? 0,
|
|
187
|
+
" calls"
|
|
188
|
+
]
|
|
189
|
+
}) : null
|
|
190
|
+
]
|
|
149
191
|
}),
|
|
150
192
|
/* @__PURE__ */ jsxs("div", {
|
|
151
193
|
className: "rounded-xl border border-border bg-card p-4",
|
|
@@ -222,7 +264,7 @@ const UsagePage = () => {
|
|
|
222
264
|
className: "mb-1 flex justify-between text-[13px]",
|
|
223
265
|
children: [/* @__PURE__ */ jsx("code", {
|
|
224
266
|
className: "font-mono text-foreground",
|
|
225
|
-
children: item.path
|
|
267
|
+
children: endpointLabel(item.path)
|
|
226
268
|
}), /* @__PURE__ */ jsxs("span", {
|
|
227
269
|
className: "text-muted-foreground",
|
|
228
270
|
children: [pct, "%"]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as useRevokeApiKey, r as useCreateApiKey, t as API_KEY_CAP_REACHED } from "./useApiKeys-
|
|
1
|
+
import { i as useRevokeApiKey, r as useCreateApiKey, t as API_KEY_CAP_REACHED } from "./useApiKeys-CEwMzqju.mjs";
|
|
2
2
|
import { Spinner } from "@lukoweb/apitogo/components";
|
|
3
3
|
import { KeyRoundIcon, TrashIcon } from "@lukoweb/apitogo/icons";
|
|
4
4
|
import { useState } from "react";
|
package/dist/index.mjs
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { t as AccountPlansPage } from "./AccountPlansPage-
|
|
2
|
-
import { a as
|
|
1
|
+
import { t as AccountPlansPage } from "./AccountPlansPage-0PYGy1aV.mjs";
|
|
2
|
+
import { a as CREDENTIALS_PATH, c as SUBSCRIPTIONS_PATH, i as CONTACT_SALES_PATH, l as USAGE_PATH, o as LOGIN_NAV_PATH, r as CHECKOUT_PATH, s as PRICING_PATH, t as ACCOUNT_PLANS_PATH } from "./paths-DaVyTIDJ.mjs";
|
|
3
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-
|
|
4
|
+
import "./PricingCard-YJIEwydG.mjs";
|
|
5
5
|
import { n as useDevPortalBillingConfig, t as DevPortalBillingProvider } from "./DevPortalBillingContext-CR83s4LO.mjs";
|
|
6
|
-
import { t as DEMO_GATEWAY_KEY_HEADER } from "./demo-session-
|
|
7
|
-
import { t as useMySubscriptions } from "./useMySubscriptions-
|
|
8
|
-
import { n as startPlansPrefetch, t as usePlans } from "./usePlans-
|
|
9
|
-
import { a as playgroundKeyIdentities, i as useRevokeApiKey, n as useApiKeys, r as useCreateApiKey, t as API_KEY_CAP_REACHED } from "./useApiKeys-
|
|
10
|
-
import { n as useUsageRecent, t as useUsage } from "./useUsage
|
|
11
|
-
import { r as ApiKeysPanel, t as accountLayoutProps } from "./account-links-
|
|
12
|
-
import { t as isPreviewPricingEnabled } from "./pricing-config-
|
|
6
|
+
import { t as DEMO_GATEWAY_KEY_HEADER } from "./demo-session-yK9qEqV-.mjs";
|
|
7
|
+
import { t as useMySubscriptions } from "./useMySubscriptions-DuVq2tXL.mjs";
|
|
8
|
+
import { n as startPlansPrefetch, t as usePlans } from "./usePlans-BF7Hy2qJ.mjs";
|
|
9
|
+
import { a as playgroundKeyIdentities, i as useRevokeApiKey, n as useApiKeys, r as useCreateApiKey, t as API_KEY_CAP_REACHED } from "./useApiKeys-CEwMzqju.mjs";
|
|
10
|
+
import { n as useUsageRecent, t as useUsage } from "./useUsage-CJ9VIBc7.mjs";
|
|
11
|
+
import { r as ApiKeysPanel, t as accountLayoutProps } from "./account-links-9h91uCGj.mjs";
|
|
12
|
+
import { t as isPreviewPricingEnabled } from "./pricing-config-C44AIqql.mjs";
|
|
13
13
|
import { Head, Link } from "@lukoweb/apitogo/components";
|
|
14
14
|
import { ArrowRightIcon, KeyRoundIcon, StarsIcon } from "@lukoweb/apitogo/icons";
|
|
15
15
|
import { Outlet } from "@lukoweb/apitogo/router";
|
|
@@ -366,13 +366,13 @@ async function getSignedInIdentities(apiBaseUrl, fetchImpl = fetch) {
|
|
|
366
366
|
|
|
367
367
|
//#endregion
|
|
368
368
|
//#region src/DevPortalBillingPlugin.tsx
|
|
369
|
-
const AccountPlansPage$1 = lazy(() => import("./AccountPlansPage-
|
|
370
|
-
const CheckoutPage = lazy(() => import("./CheckoutPage-
|
|
371
|
-
const ContactSalesPage = lazy(() => import("./ContactSalesPage-
|
|
372
|
-
const CredentialsPage = lazy(() => import("./CredentialsPage-
|
|
373
|
-
const PricingPage = lazy(() => import("./PricingPage-
|
|
374
|
-
const SubscriptionsPage = lazy(() => import("./SubscriptionsPage-
|
|
375
|
-
const UsagePage = lazy(() => import("./UsagePage-
|
|
369
|
+
const AccountPlansPage$1 = lazy(() => import("./AccountPlansPage-0PYGy1aV.mjs").then((n) => n.n));
|
|
370
|
+
const CheckoutPage = lazy(() => import("./CheckoutPage-Bpa128i2.mjs"));
|
|
371
|
+
const ContactSalesPage = lazy(() => import("./ContactSalesPage-BslD3Yk4.mjs"));
|
|
372
|
+
const CredentialsPage = lazy(() => import("./CredentialsPage-C4qpBVrh.mjs"));
|
|
373
|
+
const PricingPage = lazy(() => import("./PricingPage-DIhoFOd9.mjs"));
|
|
374
|
+
const SubscriptionsPage = lazy(() => import("./SubscriptionsPage-D7hsYtTB.mjs"));
|
|
375
|
+
const UsagePage = lazy(() => import("./UsagePage-Di9kQ_3Z.mjs"));
|
|
376
376
|
function resolvePricingEnabled(config) {
|
|
377
377
|
if (config.__meta?.pricingEnabled !== void 0) return config.__meta.pricingEnabled;
|
|
378
378
|
return isPreviewPricingEnabled();
|
|
@@ -399,13 +399,13 @@ const devPortalBillingPlugin = createPlugin((options = {}) => {
|
|
|
399
399
|
},
|
|
400
400
|
getIdentities: async () => [...playgroundKeyIdentities(), ...await getSignedInIdentities(resolveDevPortalApiBaseUrl(options.apiBaseUrl))],
|
|
401
401
|
getProfileMenuItems: () => [{
|
|
402
|
-
label: "
|
|
402
|
+
label: "Billing",
|
|
403
403
|
path: SUBSCRIPTIONS_PATH,
|
|
404
404
|
icon: StarsIcon,
|
|
405
405
|
category: "middle",
|
|
406
406
|
weight: 25
|
|
407
407
|
}, {
|
|
408
|
-
label: "API
|
|
408
|
+
label: "API keys",
|
|
409
409
|
path: CREDENTIALS_PATH,
|
|
410
410
|
icon: KeyRoundIcon,
|
|
411
411
|
category: "middle",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as ACCOUNT_PLANS_PATH } from "./paths-
|
|
1
|
+
import { t as ACCOUNT_PLANS_PATH } from "./paths-DaVyTIDJ.mjs";
|
|
2
2
|
import { Link } from "@lukoweb/apitogo/router";
|
|
3
3
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
4
|
import { Button } from "@lukoweb/apitogo/ui/Button.js";
|
|
@@ -50,4 +50,24 @@ function keyPlanNote(plans) {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
//#endregion
|
|
53
|
-
|
|
53
|
+
//#region src/utils/page-state.ts
|
|
54
|
+
/** True for the error the keys endpoint gives a user without any plan (HTTP 404). */
|
|
55
|
+
const isNoEntitlement = (error) => error instanceof Error && error.message === "NO_ENTITLEMENT";
|
|
56
|
+
/**
|
|
57
|
+
* The API keys page. The key list is what the page shows, so it decides: the credentials request only adds the gateway
|
|
58
|
+
* address, which the key list also carries, and its quick 404 right after sign-up must not show "no plan" while the
|
|
59
|
+
* keys are still loading.
|
|
60
|
+
*/
|
|
61
|
+
function apiKeysPageState(keys) {
|
|
62
|
+
if (keys.data) return keys.data.plans.length > 0 ? "ready" : "empty";
|
|
63
|
+
if (keys.error) return isNoEntitlement(keys.error) ? "empty" : "failed";
|
|
64
|
+
return "loading";
|
|
65
|
+
}
|
|
66
|
+
/** The Billing page: subscriptions decide; until they arrive the page is loading. */
|
|
67
|
+
function billingPageState(me) {
|
|
68
|
+
if (me.data) return (me.data.subscriptions ?? []).length > 0 ? "ready" : "empty";
|
|
69
|
+
return me.error ? "failed" : "loading";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
//#endregion
|
|
73
|
+
export { ChoosePlanNotice as i, billingPageState as n, keyPlanNote as r, apiKeysPageState as t };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/paths.ts
|
|
2
|
+
const PRICING_PATH = "/pricing";
|
|
3
|
+
const ACCOUNT_PLANS_PATH = "/account/plans";
|
|
4
|
+
const CHECKOUT_PATH = "/checkout";
|
|
5
|
+
const CONTACT_SALES_PATH = "/contact-sales";
|
|
6
|
+
const SUBSCRIPTIONS_PATH = "/account/subscriptions";
|
|
7
|
+
const CREDENTIALS_PATH = "/account/credentials";
|
|
8
|
+
const USAGE_PATH = "/account/usage";
|
|
9
|
+
const LOGIN_NAV_PATH = "/signin";
|
|
10
|
+
/** Billing opens with this query parameter, naming the plan the user tried to buy again (API-123). */
|
|
11
|
+
const ALREADY_SUBSCRIBED_PARAM = "alreadySubscribed";
|
|
12
|
+
|
|
13
|
+
//#endregion
|
|
14
|
+
export { CREDENTIALS_PATH as a, SUBSCRIPTIONS_PATH as c, CONTACT_SALES_PATH as i, USAGE_PATH as l, ALREADY_SUBSCRIBED_PARAM as n, LOGIN_NAV_PATH as o, CHECKOUT_PATH as r, PRICING_PATH as s, ACCOUNT_PLANS_PATH as t };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/utils/problem-details.ts
|
|
2
|
+
/**
|
|
3
|
+
* The sentence an RFC 7807 Problem Details response carries for a person: the first field error, else `detail`, else
|
|
4
|
+
* `title`. Pages show this, never the raw JSON (API-123). Null when the body is not Problem Details.
|
|
5
|
+
*/
|
|
6
|
+
async function readProblemDetail(response) {
|
|
7
|
+
try {
|
|
8
|
+
const body = await response.json();
|
|
9
|
+
return (body.errors ? Object.values(body.errors).flat()[0] : void 0)?.trim() || body.detail?.trim() || body.title?.trim() || null;
|
|
10
|
+
} catch {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/** The plans service's answer when a user starts checkout for a plan they already hold. */
|
|
15
|
+
const isAlreadySubscribedMessage = (message) => /already have an active subscription/i.test(message);
|
|
16
|
+
|
|
17
|
+
//#endregion
|
|
18
|
+
export { readProblemDetail as n, isAlreadySubscribedMessage as t };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
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 { n as
|
|
3
|
+
import { n as readProblemDetail } from "./problem-details-n20-dy3w.mjs";
|
|
4
|
+
import { n as createDemoApiKeys, t as DEMO_GATEWAY_KEY_HEADER } from "./demo-session-yK9qEqV-.mjs";
|
|
4
5
|
import { useMutation, useQuery, useQueryClient } from "@lukoweb/apitogo/react-query";
|
|
5
6
|
import { useAuth } from "@lukoweb/apitogo/hooks";
|
|
6
7
|
|
|
@@ -154,13 +155,6 @@ function useRevokeApiKey() {
|
|
|
154
155
|
}
|
|
155
156
|
});
|
|
156
157
|
}
|
|
157
|
-
async function readProblemDetail(response) {
|
|
158
|
-
try {
|
|
159
|
-
return (await response.json()).detail?.trim() || null;
|
|
160
|
-
} catch {
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
158
|
|
|
165
159
|
//#endregion
|
|
166
160
|
export { playgroundKeyIdentities as a, useRevokeApiKey as i, useApiKeys as n, useCreateApiKey as r, API_KEY_CAP_REACHED as t };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
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-
|
|
3
|
+
import { i as createDemoMeResponse } from "./demo-session-yK9qEqV-.mjs";
|
|
4
4
|
import { useQuery } from "@lukoweb/apitogo/react-query";
|
|
5
5
|
import { useAuth } from "@lukoweb/apitogo/hooks";
|
|
6
6
|
|
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.54",
|
|
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.54",
|
|
50
|
+
"@lukoweb/apitogo-module-user-panel": "0.2.54"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"react": ">=19.2.0",
|
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
import { i as CREDENTIALS_PATH, t as ACCOUNT_PLANS_PATH } from "./paths-ujy9wjVK.mjs";
|
|
2
|
-
import { a as formatPlanPrice, p as parseMonthlyQuotaCalls, s as isActiveSubscription, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-BstWzITX.mjs";
|
|
3
|
-
import { n as useDevPortalBillingConfig } from "./DevPortalBillingContext-CR83s4LO.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";
|
|
10
|
-
import { Head, Spinner } from "@lukoweb/apitogo/components";
|
|
11
|
-
import { Link as Link$1, useSearchParams } from "@lukoweb/apitogo/router";
|
|
12
|
-
import { useEffect } from "react";
|
|
13
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
14
|
-
import { useMutation } from "@lukoweb/apitogo/react-query";
|
|
15
|
-
import { useAuth } from "@lukoweb/apitogo/hooks";
|
|
16
|
-
import { Button as Button$1 } from "@lukoweb/apitogo/ui/Button.js";
|
|
17
|
-
|
|
18
|
-
//#region src/hooks/useBillingPortal.ts
|
|
19
|
-
function useBillingPortal() {
|
|
20
|
-
const apiBaseUrl = useDevPortalApiBaseUrl();
|
|
21
|
-
return useMutation({ mutationFn: async (returnUrl) => {
|
|
22
|
-
if (!apiBaseUrl) throw new Error("Dev portal API is not configured.");
|
|
23
|
-
const response = await fetch(`${apiBaseUrl}/api/v1/subscriptions/billing-portal`, {
|
|
24
|
-
method: "POST",
|
|
25
|
-
headers: {
|
|
26
|
-
Accept: "application/json",
|
|
27
|
-
"Content-Type": "application/json"
|
|
28
|
-
},
|
|
29
|
-
credentials: "include",
|
|
30
|
-
body: JSON.stringify({ returnUrl: returnUrl ?? `${window.location.origin}/account/subscriptions` })
|
|
31
|
-
});
|
|
32
|
-
if (!response.ok) throw new Error("Unable to open billing portal.");
|
|
33
|
-
const body = await response.json();
|
|
34
|
-
if (!body.url) throw new Error("Billing portal URL was not returned.");
|
|
35
|
-
window.location.assign(body.url);
|
|
36
|
-
} });
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
//#endregion
|
|
40
|
-
//#region src/pages/SubscriptionsPage.tsx
|
|
41
|
-
const SubscriptionsPage = () => {
|
|
42
|
-
const { billing } = useDevPortalBillingConfig();
|
|
43
|
-
const auth = useAuth();
|
|
44
|
-
const [searchParams] = useSearchParams();
|
|
45
|
-
const checkoutSuccess = searchParams.get("checkout") === "success";
|
|
46
|
-
const checkoutPaid = searchParams.get("paid") !== "0";
|
|
47
|
-
const { data: me, isLoading, refetch } = useMySubscriptions();
|
|
48
|
-
const { data: catalog } = usePlans();
|
|
49
|
-
const billingPortal = useBillingPortal();
|
|
50
|
-
const { data: usage } = useUsage("30d");
|
|
51
|
-
const { data: apiKeys } = useApiKeys();
|
|
52
|
-
const planNote = keyPlanNote(apiKeys?.plans ?? []);
|
|
53
|
-
const planBySku = new Map((catalog?.plans ?? []).map((plan) => [plan.sku, plan]));
|
|
54
|
-
const subscriptions = me?.subscriptions ?? [];
|
|
55
|
-
const activeSubscription = subscriptions.find((sub) => isActiveSubscription(sub.status));
|
|
56
|
-
const activePlan = activeSubscription ? planBySku.get(activeSubscription.planSku) : void 0;
|
|
57
|
-
const confirmingPayment = checkoutSuccess && !isLoading && !activeSubscription;
|
|
58
|
-
useEffect(() => {
|
|
59
|
-
if (!confirmingPayment) return;
|
|
60
|
-
let attempts = 0;
|
|
61
|
-
const timer = setInterval(() => {
|
|
62
|
-
attempts += 1;
|
|
63
|
-
refetch();
|
|
64
|
-
if (attempts >= 30) clearInterval(timer);
|
|
65
|
-
}, 2e3);
|
|
66
|
-
return () => clearInterval(timer);
|
|
67
|
-
}, [confirmingPayment, refetch]);
|
|
68
|
-
const monthlyQuota = activePlan ? parseMonthlyQuotaCalls(activePlan.limitsJson) : null;
|
|
69
|
-
const usedRequests = usage?.requestCount ?? 0;
|
|
70
|
-
const quotaPct = monthlyQuota && monthlyQuota > 0 ? Math.min(100, Math.round(usedRequests / monthlyQuota * 100)) : null;
|
|
71
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
72
|
-
className: "px-8 py-7",
|
|
73
|
-
children: [
|
|
74
|
-
/* @__PURE__ */ jsx(Head, { children: /* @__PURE__ */ jsx("title", { children: "Billing" }) }),
|
|
75
|
-
/* @__PURE__ */ jsx("h1", {
|
|
76
|
-
className: "mb-2 text-[1.6rem] font-bold tracking-[-0.01em]",
|
|
77
|
-
children: "Billing"
|
|
78
|
-
}),
|
|
79
|
-
/* @__PURE__ */ jsx("p", {
|
|
80
|
-
className: "mb-8 text-muted-foreground",
|
|
81
|
-
children: "Manage your plan, payment method, and invoices."
|
|
82
|
-
}),
|
|
83
|
-
confirmingPayment ? /* @__PURE__ */ jsx("div", {
|
|
84
|
-
className: "mb-6 rounded-xl border border-border bg-card p-4 text-sm",
|
|
85
|
-
children: checkoutConfirmingText(checkoutPaid)
|
|
86
|
-
}) : null,
|
|
87
|
-
checkoutSuccess && activeSubscription ? /* @__PURE__ */ jsx("div", {
|
|
88
|
-
className: "mb-6 rounded-xl border border-green-500/30 bg-green-500/10 p-4 text-sm",
|
|
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: [
|
|
99
|
-
"Your subscription is active.",
|
|
100
|
-
" ",
|
|
101
|
-
/* @__PURE__ */ jsx(Link$1, {
|
|
102
|
-
to: CREDENTIALS_PATH,
|
|
103
|
-
className: "font-medium underline",
|
|
104
|
-
children: "View your API credentials"
|
|
105
|
-
}),
|
|
106
|
-
"."
|
|
107
|
-
] })
|
|
108
|
-
}) : null,
|
|
109
|
-
isLoading ? /* @__PURE__ */ jsx("p", {
|
|
110
|
-
className: "text-muted-foreground",
|
|
111
|
-
children: "Loading billing details…"
|
|
112
|
-
}) : null,
|
|
113
|
-
!isLoading && activeSubscription ? /* @__PURE__ */ jsxs("div", {
|
|
114
|
-
className: "mb-8 rounded-xl border border-border bg-card p-6",
|
|
115
|
-
children: [
|
|
116
|
-
/* @__PURE__ */ jsx("p", {
|
|
117
|
-
className: "text-xs font-medium uppercase tracking-[0.05em] text-muted-foreground",
|
|
118
|
-
children: "Current plan"
|
|
119
|
-
}),
|
|
120
|
-
/* @__PURE__ */ jsx("h2", {
|
|
121
|
-
className: "mt-2 text-2xl font-bold",
|
|
122
|
-
children: activePlan?.displayName ?? activeSubscription.planSku
|
|
123
|
-
}),
|
|
124
|
-
activePlan ? /* @__PURE__ */ jsx("p", {
|
|
125
|
-
className: "mt-1 text-sm text-muted-foreground",
|
|
126
|
-
children: formatPlanPrice(activePlan)
|
|
127
|
-
}) : /* @__PURE__ */ jsx("p", {
|
|
128
|
-
className: "mt-1 text-sm text-muted-foreground",
|
|
129
|
-
children: "This plan is no longer offered. Your access continues until you cancel or change plan."
|
|
130
|
-
}),
|
|
131
|
-
activeSubscription.currentPeriodEnd ? /* @__PURE__ */ jsxs("p", {
|
|
132
|
-
className: "mt-3 text-sm text-muted-foreground",
|
|
133
|
-
children: [
|
|
134
|
-
"Renews",
|
|
135
|
-
" ",
|
|
136
|
-
new Date(activeSubscription.currentPeriodEnd).toLocaleDateString()
|
|
137
|
-
]
|
|
138
|
-
}) : null,
|
|
139
|
-
quotaPct != null && monthlyQuota != null ? /* @__PURE__ */ jsxs("div", {
|
|
140
|
-
className: "mt-5",
|
|
141
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
142
|
-
className: "mb-1.5 flex justify-between text-xs text-muted-foreground",
|
|
143
|
-
children: [/* @__PURE__ */ jsx("span", { children: "Monthly requests" }), /* @__PURE__ */ jsxs("span", { children: [
|
|
144
|
-
usedRequests.toLocaleString(),
|
|
145
|
-
" /",
|
|
146
|
-
" ",
|
|
147
|
-
monthlyQuota.toLocaleString()
|
|
148
|
-
] })]
|
|
149
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
150
|
-
className: "h-2 rounded-full bg-muted",
|
|
151
|
-
children: /* @__PURE__ */ jsx("div", {
|
|
152
|
-
className: "h-full rounded-full bg-primary transition-[width]",
|
|
153
|
-
style: { width: `${quotaPct}%` }
|
|
154
|
-
})
|
|
155
|
-
})]
|
|
156
|
-
}) : null
|
|
157
|
-
]
|
|
158
|
-
}) : null,
|
|
159
|
-
!isLoading && subscriptions.length === 0 ? /* @__PURE__ */ jsx(ChoosePlanNotice, {
|
|
160
|
-
className: "mb-8 rounded-xl border border-dashed p-8 text-center",
|
|
161
|
-
body: "You do not have a plan yet. Choose one to start using the API; your billing details appear here afterwards."
|
|
162
|
-
}) : null,
|
|
163
|
-
!isLoading && subscriptions.length > 1 ? /* @__PURE__ */ jsx("div", {
|
|
164
|
-
className: "mb-8 space-y-4",
|
|
165
|
-
children: subscriptions.map((subscription) => {
|
|
166
|
-
const plan = planBySku.get(subscription.planSku);
|
|
167
|
-
return /* @__PURE__ */ jsx("article", {
|
|
168
|
-
className: "rounded-xl border bg-card p-5",
|
|
169
|
-
children: /* @__PURE__ */ jsxs("div", {
|
|
170
|
-
className: "flex flex-wrap items-start justify-between gap-4",
|
|
171
|
-
children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h3", {
|
|
172
|
-
className: "font-semibold",
|
|
173
|
-
children: plan?.displayName ?? subscription.planSku
|
|
174
|
-
}), plan ? /* @__PURE__ */ jsx("p", {
|
|
175
|
-
className: "mt-1 text-sm text-muted-foreground",
|
|
176
|
-
children: formatPlanPrice(plan)
|
|
177
|
-
}) : null] }), /* @__PURE__ */ jsx("span", {
|
|
178
|
-
className: "rounded-full bg-muted px-3 py-1 text-xs font-medium text-muted-foreground",
|
|
179
|
-
children: subscription.status
|
|
180
|
-
})]
|
|
181
|
-
})
|
|
182
|
-
}, `${subscription.planSku}-${subscription.status}`);
|
|
183
|
-
})
|
|
184
|
-
}) : null,
|
|
185
|
-
/* @__PURE__ */ jsxs("div", {
|
|
186
|
-
className: "flex flex-wrap gap-3",
|
|
187
|
-
children: [
|
|
188
|
-
/* @__PURE__ */ jsx(Button$1, {
|
|
189
|
-
variant: "outline",
|
|
190
|
-
asChild: true,
|
|
191
|
-
children: /* @__PURE__ */ jsx(Link$1, {
|
|
192
|
-
to: ACCOUNT_PLANS_PATH,
|
|
193
|
-
children: "Change plan"
|
|
194
|
-
})
|
|
195
|
-
}),
|
|
196
|
-
auth.isAuthenticated && hasPaymentProvider(billing) ? /* @__PURE__ */ jsx(Button$1, {
|
|
197
|
-
variant: "default",
|
|
198
|
-
disabled: billingPortal.isPending,
|
|
199
|
-
onClick: () => {
|
|
200
|
-
billingPortal.mutate();
|
|
201
|
-
},
|
|
202
|
-
children: billingPortal.isPending ? /* @__PURE__ */ jsx(Spinner, {}) : "Manage payment & invoices"
|
|
203
|
-
}) : null,
|
|
204
|
-
auth.isAuthenticated ? /* @__PURE__ */ jsx(Button$1, {
|
|
205
|
-
variant: "secondary",
|
|
206
|
-
asChild: true,
|
|
207
|
-
children: /* @__PURE__ */ jsx(Link$1, {
|
|
208
|
-
to: CREDENTIALS_PATH,
|
|
209
|
-
children: "API credentials"
|
|
210
|
-
})
|
|
211
|
-
}) : null
|
|
212
|
-
]
|
|
213
|
-
}),
|
|
214
|
-
billingPortal.isError ? /* @__PURE__ */ jsx("p", {
|
|
215
|
-
className: "mt-4 text-sm text-destructive",
|
|
216
|
-
children: billingPortal.error instanceof Error ? billingPortal.error.message : "Unable to open billing portal."
|
|
217
|
-
}) : null
|
|
218
|
-
]
|
|
219
|
-
});
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
//#endregion
|
|
223
|
-
export { SubscriptionsPage as default };
|
package/dist/paths-ujy9wjVK.mjs
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
//#region src/paths.ts
|
|
2
|
-
const PRICING_PATH = "/pricing";
|
|
3
|
-
const ACCOUNT_PLANS_PATH = "/account/plans";
|
|
4
|
-
const CHECKOUT_PATH = "/checkout";
|
|
5
|
-
const CONTACT_SALES_PATH = "/contact-sales";
|
|
6
|
-
const SUBSCRIPTIONS_PATH = "/account/subscriptions";
|
|
7
|
-
const CREDENTIALS_PATH = "/account/credentials";
|
|
8
|
-
const USAGE_PATH = "/account/usage";
|
|
9
|
-
const LOGIN_NAV_PATH = "/signin";
|
|
10
|
-
|
|
11
|
-
//#endregion
|
|
12
|
-
export { LOGIN_NAV_PATH as a, USAGE_PATH as c, CREDENTIALS_PATH as i, CHECKOUT_PATH as n, PRICING_PATH as o, CONTACT_SALES_PATH as r, SUBSCRIPTIONS_PATH as s, ACCOUNT_PLANS_PATH as t };
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|