@lukoweb/apitogo-plugin-dev-portal-billing 0.1.37 → 0.1.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -8,10 +8,13 @@ type DevPortalBillingConfig = {
8
8
  };
9
9
  };
10
10
  //#endregion
11
- //#region src/DevPortalBillingPlugin.d.ts
11
+ //#region src/paths.d.ts
12
12
  declare const PRICING_PATH = "/pricing";
13
13
  declare const CHECKOUT_PATH = "/checkout";
14
14
  declare const SUBSCRIPTIONS_PATH = "/account/subscriptions";
15
+ declare const CREDENTIALS_PATH = "/account/credentials";
16
+ //#endregion
17
+ //#region src/DevPortalBillingPlugin.d.ts
15
18
  declare const devPortalBillingPlugin: (options?: DevPortalBillingConfig | undefined) => _lukoweb_apitogo0.ApitogoPlugin;
16
19
  //#endregion
17
- export { CHECKOUT_PATH, PRICING_PATH, SUBSCRIPTIONS_PATH, devPortalBillingPlugin };
20
+ export { CHECKOUT_PATH, CREDENTIALS_PATH, PRICING_PATH, SUBSCRIPTIONS_PATH, devPortalBillingPlugin };
package/dist/index.mjs CHANGED
@@ -1,13 +1,14 @@
1
1
  import { cn, createPlugin } from "@lukoweb/apitogo";
2
- import { ShieldIcon, StarsIcon } from "@lukoweb/apitogo/icons";
2
+ import { KeyRoundIcon, ShieldIcon, StarsIcon } from "@lukoweb/apitogo/icons";
3
3
  import { Button, ClientOnly, Head, Heading } from "@lukoweb/apitogo/components";
4
4
  import { Link, Outlet, useNavigate, useSearchParams } from "@lukoweb/apitogo/router";
5
- import { createContext, useContext, useEffect } from "react";
5
+ import { createContext, useContext, useEffect, useState } from "react";
6
6
  import { jsx, jsxs } from "react/jsx-runtime";
7
7
  import { useAuth } from "@lukoweb/apitogo/hooks";
8
8
  import { useQuery } from "@lukoweb/apitogo/react-query";
9
9
  import { Alert, AlertAction, AlertDescription } from "@lukoweb/apitogo/ui/Alert";
10
10
  import { Button as Button$1 } from "@lukoweb/apitogo/ui/Button";
11
+ import { Secret } from "@lukoweb/apitogo/ui/Secret.js";
11
12
 
12
13
  //#region src/DevPortalBillingContext.tsx
13
14
  const DevPortalBillingContext = createContext({});
@@ -248,6 +249,204 @@ const CheckoutPage = () => {
248
249
  });
249
250
  };
250
251
 
