@lukoweb/apitogo-plugin-dev-portal-billing 0.1.72 → 0.1.74
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/index.d.mts +26 -14
- package/dist/index.mjs +168 -164
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -5,17 +5,6 @@ import * as _tanstack_react_query0 from "@tanstack/react-query";
|
|
|
5
5
|
//#region src/pages/AccountPlansPage.d.ts
|
|
6
6
|
declare const AccountPlansPage: () => react_jsx_runtime0.JSX.Element;
|
|
7
7
|
//#endregion
|
|
8
|
-
//#region src/components/ApiKeysPanel.d.ts
|
|
9
|
-
/**
|
|
10
|
-
* Lists the developer's gateway API keys with create and revoke. The plaintext key is returned by the
|
|
11
|
-
* platform exactly once, so it is held in local state until dismissed and never re-fetched.
|
|
12
|
-
*/
|
|
13
|
-
declare const ApiKeysPanel: ({
|
|
14
|
-
readOnly
|
|
15
|
-
}: {
|
|
16
|
-
readOnly?: boolean;
|
|
17
|
-
}) => react_jsx_runtime0.JSX.Element;
|
|
18
|
-
//#endregion
|
|
19
8
|
//#region src/types.d.ts
|
|
20
9
|
type DevPortalApiKey = {
|
|
21
10
|
id: string;
|
|
@@ -26,11 +15,16 @@ type DevPortalApiKey = {
|
|
|
26
15
|
revokedAtUtc: string | null; /** Present only in the response to a successful create; never returned again. */
|
|
27
16
|
key: string | null;
|
|
28
17
|
};
|
|
29
|
-
type
|
|
18
|
+
type DevPortalApiKeyPlan = {
|
|
19
|
+
planSku: string;
|
|
20
|
+
displayName: string;
|
|
30
21
|
keys: DevPortalApiKey[];
|
|
31
22
|
activeCount: number; /** Per-developer cap from the project's platform plan; null means unlimited. */
|
|
32
23
|
maxKeys: number | null;
|
|
33
|
-
canCreate: boolean;
|
|
24
|
+
canCreate: boolean;
|
|
25
|
+
};
|
|
26
|
+
type DevPortalApiKeyListResponse = {
|
|
27
|
+
plans: DevPortalApiKeyPlan[]; /** Header the key must be sent in when calling the gateway. */
|
|
34
28
|
header: string;
|
|
35
29
|
gatewayBaseUrl: string | null;
|
|
36
30
|
};
|
|
@@ -50,6 +44,21 @@ type DevPortalBillingConfig = {
|
|
|
50
44
|
}>;
|
|
51
45
|
};
|
|
52
46
|
//#endregion
|
|
47
|
+
//#region src/components/ApiKeysPanel.d.ts
|
|
48
|
+
/**
|
|
49
|
+
* Lists gateway API keys for one plan with create and revoke. The plaintext key is returned by the
|
|
50
|
+
* platform exactly once, so it is held in local state until dismissed and never re-fetched.
|
|
51
|
+
*/
|
|
52
|
+
declare const ApiKeysPanel: ({
|
|
53
|
+
plan,
|
|
54
|
+
header,
|
|
55
|
+
readOnly
|
|
56
|
+
}: {
|
|
57
|
+
plan: DevPortalApiKeyPlan;
|
|
58
|
+
header: string;
|
|
59
|
+
readOnly?: boolean;
|
|
60
|
+
}) => react_jsx_runtime0.JSX.Element;
|
|
61
|
+
//#endregion
|
|
53
62
|
//#region src/paths.d.ts
|
|
54
63
|
declare const PRICING_PATH = "/pricing";
|
|
55
64
|
declare const CHECKOUT_PATH = "/checkout";
|
|
@@ -171,7 +180,10 @@ declare function useApiKeys(): _tanstack_react_query0.QueryObserverRefetchErrorR
|
|
|
171
180
|
fetchStatus: _tanstack_react_query0.FetchStatus;
|
|
172
181
|
promise: Promise<DevPortalApiKeyListResponse>;
|
|
173
182
|
};
|
|
174
|
-
declare function useCreateApiKey(): _tanstack_react_query0.UseMutationResult<DevPortalApiKey, Error,
|
|
183
|
+
declare function useCreateApiKey(): _tanstack_react_query0.UseMutationResult<DevPortalApiKey, Error, {
|
|
184
|
+
name: string;
|
|
185
|
+
planSku: string;
|
|
186
|
+
}, unknown>;
|
|
175
187
|
declare function useRevokeApiKey(): _tanstack_react_query0.UseMutationResult<void, Error, string, unknown>;
|
|
176
188
|
//#endregion
|
|
177
189
|
export { API_KEY_CAP_REACHED, AccountPlansPage, ApiKeysPanel, CHECKOUT_PATH, CREDENTIALS_PATH, PRICING_PATH, SUBSCRIPTIONS_PATH, devPortalBillingPlugin, useApiKeys, useCreateApiKey, useRevokeApiKey };
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Button, Head, Heading } from "@lukoweb/apitogo/components";
|
|
1
|
+
import { Button, Head, Heading, Spinner } from "@lukoweb/apitogo/components";
|
|
2
2
|
import { CreditCardIcon, KeyRoundIcon, ShieldIcon, StarsIcon, TrashIcon } from "@lukoweb/apitogo/icons";
|
|
3
3
|
import { createContext, useContext, useEffect, useState } from "react";
|
|
4
4
|
import { Link, Navigate, Outlet, useNavigate, useSearchParams } from "@lukoweb/apitogo/router";
|
|
@@ -11,7 +11,7 @@ import { useMutation, useQuery, useQueryClient } from "@lukoweb/apitogo/react-qu
|
|
|
11
11
|
import { useAuth } from "@lukoweb/apitogo/hooks";
|
|
12
12
|
import { Alert, AlertTitle } from "@lukoweb/apitogo/ui/Alert.js";
|
|
13
13
|
import { Button as Button$1 } from "@lukoweb/apitogo/ui/Button.js";
|
|
14
|
-
import { Dialog,
|
|
14
|
+
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@lukoweb/apitogo/ui/Dialog.js";
|
|
15
15
|
import { Input } from "@lukoweb/apitogo/ui/Input.js";
|
|
16
16
|
import { Secret } from "@lukoweb/apitogo/ui/Secret.js";
|
|
17
17
|
import { createPlugin } from "@lukoweb/apitogo/plugins";
|
|
@@ -177,7 +177,7 @@ const PricingCard = ({ plan, mode = "public", subscriptionStatus, isSubscribed =
|
|
|
177
177
|
variant: "secondary",
|
|
178
178
|
className: "w-full",
|
|
179
179
|
disabled: true,
|
|
180
|
-
children: "
|
|
180
|
+
children: "Subscribed"
|
|
181
181
|
}),
|
|
182
182
|
onUnsubscribe ? /* @__PURE__ */ jsxs(AlertDialog, { children: [/* @__PURE__ */ jsx(AlertDialogTrigger, {
|
|
183
183
|
asChild: true,
|
|
@@ -292,48 +292,57 @@ function useCancelSubscription() {
|
|
|
292
292
|
const DEMO_SUBSCRIPTION_KEY = "demo-subscription-key";
|
|
293
293
|
const DEMO_GATEWAY_KEY_HEADER = "Ocp-Apim-Subscription-Key";
|
|
294
294
|
const DEMO_MAX_KEYS = 5;
|
|
295
|
-
function
|
|
296
|
-
|
|
295
|
+
function resolveDemoPlans(plans) {
|
|
296
|
+
if (plans && plans.length > 0) return plans.map((plan) => ({
|
|
297
|
+
sku: plan.sku,
|
|
298
|
+
displayName: plan.displayName
|
|
299
|
+
}));
|
|
300
|
+
return [{
|
|
301
|
+
sku: "free",
|
|
302
|
+
displayName: "Free"
|
|
303
|
+
}];
|
|
297
304
|
}
|
|
298
305
|
function createDemoMeResponse(plans) {
|
|
299
|
-
const planSku = resolveDemoPlanSku(plans);
|
|
300
306
|
const periodEnd = /* @__PURE__ */ new Date();
|
|
301
307
|
periodEnd.setMonth(periodEnd.getMonth() + 1);
|
|
302
308
|
return {
|
|
303
309
|
userId: "00000000-0000-0000-0000-000000000001",
|
|
304
310
|
displayName: "Demo User",
|
|
305
|
-
subscriptions:
|
|
306
|
-
planSku,
|
|
311
|
+
subscriptions: resolveDemoPlans(plans).map((plan) => ({
|
|
312
|
+
planSku: plan.sku,
|
|
307
313
|
status: "Active",
|
|
308
314
|
entitlementState: "active",
|
|
309
315
|
currentPeriodEnd: periodEnd.toISOString()
|
|
310
|
-
}
|
|
316
|
+
}))
|
|
311
317
|
};
|
|
312
318
|
}
|
|
313
319
|
function createDemoCredentials(plans) {
|
|
314
320
|
return {
|
|
315
321
|
idToken: "demo-id-token",
|
|
316
322
|
accessToken: null,
|
|
317
|
-
subscriptionKey: DEMO_SUBSCRIPTION_KEY,
|
|
318
323
|
gatewayBaseUrl: null,
|
|
319
|
-
|
|
324
|
+
planSkus: resolveDemoPlans(plans).map((plan) => plan.sku),
|
|
320
325
|
expiresAt: null
|
|
321
326
|
};
|
|
322
327
|
}
|
|
323
|
-
function createDemoApiKeys() {
|
|
328
|
+
function createDemoApiKeys(plans) {
|
|
324
329
|
return {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
330
|
+
plans: resolveDemoPlans(plans).map((plan, index) => ({
|
|
331
|
+
planSku: plan.sku,
|
|
332
|
+
displayName: plan.displayName,
|
|
333
|
+
keys: index === 0 ? [{
|
|
334
|
+
id: "00000000-0000-0000-0000-0000000000a1",
|
|
335
|
+
name: "default",
|
|
336
|
+
keyPrefix: DEMO_SUBSCRIPTION_KEY.slice(0, 8),
|
|
337
|
+
status: "active",
|
|
338
|
+
createdAtUtc: (/* @__PURE__ */ new Date()).toISOString(),
|
|
339
|
+
revokedAtUtc: null,
|
|
340
|
+
key: null
|
|
341
|
+
}] : [],
|
|
342
|
+
activeCount: index === 0 ? 1 : 0,
|
|
343
|
+
maxKeys: DEMO_MAX_KEYS,
|
|
344
|
+
canCreate: true
|
|
345
|
+
})),
|
|
337
346
|
header: DEMO_GATEWAY_KEY_HEADER,
|
|
338
347
|
gatewayBaseUrl: null
|
|
339
348
|
};
|
|
@@ -503,6 +512,7 @@ function useApiKeys() {
|
|
|
503
512
|
const apiBaseUrl = useDevPortalApiBaseUrl();
|
|
504
513
|
const isPreview = useDevPortalPreviewMode();
|
|
505
514
|
const isDemoSession = useIsDemoSession();
|
|
515
|
+
const { previewPlans } = useDevPortalBillingConfig();
|
|
506
516
|
const liveQuery = useQuery({
|
|
507
517
|
queryKey: apiKeysQueryKey(apiBaseUrl),
|
|
508
518
|
enabled: auth.isAuthenticated && !isPreview && !isDemoSession && Boolean(apiBaseUrl),
|
|
@@ -520,7 +530,7 @@ function useApiKeys() {
|
|
|
520
530
|
});
|
|
521
531
|
if (isDemoSession && auth.isAuthenticated) return {
|
|
522
532
|
...liveQuery,
|
|
523
|
-
data: createDemoApiKeys(),
|
|
533
|
+
data: createDemoApiKeys(previewPlans),
|
|
524
534
|
error: null,
|
|
525
535
|
isError: false,
|
|
526
536
|
isFetching: false,
|
|
@@ -535,7 +545,7 @@ function useCreateApiKey() {
|
|
|
535
545
|
const apiBaseUrl = useDevPortalApiBaseUrl();
|
|
536
546
|
const queryClient = useQueryClient();
|
|
537
547
|
return useMutation({
|
|
538
|
-
mutationFn: async (name) => {
|
|
548
|
+
mutationFn: async ({ name, planSku }) => {
|
|
539
549
|
if (!apiBaseUrl) throw new Error("Dev portal API is not configured.");
|
|
540
550
|
const response = await fetch(`${apiBaseUrl}/api/v1/api-keys`, {
|
|
541
551
|
method: "POST",
|
|
@@ -544,7 +554,10 @@ function useCreateApiKey() {
|
|
|
544
554
|
"Content-Type": "application/json"
|
|
545
555
|
},
|
|
546
556
|
credentials: "include",
|
|
547
|
-
body: JSON.stringify({
|
|
557
|
+
body: JSON.stringify({
|
|
558
|
+
name,
|
|
559
|
+
planSku
|
|
560
|
+
})
|
|
548
561
|
});
|
|
549
562
|
if (response.status === 409) throw new Error(API_KEY_CAP_REACHED);
|
|
550
563
|
if (!response.ok) throw new Error(await readProblemDetail(response) ?? "Failed to create API key.");
|
|
@@ -584,26 +597,38 @@ async function readProblemDetail(response) {
|
|
|
584
597
|
//#endregion
|
|
585
598
|
//#region src/components/ApiKeysPanel.tsx
|
|
586
599
|
/**
|
|
587
|
-
* Lists
|
|
600
|
+
* Lists gateway API keys for one plan with create and revoke. The plaintext key is returned by the
|
|
588
601
|
* platform exactly once, so it is held in local state until dismissed and never re-fetched.
|
|
589
602
|
*/
|
|
590
|
-
const ApiKeysPanel = ({ readOnly = false }) => {
|
|
591
|
-
const { data, isLoading, error } = useApiKeys();
|
|
603
|
+
const ApiKeysPanel = ({ plan, header, readOnly = false }) => {
|
|
592
604
|
const createKey = useCreateApiKey();
|
|
593
605
|
const revokeKey = useRevokeApiKey();
|
|
594
606
|
const [name, setName] = useState("");
|
|
595
607
|
const [newKey, setNewKey] = useState(null);
|
|
596
|
-
const
|
|
597
|
-
const
|
|
598
|
-
const
|
|
608
|
+
const [openRevokeId, setOpenRevokeId] = useState(null);
|
|
609
|
+
const [pendingRevokeId, setPendingRevokeId] = useState(null);
|
|
610
|
+
const activeKeys = plan.keys.filter((key) => key.status === "active");
|
|
611
|
+
const planActiveCount = activeKeys.length;
|
|
612
|
+
const capLabel = plan.maxKeys == null ? `${plan.activeCount} active (all plans)` : `${plan.activeCount} of ${plan.maxKeys} used (all plans)`;
|
|
613
|
+
const canCreate = !readOnly && plan.canCreate;
|
|
599
614
|
const handleCreate = () => {
|
|
600
615
|
const trimmed = name.trim();
|
|
601
616
|
if (!trimmed) return;
|
|
602
|
-
createKey.mutate(
|
|
617
|
+
createKey.mutate({
|
|
618
|
+
name: trimmed,
|
|
619
|
+
planSku: plan.planSku
|
|
620
|
+
}, { onSuccess: (key) => {
|
|
603
621
|
setNewKey(key);
|
|
604
622
|
setName("");
|
|
605
623
|
} });
|
|
606
624
|
};
|
|
625
|
+
const handleRevoke = (keyId) => {
|
|
626
|
+
setPendingRevokeId(keyId);
|
|
627
|
+
revokeKey.mutate(keyId, { onSettled: () => {
|
|
628
|
+
setPendingRevokeId(null);
|
|
629
|
+
setOpenRevokeId(null);
|
|
630
|
+
} });
|
|
631
|
+
};
|
|
607
632
|
return /* @__PURE__ */ jsxs("article", {
|
|
608
633
|
className: "rounded-xl border bg-card/60 p-6 space-y-5",
|
|
609
634
|
children: [
|
|
@@ -611,9 +636,9 @@ const ApiKeysPanel = ({ readOnly = false }) => {
|
|
|
611
636
|
className: "flex flex-wrap items-start justify-between gap-3",
|
|
612
637
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
613
638
|
className: "space-y-1",
|
|
614
|
-
children: [/* @__PURE__ */
|
|
639
|
+
children: [/* @__PURE__ */ jsxs("h2", {
|
|
615
640
|
className: "text-lg font-medium",
|
|
616
|
-
children: "API keys"
|
|
641
|
+
children: [plan.displayName, " API keys"]
|
|
617
642
|
}), /* @__PURE__ */ jsxs("p", {
|
|
618
643
|
className: "text-sm text-muted-foreground",
|
|
619
644
|
children: [
|
|
@@ -621,16 +646,16 @@ const ApiKeysPanel = ({ readOnly = false }) => {
|
|
|
621
646
|
" ",
|
|
622
647
|
/* @__PURE__ */ jsx("code", {
|
|
623
648
|
className: "rounded bg-muted px-1 py-0.5 text-xs",
|
|
624
|
-
children:
|
|
649
|
+
children: header
|
|
625
650
|
}),
|
|
626
651
|
" ",
|
|
627
|
-
"header when calling
|
|
652
|
+
"header when calling this plan's endpoints. Keys share your developer key cap across all plans."
|
|
628
653
|
]
|
|
629
654
|
})]
|
|
630
|
-
}),
|
|
655
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
631
656
|
className: "shrink-0 rounded-full border px-3 py-1 text-xs text-muted-foreground",
|
|
632
657
|
children: capLabel
|
|
633
|
-
})
|
|
658
|
+
})]
|
|
634
659
|
}),
|
|
635
660
|
newKey?.key ? /* @__PURE__ */ jsxs(Alert, { children: [
|
|
636
661
|
/* @__PURE__ */ jsx(KeyRoundIcon, { size: 16 }),
|
|
@@ -650,72 +675,71 @@ const ApiKeysPanel = ({ readOnly = false }) => {
|
|
|
650
675
|
] }) : null,
|
|
651
676
|
createKey.isError ? /* @__PURE__ */ jsx(Alert, {
|
|
652
677
|
variant: "destructive",
|
|
653
|
-
children: /* @__PURE__ */ jsx(AlertTitle, { children: createKey.error.message === API_KEY_CAP_REACHED ?
|
|
678
|
+
children: /* @__PURE__ */ jsx(AlertTitle, { children: createKey.error.message === API_KEY_CAP_REACHED ? plan.maxKeys == null ? "You cannot create more API keys right now." : `You already have the maximum of ${plan.maxKeys} active API keys. Revoke one to create another.` : createKey.error.message })
|
|
654
679
|
}) : null,
|
|
655
680
|
revokeKey.isError ? /* @__PURE__ */ jsx(Alert, {
|
|
656
681
|
variant: "destructive",
|
|
657
682
|
children: /* @__PURE__ */ jsx(AlertTitle, { children: revokeKey.error.message })
|
|
658
683
|
}) : null,
|
|
659
|
-
|
|
660
|
-
className: "text-sm text-muted-foreground",
|
|
661
|
-
children: "Loading…"
|
|
662
|
-
}) : null,
|
|
663
|
-
error ? /* @__PURE__ */ jsx("p", {
|
|
664
|
-
className: "text-sm text-muted-foreground",
|
|
665
|
-
children: error instanceof Error && error.message === "NO_ENTITLEMENT" ? "Choose a plan to get your first API key." : "Unable to load your API keys. Try again in a moment."
|
|
666
|
-
}) : null,
|
|
667
|
-
data && activeKeys.length === 0 && !isLoading ? /* @__PURE__ */ jsx("p", {
|
|
684
|
+
planActiveCount === 0 ? /* @__PURE__ */ jsx("p", {
|
|
668
685
|
className: "text-sm text-muted-foreground",
|
|
669
|
-
children: "You have no active API keys yet."
|
|
686
|
+
children: "You have no active API keys for this plan yet."
|
|
670
687
|
}) : null,
|
|
671
688
|
activeKeys.length > 0 ? /* @__PURE__ */ jsx("ul", {
|
|
672
689
|
className: "divide-y rounded-lg border",
|
|
673
|
-
children: activeKeys.map((key) =>
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
className: "
|
|
677
|
-
children: [/* @__PURE__ */
|
|
678
|
-
className: "
|
|
679
|
-
children:
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
asChild: true,
|
|
691
|
-
children: /* @__PURE__ */ jsxs(Button$1, {
|
|
692
|
-
variant: "ghost",
|
|
693
|
-
className: "flex items-center gap-2",
|
|
694
|
-
disabled: revokeKey.isPending,
|
|
695
|
-
children: [/* @__PURE__ */ jsx(TrashIcon, { size: 16 }), /* @__PURE__ */ jsx("span", {
|
|
696
|
-
className: "hidden md:block",
|
|
697
|
-
children: "Revoke"
|
|
690
|
+
children: activeKeys.map((key) => {
|
|
691
|
+
const isRevoking = pendingRevokeId === key.id;
|
|
692
|
+
return /* @__PURE__ */ jsxs("li", {
|
|
693
|
+
className: "flex flex-wrap items-center justify-between gap-3 p-4",
|
|
694
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
695
|
+
className: "min-w-0 space-y-1",
|
|
696
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
697
|
+
className: "truncate font-medium",
|
|
698
|
+
children: key.name
|
|
699
|
+
}), /* @__PURE__ */ jsxs("p", {
|
|
700
|
+
className: "text-xs text-muted-foreground",
|
|
701
|
+
children: [
|
|
702
|
+
/* @__PURE__ */ jsxs("code", { children: [key.keyPrefix, "…"] }),
|
|
703
|
+
" · created",
|
|
704
|
+
" ",
|
|
705
|
+
new Date(key.createdAtUtc).toLocaleDateString()
|
|
706
|
+
]
|
|
698
707
|
})]
|
|
699
|
-
})
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
708
|
+
}), readOnly ? null : /* @__PURE__ */ jsxs(Dialog, {
|
|
709
|
+
open: openRevokeId === key.id,
|
|
710
|
+
onOpenChange: (open) => {
|
|
711
|
+
if (isRevoking) return;
|
|
712
|
+
setOpenRevokeId(open ? key.id : null);
|
|
713
|
+
},
|
|
714
|
+
children: [/* @__PURE__ */ jsx(DialogTrigger, {
|
|
715
|
+
asChild: true,
|
|
716
|
+
children: /* @__PURE__ */ jsxs(Button$1, {
|
|
717
|
+
variant: "ghost",
|
|
718
|
+
className: "flex items-center gap-2",
|
|
719
|
+
disabled: isRevoking,
|
|
720
|
+
children: [isRevoking ? /* @__PURE__ */ jsx(Spinner, { size: 14 }) : /* @__PURE__ */ jsx(TrashIcon, { size: 16 }), /* @__PURE__ */ jsx("span", {
|
|
721
|
+
className: "hidden md:block",
|
|
722
|
+
children: isRevoking ? "Revoking…" : "Revoke"
|
|
723
|
+
})]
|
|
724
|
+
})
|
|
725
|
+
}), /* @__PURE__ */ jsxs(DialogContent, { children: [/* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsxs(DialogTitle, { children: [
|
|
726
|
+
"Revoke “",
|
|
727
|
+
key.name,
|
|
728
|
+
"”?"
|
|
729
|
+
] }), /* @__PURE__ */ jsx(DialogDescription, { children: "Any application using this key will immediately start getting 401 responses from the gateway. This cannot be undone." })] }), /* @__PURE__ */ jsxs(DialogFooter, { children: [/* @__PURE__ */ jsx(Button$1, {
|
|
730
|
+
variant: "outline",
|
|
731
|
+
disabled: isRevoking,
|
|
732
|
+
onClick: () => setOpenRevokeId(null),
|
|
733
|
+
children: "Cancel"
|
|
734
|
+
}), /* @__PURE__ */ jsx(Button$1, {
|
|
735
|
+
variant: "destructive",
|
|
736
|
+
disabled: isRevoking,
|
|
737
|
+
onClick: () => handleRevoke(key.id),
|
|
738
|
+
children: isRevoking ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Spinner, { size: 14 }), "Revoking…"] }) : "Revoke key"
|
|
739
|
+
})] })] })]
|
|
740
|
+
})]
|
|
741
|
+
}, key.id);
|
|
742
|
+
})
|
|
719
743
|
}) : null,
|
|
720
744
|
readOnly ? null : /* @__PURE__ */ jsxs("div", {
|
|
721
745
|
className: "flex flex-wrap items-center gap-2",
|
|
@@ -723,7 +747,7 @@ const ApiKeysPanel = ({ readOnly = false }) => {
|
|
|
723
747
|
className: "max-w-xs",
|
|
724
748
|
maxLength: 64,
|
|
725
749
|
placeholder: "Key name (e.g. production)",
|
|
726
|
-
"aria-label":
|
|
750
|
+
"aria-label": `New API key name for ${plan.displayName}`,
|
|
727
751
|
value: name,
|
|
728
752
|
disabled: !canCreate || createKey.isPending,
|
|
729
753
|
onChange: (event) => setName(event.target.value),
|
|
@@ -733,12 +757,13 @@ const ApiKeysPanel = ({ readOnly = false }) => {
|
|
|
733
757
|
}), /* @__PURE__ */ jsx(Button$1, {
|
|
734
758
|
onClick: handleCreate,
|
|
735
759
|
disabled: !canCreate || createKey.isPending || !name.trim(),
|
|
736
|
-
|
|
760
|
+
className: "flex items-center gap-2",
|
|
761
|
+
children: createKey.isPending ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Spinner, { size: 14 }), "Creating…"] }) : "Create API key"
|
|
737
762
|
})]
|
|
738
763
|
}),
|
|
739
|
-
|
|
764
|
+
plan && !plan.canCreate && !readOnly ? /* @__PURE__ */ jsx("p", {
|
|
740
765
|
className: "text-sm text-muted-foreground",
|
|
741
|
-
children:
|
|
766
|
+
children: plan.maxKeys == null ? "API key creation is currently disabled for this API." : `You have reached your limit of ${plan.maxKeys} API keys across all plans. Revoke a key to create a new one.`
|
|
742
767
|
}) : null
|
|
743
768
|
]
|
|
744
769
|
});
|
|
@@ -965,9 +990,10 @@ const CredentialsPage = () => {
|
|
|
965
990
|
const isPreview = useDevPortalPreviewMode();
|
|
966
991
|
const isDemoSession = useIsDemoSession();
|
|
967
992
|
const { data: credentials, isLoading, error } = useCredentials();
|
|
968
|
-
const { data:
|
|
969
|
-
const
|
|
970
|
-
const
|
|
993
|
+
const { data: apiKeys, isLoading: keysLoading, error: keysError } = useApiKeys();
|
|
994
|
+
const gatewayBaseUrl = credentials?.gatewayBaseUrl ?? apiKeys?.gatewayBaseUrl ?? null;
|
|
995
|
+
const header = apiKeys?.header ?? "Ocp-Apim-Subscription-Key";
|
|
996
|
+
const plans = apiKeys?.plans ?? [];
|
|
971
997
|
return /* @__PURE__ */ jsxs("div", {
|
|
972
998
|
className: "mx-auto max-w-screen-lg px-4 pb-16 pt-(--padding-content-top) lg:px-8",
|
|
973
999
|
children: [
|
|
@@ -984,7 +1010,7 @@ const CredentialsPage = () => {
|
|
|
984
1010
|
children: "API credentials"
|
|
985
1011
|
}), /* @__PURE__ */ jsx("p", {
|
|
986
1012
|
className: "text-muted-foreground",
|
|
987
|
-
children: "Create API keys and use them to authenticate requests to the API gateway."
|
|
1013
|
+
children: "Create API keys for each subscribed plan and use them to authenticate requests to the API gateway."
|
|
988
1014
|
})]
|
|
989
1015
|
})]
|
|
990
1016
|
}),
|
|
@@ -1003,7 +1029,7 @@ const CredentialsPage = () => {
|
|
|
1003
1029
|
className: "rounded-xl border border-dashed p-8 text-center",
|
|
1004
1030
|
children: [/* @__PURE__ */ jsx("p", {
|
|
1005
1031
|
className: "text-muted-foreground",
|
|
1006
|
-
children: "Sign in to view your subscription
|
|
1032
|
+
children: "Sign in to view your subscription keys."
|
|
1007
1033
|
}), /* @__PURE__ */ jsx(Button$2, {
|
|
1008
1034
|
className: "mt-4",
|
|
1009
1035
|
asChild: true,
|
|
@@ -1013,15 +1039,15 @@ const CredentialsPage = () => {
|
|
|
1013
1039
|
})
|
|
1014
1040
|
})]
|
|
1015
1041
|
}) : null,
|
|
1016
|
-
!isPreview && !isDemoSession && auth.isAuthenticated && isLoading ? /* @__PURE__ */ jsx("p", {
|
|
1042
|
+
!isPreview && !isDemoSession && auth.isAuthenticated && (isLoading || keysLoading) ? /* @__PURE__ */ jsx("p", {
|
|
1017
1043
|
className: "text-muted-foreground",
|
|
1018
1044
|
children: "Loading credentials…"
|
|
1019
1045
|
}) : null,
|
|
1020
|
-
!isPreview && !isDemoSession && auth.isAuthenticated && error ? /* @__PURE__ */ jsxs("div", {
|
|
1046
|
+
!isPreview && !isDemoSession && auth.isAuthenticated && (error || keysError) ? /* @__PURE__ */ jsxs("div", {
|
|
1021
1047
|
className: "rounded-xl border border-dashed p-8 text-center",
|
|
1022
1048
|
children: [/* @__PURE__ */ jsx("p", {
|
|
1023
1049
|
className: "text-muted-foreground",
|
|
1024
|
-
children: error instanceof Error && error.message === "NO_ENTITLEMENT" ? "No active subscription found. Choose a plan to get your API key." : "Unable to load API credentials. Try again in a moment."
|
|
1050
|
+
children: error instanceof Error && error.message === "NO_ENTITLEMENT" || keysError instanceof Error && keysError.message === "NO_ENTITLEMENT" ? "No active subscription found. Choose a plan to get your API key." : "Unable to load API credentials. Try again in a moment."
|
|
1025
1051
|
}), /* @__PURE__ */ jsx(Button$2, {
|
|
1026
1052
|
className: "mt-4",
|
|
1027
1053
|
asChild: true,
|
|
@@ -1031,66 +1057,44 @@ const CredentialsPage = () => {
|
|
|
1031
1057
|
})
|
|
1032
1058
|
})]
|
|
1033
1059
|
}) : null,
|
|
1034
|
-
(isDemoSession || !isPreview) &&
|
|
1060
|
+
(isDemoSession || !isPreview) && auth.isAuthenticated && !error && !keysError && !isLoading && !keysLoading ? /* @__PURE__ */ jsxs("div", {
|
|
1035
1061
|
className: "space-y-6",
|
|
1036
|
-
children: [
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
})]
|
|
1062
|
+
children: [plans.length === 0 ? /* @__PURE__ */ jsxs("div", {
|
|
1063
|
+
className: "rounded-xl border border-dashed p-8 text-center",
|
|
1064
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
1065
|
+
className: "text-muted-foreground",
|
|
1066
|
+
children: "Choose a plan to get your first API key."
|
|
1067
|
+
}), /* @__PURE__ */ jsx(Button$2, {
|
|
1068
|
+
className: "mt-4",
|
|
1069
|
+
asChild: true,
|
|
1070
|
+
children: /* @__PURE__ */ jsx(Link, {
|
|
1071
|
+
to: "/account/plans",
|
|
1072
|
+
children: "View plans"
|
|
1048
1073
|
})
|
|
1049
|
-
})
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
/* @__PURE__ */ jsx("code", { children: "default" }),
|
|
1063
|
-
" key issued with your plan. Keys you create above are shown only once, so store them yourself."
|
|
1064
|
-
]
|
|
1065
|
-
})]
|
|
1066
|
-
}), /* @__PURE__ */ jsx(Secret, {
|
|
1067
|
-
className: "max-w-full w-full",
|
|
1068
|
-
secret: credentials.subscriptionKey,
|
|
1069
|
-
status: "active",
|
|
1070
|
-
revealed,
|
|
1071
|
-
onReveal: setRevealed
|
|
1072
|
-
})]
|
|
1073
|
-
}) : null,
|
|
1074
|
-
/* @__PURE__ */ jsxs("article", {
|
|
1075
|
-
className: "rounded-xl border bg-card/60 p-6 space-y-4",
|
|
1076
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
1077
|
-
className: "space-y-1",
|
|
1078
|
-
children: [/* @__PURE__ */ jsx("h2", {
|
|
1079
|
-
className: "text-lg font-medium",
|
|
1080
|
-
children: "Gateway URL"
|
|
1081
|
-
}), /* @__PURE__ */ jsx("p", {
|
|
1082
|
-
className: "text-sm text-muted-foreground",
|
|
1083
|
-
children: "Base URL for API requests through the gateway."
|
|
1084
|
-
})]
|
|
1085
|
-
}), credentials.gatewayBaseUrl ? /* @__PURE__ */ jsx(Value, {
|
|
1086
|
-
className: "max-w-full w-full",
|
|
1087
|
-
value: credentials.gatewayBaseUrl
|
|
1088
|
-
}) : /* @__PURE__ */ jsx("p", {
|
|
1074
|
+
})]
|
|
1075
|
+
}) : plans.map((plan) => /* @__PURE__ */ jsx(ApiKeysPanel, {
|
|
1076
|
+
plan,
|
|
1077
|
+
header,
|
|
1078
|
+
readOnly: isDemoSession
|
|
1079
|
+
}, plan.planSku)), /* @__PURE__ */ jsxs("article", {
|
|
1080
|
+
className: "rounded-xl border bg-card/60 p-6 space-y-4",
|
|
1081
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1082
|
+
className: "space-y-1",
|
|
1083
|
+
children: [/* @__PURE__ */ jsx("h2", {
|
|
1084
|
+
className: "text-lg font-medium",
|
|
1085
|
+
children: "Gateway URL"
|
|
1086
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
1089
1087
|
className: "text-sm text-muted-foreground",
|
|
1090
|
-
children: "
|
|
1088
|
+
children: "Base URL for API requests through the gateway."
|
|
1091
1089
|
})]
|
|
1092
|
-
})
|
|
1093
|
-
|
|
1090
|
+
}), gatewayBaseUrl ? /* @__PURE__ */ jsx(Value, {
|
|
1091
|
+
className: "max-w-full w-full",
|
|
1092
|
+
value: gatewayBaseUrl
|
|
1093
|
+
}) : /* @__PURE__ */ jsx("p", {
|
|
1094
|
+
className: "text-sm text-muted-foreground",
|
|
1095
|
+
children: "Available after your API is deployed to the gateway."
|
|
1096
|
+
})]
|
|
1097
|
+
})]
|
|
1094
1098
|
}) : null,
|
|
1095
1099
|
/* @__PURE__ */ jsxs("div", {
|
|
1096
1100
|
className: "mt-8 flex flex-wrap gap-3",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lukoweb/apitogo-plugin-dev-portal-billing",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.74",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/zuplo/zudoku",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"react": "19.2.4",
|
|
47
47
|
"react-dom": "19.2.4",
|
|
48
48
|
"tsdown": "0.20.3",
|
|
49
|
-
"@lukoweb/apitogo": "0.1.
|
|
49
|
+
"@lukoweb/apitogo": "0.1.74"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"react": ">=19.2.0",
|