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

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
@@ -1,5 +1,9 @@
1
1
  import * as _lukoweb_apitogo0 from "@lukoweb/apitogo";
2
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
2
3
 
4
+ //#region src/pages/AccountPlansPage.d.ts
5
+ declare const AccountPlansPage: () => react_jsx_runtime0.JSX.Element;
6
+ //#endregion
3
7
  //#region src/types.d.ts
4
8
  type DevPortalBillingConfig = {
5
9
  pricing?: {
@@ -17,4 +21,4 @@ declare const CREDENTIALS_PATH = "/account/credentials";
17
21
  //#region src/DevPortalBillingPlugin.d.ts
18
22
  declare const devPortalBillingPlugin: (options?: DevPortalBillingConfig | undefined) => _lukoweb_apitogo0.ApitogoPlugin;
19
23
  //#endregion
20
- export { CHECKOUT_PATH, CREDENTIALS_PATH, PRICING_PATH, SUBSCRIPTIONS_PATH, devPortalBillingPlugin };
24
+ export { AccountPlansPage, CHECKOUT_PATH, CREDENTIALS_PATH, PRICING_PATH, SUBSCRIPTIONS_PATH, devPortalBillingPlugin };
package/dist/index.mjs CHANGED
@@ -1,73 +1,16 @@
1
- import { cn, createPlugin } from "@lukoweb/apitogo";
2
- import { KeyRoundIcon, ShieldIcon, StarsIcon } from "@lukoweb/apitogo/icons";
3
1
  import { Button, ClientOnly, Head, Heading } from "@lukoweb/apitogo/components";
4
- import { Link, Outlet, useNavigate, useSearchParams } from "@lukoweb/apitogo/router";
2
+ import { CreditCardIcon, KeyRoundIcon, ShieldIcon, StarsIcon } from "@lukoweb/apitogo/icons";
5
3
  import { createContext, useContext, useEffect, useState } from "react";
6
- import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { cn, createPlugin } from "@lukoweb/apitogo";
5
+ import { Link, Navigate, Outlet, useNavigate, useSearchParams } from "@lukoweb/apitogo/router";
6
+ import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@lukoweb/apitogo/ui/AlertDialog.js";
7
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
+ import { useMutation, useQuery, useQueryClient } from "@lukoweb/apitogo/react-query";
7
9
  import { useAuth } from "@lukoweb/apitogo/hooks";
8
- import { useQuery } from "@lukoweb/apitogo/react-query";
9
10
  import { Alert, AlertAction, AlertDescription } from "@lukoweb/apitogo/ui/Alert";
10
11
  import { Button as Button$1 } from "@lukoweb/apitogo/ui/Button";
11
12
  import { Secret } from "@lukoweb/apitogo/ui/Secret.js";
12
13
 
13
- //#region src/DevPortalBillingContext.tsx
14
- const DevPortalBillingContext = createContext({});
15
- const DevPortalBillingProvider = ({ options, children }) => /* @__PURE__ */ jsx(DevPortalBillingContext.Provider, {
16
- value: options,
17
- children
18
- });
19
- const useDevPortalBillingConfig = () => useContext(DevPortalBillingContext);
20
-
21
- //#endregion
22
- //#region src/DevPortalBillingWrapper.tsx
23
- const DevPortalBillingWrapper = ({ options = {} }) => /* @__PURE__ */ jsx(DevPortalBillingProvider, {
24
- options,
25
- children: /* @__PURE__ */ jsx(ClientOnly, { children: /* @__PURE__ */ jsx(Outlet, {}) })
26
- });
27
-
28
- //#endregion
29
- //#region src/components/RedirectPage.tsx
30
- const RedirectPage = ({ icon: Icon, title, description, url, children }) => {
31
- useEffect(() => {
32
- if (url) window.location.assign(url);
33
- }, [url]);
34
- return /* @__PURE__ */ jsx("div", {
35
- className: "flex min-h-screen items-center justify-center bg-muted",
36
- children: /* @__PURE__ */ jsxs("div", {
37
- className: "flex max-w-md flex-col items-center space-y-6 text-center",
38
- children: [
39
- /* @__PURE__ */ jsx("div", {
40
- className: "relative",
41
- children: /* @__PURE__ */ jsx("div", {
42
- className: "flex h-24 w-24 items-center justify-center rounded-full bg-foreground/10",
43
- children: /* @__PURE__ */ jsx(Icon, { className: "h-12 w-12 text-foreground" })
44
- })
45
- }),
46
- children,
47
- /* @__PURE__ */ jsxs("div", {
48
- className: "space-y-2",
49
- children: [/* @__PURE__ */ jsx("h2", {
50
- className: "text-2xl font-bold text-card-foreground",
51
- children: title
52
- }), /* @__PURE__ */ jsx("p", {
53
- className: "text-muted-foreground",
54
- children: description
55
- })]
56
- }),
57
- /* @__PURE__ */ jsxs("div", {
58
- className: "flex space-x-2",
59
- children: [
60
- /* @__PURE__ */ jsx("div", { className: "h-3 w-3 animate-pulse rounded-full bg-primary [animation-delay:-0.3s]" }),
61
- /* @__PURE__ */ jsx("div", { className: "h-3 w-3 animate-pulse rounded-full bg-primary [animation-delay:-0.15s]" }),
62
- /* @__PURE__ */ jsx("div", { className: "h-3 w-3 animate-pulse rounded-full bg-primary" })
63
- ]
64
- })
65
- ]
66
- })
67
- });
68
- };
69
-
70
- //#endregion
71
14
  //#region src/utils/dev-portal-api.ts
72
15
  const DEV_PORTAL_PLACEHOLDER_API_URL = "https://dp-example.azurecontainerapps.io";
73
16
  function resolveDevPortalApiBaseUrl(configuredUrl) {
@@ -139,6 +82,99 @@ function isActiveSubscription(status) {
139
82
  return status.toLowerCase() === "active";
140
83
  }
141
84
 
85
+ //#endregion
86
+ //#region src/components/PricingCard.tsx
87
+ const PricingCard = ({ plan, mode = "public", subscriptionStatus, isSubscribed = false, onUnsubscribe, isCanceling = false, cancelDisabled = false }) => {
88
+ const limitsSummary = summarizeLimits(plan.limitsJson);
89
+ const isActive = isSubscribed || isActiveSubscription(subscriptionStatus ?? "");
90
+ return /* @__PURE__ */ jsxs("article", {
91
+ className: cn("flex w-full max-w-sm flex-col rounded-xl border bg-card/60 p-6 shadow-xs", isActive && mode === "account" && "border-primary/40 ring-1 ring-primary/20"),
92
+ "data-testid": `pricing-card-${plan.sku}`,
93
+ children: [
94
+ /* @__PURE__ */ jsxs("div", {
95
+ className: "flex items-start justify-between gap-2",
96
+ children: [/* @__PURE__ */ jsx("h2", {
97
+ className: "text-xl font-semibold",
98
+ children: plan.displayName
99
+ }), isActive && mode === "account" ? /* @__PURE__ */ jsx("span", {
100
+ className: "shrink-0 rounded-full bg-primary/10 px-3 py-1 text-xs font-medium text-primary",
101
+ "data-testid": `subscribed-badge-${plan.sku}`,
102
+ children: "Subscribed"
103
+ }) : null]
104
+ }),
105
+ /* @__PURE__ */ jsx("p", {
106
+ className: "mt-2 text-3xl font-bold",
107
+ children: formatPlanPrice(plan)
108
+ }),
109
+ /* @__PURE__ */ jsx("p", {
110
+ className: "mt-1 text-sm text-muted-foreground",
111
+ children: formatBillingPeriod(plan.billingPeriod)
112
+ }),
113
+ limitsSummary ? /* @__PURE__ */ jsx("p", {
114
+ className: "mt-4 text-sm text-muted-foreground",
115
+ children: limitsSummary
116
+ }) : null,
117
+ /* @__PURE__ */ jsx("div", {
118
+ className: "mt-6 space-y-2",
119
+ children: mode === "account" ? isActive ? /* @__PURE__ */ jsxs(Fragment, { children: [
120
+ /* @__PURE__ */ jsx(Button, {
121
+ variant: "secondary",
122
+ className: "w-full",
123
+ disabled: true,
124
+ children: "Current plan"
125
+ }),
126
+ onUnsubscribe ? /* @__PURE__ */ jsxs(AlertDialog, { children: [/* @__PURE__ */ jsx(AlertDialogTrigger, {
127
+ asChild: true,
128
+ children: /* @__PURE__ */ jsx(Button, {
129
+ variant: "outline",
130
+ className: "w-full",
131
+ disabled: cancelDisabled || isCanceling,
132
+ "data-testid": `unsubscribe-${plan.sku}`,
133
+ children: isCanceling ? "Canceling…" : "Unsubscribe"
134
+ })
135
+ }), /* @__PURE__ */ jsxs(AlertDialogContent, { children: [/* @__PURE__ */ jsxs(AlertDialogHeader, { children: [/* @__PURE__ */ jsxs(AlertDialogTitle, { children: [
136
+ "Unsubscribe from ",
137
+ plan.displayName,
138
+ "?"
139
+ ] }), /* @__PURE__ */ jsx(AlertDialogDescription, { children: "Your API access for this plan will be revoked. You can subscribe again later." })] }), /* @__PURE__ */ jsxs(AlertDialogFooter, { children: [/* @__PURE__ */ jsx(AlertDialogCancel, { children: "Keep subscription" }), /* @__PURE__ */ jsx(AlertDialogAction, {
140
+ onClick: () => onUnsubscribe(plan.sku),
141
+ disabled: isCanceling,
142
+ children: "Unsubscribe"
143
+ })] })] })] }) : null,
144
+ cancelDisabled ? /* @__PURE__ */ jsx("p", {
145
+ className: "text-center text-xs text-muted-foreground",
146
+ children: "Unsubscribe is available after the portal is published."
147
+ }) : null
148
+ ] }) : /* @__PURE__ */ jsx(Button, {
149
+ variant: "default",
150
+ className: "w-full",
151
+ asChild: true,
152
+ children: /* @__PURE__ */ jsx(Link, {
153
+ to: `/checkout?planSku=${encodeURIComponent(plan.sku)}`,
154
+ children: plan.isFree ? "Get started" : "Subscribe"
155
+ })
156
+ }) : isActive ? /* @__PURE__ */ jsx(Button, {
157
+ variant: "secondary",
158
+ className: "w-full",
159
+ asChild: true,
160
+ children: /* @__PURE__ */ jsx(Link, {
161
+ to: "/account/subscriptions",
162
+ children: "Manage subscription"
163
+ })
164
+ }) : /* @__PURE__ */ jsx(Button, {
165
+ variant: "default",
166
+ className: "w-full",
167
+ asChild: true,
168
+ children: /* @__PURE__ */ jsx(Link, {
169
+ to: `/checkout?planSku=${encodeURIComponent(plan.sku)}`,
170
+ children: plan.isFree ? "Get started" : "Subscribe"
171
+ })
172
+ })
173
+ })
174
+ ]
175
+ });
176
+ };
177
+
142
178
  //#endregion
143
179
  //#region src/hooks/useDevPortalApiBaseUrl.ts
144
180
  function useDevPortalApiBaseUrl() {
@@ -153,8 +189,221 @@ function useDevPortalPreviewMode() {
153
189
  return isPlaceholderDevPortalApiUrl(apiBaseUrl);
154
190
  }
155
191
 
192
+ //#endregion
193
+ //#region src/hooks/useCancelSubscription.ts
194
+ function useCancelSubscription() {
195
+ const apiBaseUrl = useDevPortalApiBaseUrl();
196
+ const queryClient = useQueryClient();
197
+ return useMutation({
198
+ mutationFn: async (planSku) => {
199
+ if (!apiBaseUrl) throw new Error("Dev portal API is not configured.");
200
+ const response = await fetch(`${apiBaseUrl}/api/v1/subscriptions/cancel`, {
201
+ method: "POST",
202
+ headers: {
203
+ Accept: "application/json",
204
+ "Content-Type": "application/json"
205
+ },
206
+ credentials: "include",
207
+ body: JSON.stringify({ planSku })
208
+ });
209
+ if (!response.ok) {
210
+ const detail = await response.text();
211
+ throw new Error(detail || "Failed to cancel subscription.");
212
+ }
213
+ return await response.json();
214
+ },
215
+ onSuccess: async () => {
216
+ await queryClient.invalidateQueries({ queryKey: ["dev-portal-me", apiBaseUrl] });
217
+ }
218
+ });
219
+ }
220
+
221
+ //#endregion
222
+ //#region src/hooks/useMySubscriptions.ts
223
+ function useMySubscriptions() {
224
+ const auth = useAuth();
225
+ const apiBaseUrl = useDevPortalApiBaseUrl();
226
+ const isPreview = useDevPortalPreviewMode();
227
+ return useQuery({
228
+ queryKey: ["dev-portal-me", apiBaseUrl],
229
+ enabled: auth.isAuthenticated && !isPreview && Boolean(apiBaseUrl),
230
+ queryFn: async () => {
231
+ const response = await fetch(`${apiBaseUrl}/api/v1/auth/me`, {
232
+ method: "GET",
233
+ headers: { Accept: "application/json" },
234
+ credentials: "include"
235
+ });
236
+ if (!response.ok) throw new Error("Failed to load subscription status.");
237
+ return await response.json();
238
+ }
239
+ });
240
+ }
241
+
242
+ //#endregion
243
+ //#region src/hooks/usePlans.ts
244
+ function usePlans() {
245
+ const apiBaseUrl = useDevPortalApiBaseUrl();
246
+ const isPreview = useDevPortalPreviewMode();
247
+ return useQuery({
248
+ queryKey: [
249
+ "dev-portal-plans",
250
+ apiBaseUrl,
251
+ isPreview
252
+ ],
253
+ queryFn: async () => {
254
+ if (isPreview) return parseMockPlans();
255
+ if (!apiBaseUrl) return { plans: [] };
256
+ const response = await fetch(`${apiBaseUrl}/api/v1/plans`, { headers: { Accept: "application/json" } });
257
+ if (!response.ok) throw new Error("Failed to load plans.");
258
+ return await response.json();
259
+ }
260
+ });
261
+ }
262
+
263
+ //#endregion
264
+ //#region src/pages/AccountPlansPage.tsx
265
+ const AccountPlansPage = () => {
266
+ const isPreview = useDevPortalPreviewMode();
267
+ const { data: catalog, isLoading, isError } = usePlans();
268
+ const { data: me } = useMySubscriptions();
269
+ const cancelMutation = useCancelSubscription();
270
+ const [cancelingSku, setCancelingSku] = useState(null);
271
+ const subscriptionBySku = new Map((me?.subscriptions ?? []).map((sub) => [sub.planSku, sub]));
272
+ const handleUnsubscribe = async (planSku) => {
273
+ setCancelingSku(planSku);
274
+ try {
275
+ await cancelMutation.mutateAsync(planSku);
276
+ } finally {
277
+ setCancelingSku(null);
278
+ }
279
+ };
280
+ return /* @__PURE__ */ jsxs("div", {
281
+ className: "mx-auto max-w-screen-xl px-4 pb-16 pt-(--padding-content-top) lg:px-8",
282
+ children: [
283
+ /* @__PURE__ */ jsx(Head, { children: /* @__PURE__ */ jsx("title", { children: "Plans & billing" }) }),
284
+ /* @__PURE__ */ jsxs("div", {
285
+ className: "mb-8 flex items-start gap-4",
286
+ children: [/* @__PURE__ */ jsx("div", {
287
+ className: "flex size-12 shrink-0 items-center justify-center rounded-xl bg-primary/10 text-primary",
288
+ children: /* @__PURE__ */ jsx(CreditCardIcon, { size: 24 })
289
+ }), /* @__PURE__ */ jsxs("div", {
290
+ className: "space-y-1",
291
+ children: [/* @__PURE__ */ jsx("h1", {
292
+ className: "text-3xl font-semibold tracking-tight",
293
+ children: "Plans & billing"
294
+ }), /* @__PURE__ */ jsx("p", {
295
+ className: "max-w-2xl text-muted-foreground",
296
+ children: "Compare plans, manage your subscriptions, and change tiers."
297
+ })]
298
+ })]
299
+ }),
300
+ isPreview ? /* @__PURE__ */ jsx("div", {
301
+ className: "mb-8 rounded-xl border border-dashed bg-muted/20 p-6 text-center text-sm text-muted-foreground",
302
+ children: "Local preview uses mock plans. Subscribe and unsubscribe activate after the portal is published."
303
+ }) : null,
304
+ cancelMutation.isError ? /* @__PURE__ */ jsx("div", {
305
+ className: "mb-6 rounded-xl border border-destructive/30 bg-destructive/10 p-4 text-sm text-destructive",
306
+ children: cancelMutation.error instanceof Error ? cancelMutation.error.message : "Failed to cancel subscription."
307
+ }) : null,
308
+ isLoading ? /* @__PURE__ */ jsx("p", {
309
+ className: "text-muted-foreground",
310
+ children: "Loading plans…"
311
+ }) : null,
312
+ isError ? /* @__PURE__ */ jsx("p", {
313
+ className: "text-destructive",
314
+ children: "Unable to load plans."
315
+ }) : null,
316
+ catalog && catalog.plans.length > 0 ? /* @__PURE__ */ jsx("div", {
317
+ className: "grid grid-cols-1 justify-center gap-6 sm:grid-cols-[repeat(auto-fit,minmax(280px,max-content))]",
318
+ children: catalog.plans.map((plan) => {
319
+ const subscription = subscriptionBySku.get(plan.sku);
320
+ const isSubscribed = isActiveSubscription(subscription?.status ?? "");
321
+ return /* @__PURE__ */ jsx(PricingCard, {
322
+ plan,
323
+ mode: "account",
324
+ subscriptionStatus: subscription?.status,
325
+ isSubscribed,
326
+ onUnsubscribe: isPreview ? void 0 : handleUnsubscribe,
327
+ isCanceling: cancelingSku === plan.sku,
328
+ cancelDisabled: isPreview
329
+ }, plan.sku);
330
+ })
331
+ }) : !isLoading && !isError ? /* @__PURE__ */ jsx("div", {
332
+ className: "rounded-xl border border-dashed p-8 text-center text-sm text-muted-foreground",
333
+ children: "No plans are available yet. Sync plans from your APItoGo manifest or platform gateway."
334
+ }) : null
335
+ ]
336
+ });
337
+ };
338
+
339
+ //#endregion
340
+ //#region src/DevPortalBillingContext.tsx
341
+ const DevPortalBillingContext = createContext({});
342
+ const DevPortalBillingProvider = ({ options, children }) => /* @__PURE__ */ jsx(DevPortalBillingContext.Provider, {
343
+ value: options,
344
+ children
345
+ });
346
+ const useDevPortalBillingConfig = () => useContext(DevPortalBillingContext);
347
+
348
+ //#endregion
349
+ //#region src/DevPortalBillingWrapper.tsx
350
+ const DevPortalBillingWrapper = ({ options = {} }) => /* @__PURE__ */ jsx(DevPortalBillingProvider, {
351
+ options,
352
+ children: /* @__PURE__ */ jsx(ClientOnly, { children: /* @__PURE__ */ jsx(Outlet, {}) })
353
+ });
354
+
355
+ //#endregion
356
+ //#region src/components/RedirectPage.tsx
357
+ const RedirectPage = ({ icon: Icon, title, description, url, children }) => {
358
+ useEffect(() => {
359
+ if (url) window.location.assign(url);
360
+ }, [url]);
361
+ return /* @__PURE__ */ jsx("div", {
362
+ className: "flex min-h-screen items-center justify-center bg-muted",
363
+ children: /* @__PURE__ */ jsxs("div", {
364
+ className: "flex max-w-md flex-col items-center space-y-6 text-center",
365
+ children: [
366
+ /* @__PURE__ */ jsx("div", {
367
+ className: "relative",
368
+ children: /* @__PURE__ */ jsx("div", {
369
+ className: "flex h-24 w-24 items-center justify-center rounded-full bg-foreground/10",
370
+ children: /* @__PURE__ */ jsx(Icon, { className: "h-12 w-12 text-foreground" })
371
+ })
372
+ }),
373
+ children,
374
+ /* @__PURE__ */ jsxs("div", {
375
+ className: "space-y-2",
376
+ children: [/* @__PURE__ */ jsx("h2", {
377
+ className: "text-2xl font-bold text-card-foreground",
378
+ children: title
379
+ }), /* @__PURE__ */ jsx("p", {
380
+ className: "text-muted-foreground",
381
+ children: description
382
+ })]
383
+ }),
384
+ /* @__PURE__ */ jsxs("div", {
385
+ className: "flex space-x-2",
386
+ children: [
387
+ /* @__PURE__ */ jsx("div", { className: "h-3 w-3 animate-pulse rounded-full bg-primary [animation-delay:-0.3s]" }),
388
+ /* @__PURE__ */ jsx("div", { className: "h-3 w-3 animate-pulse rounded-full bg-primary [animation-delay:-0.15s]" }),
389
+ /* @__PURE__ */ jsx("div", { className: "h-3 w-3 animate-pulse rounded-full bg-primary" })
390
+ ]
391
+ })
392
+ ]
393
+ })
394
+ });
395
+ };
396
+
397
+ //#endregion
398
+ //#region src/paths.ts
399
+ const PRICING_PATH = "/pricing";
400
+ const CHECKOUT_PATH = "/checkout";
401
+ const SUBSCRIPTIONS_PATH = "/account/subscriptions";
402
+ const CREDENTIALS_PATH = "/account/credentials";
403
+
156
404
  //#endregion
157
405
  //#region src/pages/CheckoutPage.tsx
406
+ const ACCOUNT_PLANS_PATH = "/account/plans";
158
407
  const CheckoutPage = () => {
159
408
  const [searchParams] = useSearchParams();
160
409
  const planSku = searchParams.get("planSku") ?? "";
@@ -162,6 +411,7 @@ const CheckoutPage = () => {
162
411
  const auth = useAuth();
163
412
  const apiBaseUrl = useDevPortalApiBaseUrl();
164
413
  const isPreview = useDevPortalPreviewMode();
414
+ const plansPath = auth.isAuthenticated ? ACCOUNT_PLANS_PATH : PRICING_PATH;
165
415
  const checkoutQuery = useQuery({
166
416
  queryKey: [
167
417
  "dev-portal-checkout",
@@ -198,9 +448,9 @@ const CheckoutPage = () => {
198
448
  "Missing plan SKU.",
199
449
  " ",
200
450
  /* @__PURE__ */ jsx(Link, {
201
- to: "/pricing",
451
+ to: plansPath,
202
452
  className: "text-primary underline",
203
- children: "Back to pricing"
453
+ children: "Back to plans"
204
454
  })
205
455
  ]
206
456
  });
@@ -219,8 +469,8 @@ const CheckoutPage = () => {
219
469
  asChild: true,
220
470
  variant: "outline",
221
471
  children: /* @__PURE__ */ jsx(Link, {
222
- to: "/pricing",
223
- children: "Back to pricing"
472
+ to: plansPath,
473
+ children: "Back to plans"
224
474
  })
225
475
  })
226
476
  ]
@@ -241,8 +491,8 @@ const CheckoutPage = () => {
241
491
  size: "xs",
242
492
  asChild: true,
243
493
  children: /* @__PURE__ */ jsx(Link, {
244
- to: "/pricing",
245
- children: "Back to pricing"
494
+ to: plansPath,
495
+ children: "Back to plans"
246
496
  })
247
497
  }) })]
248
498
  }) : null