252
+ //#endregion
253
+ //#region src/hooks/useCredentials.ts
254
+ function useCredentials() {
255
+ const auth = useAuth();
256
+ const apiBaseUrl = useDevPortalApiBaseUrl();
257
+ const isPreview = useDevPortalPreviewMode();
258
+ return useQuery({
259
+ queryKey: ["dev-portal-credentials", apiBaseUrl],
260
+ enabled: auth.isAuthenticated && !isPreview && Boolean(apiBaseUrl),
261
+ queryFn: async () => {
262
+ const response = await fetch(`${apiBaseUrl}/api/v1/credentials`, {
263
+ method: "GET",
264
+ headers: { Accept: "application/json" },
265
+ credentials: "include"
266
+ });
267
+ if (response.status === 404) throw new Error("NO_ENTITLEMENT");
268
+ if (!response.ok) throw new Error("Failed to load API credentials.");
269
+ return await response.json();
270
+ },
271
+ retry: (failureCount, error) => error instanceof Error && error.message === "NO_ENTITLEMENT" ? false : failureCount < 2
272
+ });
273
+ }
274
+
275
+ //#endregion
276
+ //#region src/hooks/usePlans.ts
277
+ function usePlans() {
278
+ const apiBaseUrl = useDevPortalApiBaseUrl();
279
+ const isPreview = useDevPortalPreviewMode();
280
+ return useQuery({
281
+ queryKey: [
282
+ "dev-portal-plans",
283
+ apiBaseUrl,
284
+ isPreview
285
+ ],
286
+ queryFn: async () => {
287
+ if (isPreview) return parseMockPlans();
288
+ if (!apiBaseUrl) return { plans: [] };
289
+ const response = await fetch(`${apiBaseUrl}/api/v1/plans`, { headers: { Accept: "application/json" } });
290
+ if (!response.ok) throw new Error("Failed to load plans.");
291
+ return await response.json();
292
+ }
293
+ });
294
+ }
295
+
296
+ //#endregion
297
+ //#region src/pages/CredentialsPage.tsx
298
+ const CredentialsPage = () => {
299
+ const auth = useAuth();
300
+ const isPreview = useDevPortalPreviewMode();
301
+ const { data: credentials, isLoading, error } = useCredentials();
302
+ const { data: catalog } = usePlans();
303
+ const [revealed, setRevealed] = useState(false);
304
+ const plan = catalog?.plans.find((item) => item.sku === credentials?.planSku);
305
+ return /* @__PURE__ */ jsxs("div", {
306
+ className: "mx-auto max-w-screen-lg px-4 pb-16 pt-(--padding-content-top) lg:px-8",
307
+ children: [
308
+ /* @__PURE__ */ jsx(Head, { children: /* @__PURE__ */ jsx("title", { children: "API credentials" }) }),
309
+ /* @__PURE__ */ jsxs("div", {
310
+ className: "mb-8 flex items-start gap-4",
311
+ children: [/* @__PURE__ */ jsx("div", {
312
+ className: "flex size-12 shrink-0 items-center justify-center rounded-xl bg-primary/10 text-primary",
313
+ children: /* @__PURE__ */ jsx(KeyRoundIcon, { size: 24 })
314
+ }), /* @__PURE__ */ jsxs("div", {
315
+ className: "space-y-1",
316
+ children: [/* @__PURE__ */ jsx("h1", {
317
+ className: "text-3xl font-semibold tracking-tight",
318
+ children: "API credentials"
319
+ }), /* @__PURE__ */ jsx("p", {
320
+ className: "text-muted-foreground",
321
+ children: "Use your subscription key to authenticate requests to the API gateway."
322
+ })]
323
+ })]
324
+ }),
325
+ isPreview ? /* @__PURE__ */ jsx("div", {
326
+ className: "rounded-xl border border-dashed p-8 text-center",
327
+ children: /* @__PURE__ */ jsx("p", {
328
+ className: "text-muted-foreground",
329
+ children: "API credentials are available after the developer portal is published."
330
+ })
331
+ }) : null,
332
+ !isPreview && !auth.isAuthenticated ? /* @__PURE__ */ jsxs("div", {
333
+ className: "rounded-xl border border-dashed p-8 text-center",
334
+ children: [/* @__PURE__ */ jsx("p", {
335
+ className: "text-muted-foreground",
336
+ children: "Sign in to view your subscription key."
337
+ }), /* @__PURE__ */ jsx(Button$1, {
338
+ className: "mt-4",
339
+ asChild: true,
340
+ children: /* @__PURE__ */ jsx(Link, {
341
+ to: "/pricing",
342
+ children: "View plans"
343
+ })
344
+ })]
345
+ }) : null,
346
+ !isPreview && auth.isAuthenticated && isLoading ? /* @__PURE__ */ jsx("p", {
347
+ className: "text-muted-foreground",
348
+ children: "Loading credentials…"
349
+ }) : null,
350
+ !isPreview && auth.isAuthenticated && error ? /* @__PURE__ */ jsxs("div", {
351
+ className: "rounded-xl border border-dashed p-8 text-center",
352
+ children: [/* @__PURE__ */ jsx("p", {
353
+ className: "text-muted-foreground",
354
+ 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."
355
+ }), /* @__PURE__ */ jsx(Button$1, {
356
+ className: "mt-4",
357
+ asChild: true,
358
+ children: /* @__PURE__ */ jsx(Link, {
359
+ to: "/pricing",
360
+ children: "View plans"
361
+ })
362
+ })]
363
+ }) : null,
364
+ !isPreview && credentials ? /* @__PURE__ */ jsxs("div", {
365
+ className: "space-y-6",
366
+ children: [
367
+ /* @__PURE__ */ jsx("article", {
368
+ className: "rounded-xl border bg-card/60 p-6",
369
+ children: /* @__PURE__ */ jsxs("div", {
370
+ className: "space-y-1",
371
+ children: [/* @__PURE__ */ jsx("h2", {
372
+ className: "text-lg font-medium",
373
+ children: "Active plan"
374
+ }), /* @__PURE__ */ jsx("p", {
375
+ className: "text-sm text-muted-foreground",
376
+ children: plan?.displayName ?? credentials.planSku
377
+ })]
378
+ })
379
+ }),
380
+ /* @__PURE__ */ jsxs("article", {
381
+ className: "rounded-xl border bg-card/60 p-6 space-y-4",
382
+ children: [/* @__PURE__ */ jsxs("div", {
383
+ className: "space-y-1",
384
+ children: [/* @__PURE__ */ jsx("h2", {
385
+ className: "text-lg font-medium",
386
+ children: "Subscription key"
387
+ }), /* @__PURE__ */ jsxs("p", {
388
+ className: "text-sm text-muted-foreground",
389
+ children: [
390
+ "Send this value in the",
391
+ " ",
392
+ /* @__PURE__ */ jsx("code", {
393
+ className: "rounded bg-muted px-1 py-0.5 text-xs",
394
+ children: "Ocp-Apim-Subscription-Key"
395
+ }),
396
+ " ",
397
+ "header when calling the gateway."
398
+ ]
399
+ })]
400
+ }), /* @__PURE__ */ jsx(Secret, {
401
+ className: "max-w-full w-full",
402
+ secret: credentials.subscriptionKey,
403
+ status: "active",
404
+ revealed,
405
+ onReveal: setRevealed
406
+ })]
407
+ }),
408
+ /* @__PURE__ */ jsxs("article", {
409
+ className: "rounded-xl border bg-card/60 p-6 space-y-4",
410
+ children: [/* @__PURE__ */ jsxs("div", {
411
+ className: "space-y-1",
412
+ children: [/* @__PURE__ */ jsx("h2", {
413
+ className: "text-lg font-medium",
414
+ children: "Gateway URL"
415
+ }), /* @__PURE__ */ jsx("p", {
416
+ className: "text-sm text-muted-foreground",
417
+ children: "Base URL for API requests through the gateway."
418
+ })]
419
+ }), /* @__PURE__ */ jsx(Secret, {
420
+ className: "max-w-full w-full",
421
+ secret: credentials.gatewayBaseUrl,
422
+ status: "none",
423
+ noReveal: true
424
+ })]
425
+ })
426
+ ]
427
+ }) : null,
428
+ /* @__PURE__ */ jsxs("div", {
429
+ className: "mt-8 flex flex-wrap gap-3",
430
+ children: [/* @__PURE__ */ jsx(Button$1, {
431
+ variant: "outline",
432
+ asChild: true,
433
+ children: /* @__PURE__ */ jsx(Link, {
434
+ to: "/account/subscriptions",
435
+ children: "My subscriptions"
436
+ })
437
+ }), /* @__PURE__ */ jsx(Button$1, {
438
+ variant: "outline",
439
+ asChild: true,
440
+ children: /* @__PURE__ */ jsx(Link, {
441
+ to: "/api",
442
+ children: "API reference"
443
+ })
444
+ })]
445
+ })
446
+ ]
447
+ });
448
+ };
449
+
251
450
  //#endregion