@@ -272,27 +522,6 @@ function useCredentials() {
272
522
  });
273
523
  }
274
524
 
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
525
  //#endregion
297
526
  //#region src/pages/CredentialsPage.tsx
298
527
  const CredentialsPage = () => {
@@ -338,7 +567,7 @@ const CredentialsPage = () => {
338
567
  className: "mt-4",
339
568
  asChild: true,
340
569
  children: /* @__PURE__ */ jsx(Link, {
341
- to: "/pricing",
570
+ to: "/account/plans",
342
571
  children: "View plans"
343
572
  })
344
573
  })]
@@ -356,7 +585,7 @@ const CredentialsPage = () => {
356
585
  className: "mt-4",
357
586
  asChild: true,
358
587
  children: /* @__PURE__ */ jsx(Link, {
359
- to: "/pricing",
588
+ to: "/account/plans",
360
589
  children: "View plans"
361
590
  })
362
591
  })]
@@ -447,75 +676,6 @@ const CredentialsPage = () => {
447
676
  });
448
677
  };
449
678
 
450
- //#endregion
451
- //#region src/components/PricingCard.tsx
452
- const PricingCard = ({ plan, isSubscribed = false }) => {
453
- const limitsSummary = summarizeLimits(plan.limitsJson);
454
- return /* @__PURE__ */ jsxs("article", {
455
- className: cn("flex w-full max-w-sm flex-col rounded-xl border bg-card/60 p-6 shadow-xs"),
456
- "data-testid": `pricing-card-${plan.sku}`,
457
- children: [
458
- /* @__PURE__ */ jsx("h2", {
459
- className: "text-xl font-semibold",
460
- children: plan.displayName
461
- }),
462
- /* @__PURE__ */ jsx("p", {
463
- className: "mt-2 text-3xl font-bold",
464
- children: formatPlanPrice(plan)
465
- }),
466
- /* @__PURE__ */ jsx("p", {
467
- className: "mt-1 text-sm text-muted-foreground",
468
- children: formatBillingPeriod(plan.billingPeriod)
469
- }),
470
- limitsSummary ? /* @__PURE__ */ jsx("p", {
471
- className: "mt-4 text-sm text-muted-foreground",
472
- children: limitsSummary
473
- }) : null,
474
- /* @__PURE__ */ jsx("div", {
475
- className: "mt-6",
476
- children: isSubscribed ? /* @__PURE__ */ jsx(Button, {
477
- variant: "secondary",
478
- className: "w-full",
479
- asChild: true,
480
- children: /* @__PURE__ */ jsx(Link, {
481
- to: "/account/subscriptions",
482
- children: "Manage subscription"
483
- })
484
- }) : /* @__PURE__ */ jsx(Button, {
485
- variant: "default",
486
- className: "w-full",
487
- asChild: true,
488
- children: /* @__PURE__ */ jsx(Link, {
489
- to: `/checkout?planSku=${encodeURIComponent(plan.sku)}`,
490
- children: plan.isFree ? "Get started" : "Subscribe"
491
- })
492
- })
493
- })
494
- ]
495
- });
496
- };
497
-
498
- //#endregion
499
- //#region src/hooks/useMySubscriptions.ts
500
- function useMySubscriptions() {
501
- const auth = useAuth();
502
- const apiBaseUrl = useDevPortalApiBaseUrl();
503
- const isPreview = useDevPortalPreviewMode();
504
- return useQuery({
505
- queryKey: ["dev-portal-me", apiBaseUrl],
506
- enabled: auth.isAuthenticated && !isPreview && Boolean(apiBaseUrl),
507
- queryFn: async () => {
508
- const response = await fetch(`${apiBaseUrl}/api/v1/auth/me`, {
509
- method: "GET",
510
- headers: { Accept: "application/json" },
511
- credentials: "include"
512
- });
513
- if (!response.ok) throw new Error("Failed to load subscription status.");
514
- return await response.json();
515
- }
516
- });
517
- }
518
-
519
679
  //#endregion
520
680
  //#region src/pages/PricingPage.tsx
521
681
  const PricingPage = () => {
@@ -524,6 +684,10 @@ const PricingPage = () => {
524
684
  const isPreview = useDevPortalPreviewMode();
525
685
  const { data: catalog, isLoading, isError } = usePlans();
526
686
  const { data: me } = useMySubscriptions();
687
+ if (auth.isAuthenticated) return /* @__PURE__ */ jsx(Navigate, {
688
+ to: "/account/plans",
689
+ replace: true
690
+ });
527
691
  const activeSkus = new Set((me?.subscriptions ?? []).filter((sub) => isActiveSubscription(sub.status)).map((sub) => sub.planSku));
528
692
  const title = pricing?.title ?? "Pricing";
529
693
  const subtitle = pricing?.subtitle ?? "Compare plans and subscribe when you are ready to integrate.";
@@ -570,34 +734,17 @@ const PricingPage = () => {
570
734
  className: "grid grid-cols-1 justify-center gap-6 sm:grid-cols-[repeat(auto-fit,minmax(280px,max-content))]",
571
735
  children: catalog.plans.map((plan) => /* @__PURE__ */ jsx(PricingCard, {
572
736
  plan,
737
+ mode: "public",
573
738
  isSubscribed: activeSkus.has(plan.sku)
574
739
  }, plan.sku))
575
740
  }) : !isLoading && !isError ? /* @__PURE__ */ jsx("div", {
576
741
  className: "mx-auto max-w-lg rounded-xl border border-dashed p-8 text-center text-sm text-muted-foreground",
577
742
  children: "No plans are available yet. Sync plans from your APItoGo manifest or platform gateway."
578
- }) : null,
579
- auth.isAuthenticated ? /* @__PURE__ */ jsx("div", {
580
- className: "mt-10 text-center",
581
- children: /* @__PURE__ */ jsx(Button$1, {
582
- variant: "outline",
583
- asChild: true,
584
- children: /* @__PURE__ */ jsx(Link, {
585
- to: "/account/plans",
586
- children: "Manage billing"
587
- })
588
- })
589
743
  }) : null
590
744
  ]