252
451
  //#region src/components/PricingCard.tsx
253
452
  const PricingCard = ({ plan, isSubscribed = false }) => {
@@ -317,27 +516,6 @@ function useMySubscriptions() {
317
516
  });
318
517
  }
319
518
 
320
- //#endregion
321
- //#region src/hooks/usePlans.ts
322
- function usePlans() {
323
- const apiBaseUrl = useDevPortalApiBaseUrl();
324
- const isPreview = useDevPortalPreviewMode();
325
- return useQuery({
326
- queryKey: [
327
- "dev-portal-plans",
328
- apiBaseUrl,
329
- isPreview
330
- ],
331
- queryFn: async () => {
332
- if (isPreview) return parseMockPlans();
333
- if (!apiBaseUrl) return { plans: [] };
334
- const response = await fetch(`${apiBaseUrl}/api/v1/plans`, { headers: { Accept: "application/json" } });
335
- if (!response.ok) throw new Error("Failed to load plans.");
336
- return await response.json();
337
- }
338
- });
339
- }
340
-
341
519
  //#endregion
342
520
  //#region src/pages/PricingPage.tsx
343
521
  const PricingPage = () => {
@@ -413,6 +591,13 @@ const PricingPage = () => {
413
591
  });
414
592
  };
415
593
 
594
+ //#endregion
595
+ //#region src/paths.ts
596
+ const PRICING_PATH = "/pricing";
597
+ const CHECKOUT_PATH = "/checkout";
598
+ const SUBSCRIPTIONS_PATH = "/account/subscriptions";
599
+ const CREDENTIALS_PATH = "/account/credentials";
600
+
416
601
  //#endregion
417
602
  //#region src/pages/SubscriptionsPage.tsx
418
603
  const SubscriptionsPage = () => {
@@ -437,9 +622,18 @@ const SubscriptionsPage = () => {
437
622
  children: "Review your active plans and manage billing."
438
623
  })]
439
624
  }),
440
- checkoutSuccess ? /* @__PURE__ */ jsx("div", {
625
+ checkoutSuccess ? /* @__PURE__ */ jsxs("div", {
441
626
  className: "mb-6 rounded-xl border border-green-500/30 bg-green-500/10 p-4 text-sm",
442
- children: "Your subscription is active. API credentials are available from your account dashboard."
627
+ children: [
628
+ "Your subscription is active.",
629
+ " ",
630
+ /* @__PURE__ */ jsx(Link, {
631
+ to: CREDENTIALS_PATH,
632
+ className: "font-medium underline",
633
+ children: "View your API credentials"
634
+ }),
635
+ "."
636
+ ]
443
637
  }) : null,
444
638
  isLoading ? /* @__PURE__ */ jsx("p", {
445
639
  className: "text-muted-foreground",
@@ -516,8 +710,8 @@ const SubscriptionsPage = () => {
516
710
  variant: "secondary",
517
711
  asChild: true,
518
712
  children: /* @__PURE__ */ jsx(Link, {
519
- to: "/account/dashboard",
520
- children: "Account dashboard"
713
+ to: CREDENTIALS_PATH,
714
+ children: "API credentials"
521
715
  })
522
716
  }) : null
523
717
  ]
@@ -528,9 +722,6 @@ const SubscriptionsPage = () => {
528
722
 
529
723
  //#endregion
530
724
  //#region src/DevPortalBillingPlugin.tsx
531
- const PRICING_PATH = "/pricing";
532
- const CHECKOUT_PATH = "/checkout";
533
- const SUBSCRIPTIONS_PATH = "/account/subscriptions";
534
725
  const devPortalBillingPlugin = createPlugin((options = {}) => ({
535
726
  transformConfig: ({ config, merge }) => merge({ header: { navigation: [...config.header?.navigation ?? [], {
536
727
  label: "Pricing",
@@ -542,6 +733,12 @@ const devPortalBillingPlugin = createPlugin((options = {}) => ({
542
733
  icon: StarsIcon,
543
734
  category: "middle",
544
735
  weight: 25
736
+ }, {
737
+ label: "API credentials",
738
+ path: CREDENTIALS_PATH,
739
+ icon: KeyRoundIcon,
740
+ category: "middle",
741
+ weight: 26
545
742
  }],
546
743
  getRoutes: () => [{
547
744
  element: /* @__PURE__ */ jsx(DevPortalBillingWrapper, { options }),
@@ -561,11 +758,20 @@ const devPortalBillingPlugin = createPlugin((options = {}) => ({
561
758
  path: SUBSCRIPTIONS_PATH,
562
759
  handle: { layout: "default" },
563
760
  element: /* @__PURE__ */ jsx(SubscriptionsPage, {})
761
+ },
762
+ {
763
+ path: CREDENTIALS_PATH,
764
+ handle: { layout: "default" },
765
+ element: /* @__PURE__ */ jsx(CredentialsPage, {})
564
766
  }
565
767
  ]
566
768
  }],
567
- getProtectedRoutes: () => [CHECKOUT_PATH, SUBSCRIPTIONS_PATH]
769
+ getProtectedRoutes: () => [
770
+ CHECKOUT_PATH,
771
+ SUBSCRIPTIONS_PATH,
772
+ CREDENTIALS_PATH
773
+ ]
568
774
  }));
569
775
 
570
776
  //#endregion
571
- export { CHECKOUT_PATH, PRICING_PATH, SUBSCRIPTIONS_PATH, devPortalBillingPlugin };
777
+ export { CHECKOUT_PATH, CREDENTIALS_PATH, PRICING_PATH, SUBSCRIPTIONS_PATH, devPortalBillingPlugin };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lukoweb/apitogo-plugin-dev-portal-billing",
3
- "version": "0.1.37",
3
+ "version": "0.1.39",
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.37"
49
+ "@lukoweb/apitogo": "0.1.39"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "react": ">=19.2.0",