591
745
  });
592
746
  };
593
747
 
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
-
601
748
  //#endregion
602
749
  //#region src/pages/SubscriptionsPage.tsx
603
750
  const SubscriptionsPage = () => {
@@ -648,7 +795,7 @@ const SubscriptionsPage = () => {
648
795
  className: "mt-4",
649
796
  asChild: true,
650
797
  children: /* @__PURE__ */ jsx(Link, {
651
- to: "/pricing",
798
+ to: "/account/plans",
652
799
  children: "View plans"
653
800
  })
654
801
  })]
@@ -694,7 +841,7 @@ const SubscriptionsPage = () => {
694
841
  variant: "outline",
695
842
  asChild: true,
696
843
  children: /* @__PURE__ */ jsx(Link, {
697
- to: "/pricing",
844
+ to: "/account/plans",
698
845
  children: "Change plan"
699
846
  })
700
847
  }),
@@ -725,7 +872,8 @@ const SubscriptionsPage = () => {
725
872
  const devPortalBillingPlugin = createPlugin((options = {}) => ({
726
873
  transformConfig: ({ config, merge }) => merge({ header: { navigation: [...config.header?.navigation ?? [], {
727
874
  label: "Pricing",
728
- to: PRICING_PATH
875
+ to: PRICING_PATH,
876
+ display: "anon"
729
877
  }] } }),
730
878
  getProfileMenuItems: () => [{
731
879
  label: "My Subscriptions",
@@ -774,4 +922,4 @@ const devPortalBillingPlugin = createPlugin((options = {}) => ({
774
922
  }));
775
923
 
776
924
  //#endregion
777
- export { CHECKOUT_PATH, CREDENTIALS_PATH, PRICING_PATH, SUBSCRIPTIONS_PATH, devPortalBillingPlugin };
925
+ export { AccountPlansPage, 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.39",
3
+ "version": "0.1.40",
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.39"
49
+ "@lukoweb/apitogo": "0.1.40"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "react": ">=19.2.0",