@lukoweb/apitogo-plugin-dev-portal-billing 0.1.120 → 0.1.127

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
@@ -29,6 +29,8 @@ type DevPortalBillingConfig = {
29
29
  sku: string;
30
30
  displayName: string;
31
31
  priceAmount?: number;
32
+ pricingModel?: string;
33
+ priceLabel?: string;
32
34
  priceCurrency?: string;
33
35
  billingPeriod?: string;
34
36
  limits?: Record<string, unknown>;
@@ -79,6 +81,7 @@ declare const ApiKeysPanel: ({
79
81
  declare const PRICING_PATH = "/pricing";
80
82
  declare const ACCOUNT_PLANS_PATH = "/account/plans";
81
83
  declare const CHECKOUT_PATH = "/checkout";
84
+ declare const CONTACT_SALES_PATH = "/contact-sales";
82
85
  declare const SUBSCRIPTIONS_PATH = "/account/subscriptions";
83
86
  declare const CREDENTIALS_PATH = "/account/credentials";
84
87
  declare const USAGE_PATH = "/account/usage";
@@ -204,4 +207,4 @@ declare function useCreateApiKey(): _tanstack_react_query0.UseMutationResult<Dev
204
207
  }, unknown>;
205
208
  declare function useRevokeApiKey(): _tanstack_react_query0.UseMutationResult<void, Error, string, unknown>;
206
209
  //#endregion
207
- export { ACCOUNT_PLANS_PATH, API_KEY_CAP_REACHED, AccountPlansPage, ApiKeysPanel, CHECKOUT_PATH, CREDENTIALS_PATH, OverviewDashboardPage, PRICING_PATH, SUBSCRIPTIONS_PATH, USAGE_PATH, devPortalBillingPlugin, useApiKeys, useCreateApiKey, useRevokeApiKey };
210
+ export { ACCOUNT_PLANS_PATH, API_KEY_CAP_REACHED, AccountPlansPage, ApiKeysPanel, CHECKOUT_PATH, CONTACT_SALES_PATH, CREDENTIALS_PATH, OverviewDashboardPage, PRICING_PATH, SUBSCRIPTIONS_PATH, USAGE_PATH, devPortalBillingPlugin, useApiKeys, useCreateApiKey, useRevokeApiKey };
package/dist/index.mjs CHANGED
@@ -16,10 +16,24 @@ import { Input } from "@lukoweb/apitogo/ui/Input.js";
16
16
  import { Secret } from "@lukoweb/apitogo/ui/Secret.js";
17
17
  import { AccountLayout } from "@lukoweb/apitogo-module-user-panel";
18
18
  import { createPlugin } from "@lukoweb/apitogo/plugins";
19
- import { Alert as Alert$1, AlertAction, AlertDescription } from "@lukoweb/apitogo/ui/Alert";
19
+ import { Alert as Alert$1, AlertAction, AlertDescription, AlertTitle as AlertTitle$1 } from "@lukoweb/apitogo/ui/Alert";
20
20
  import { Button as Button$2 } from "@lukoweb/apitogo/ui/Button";
21
+ import { Input as Input$1 } from "@lukoweb/apitogo/ui/Input";
22
+ import { Label } from "@lukoweb/apitogo/ui/Label";
23
+ import { Textarea } from "@lukoweb/apitogo/ui/Textarea";
21
24
  import { Value } from "@lukoweb/apitogo/ui/Value.js";
22
25
 
26
+ //#region src/paths.ts
27
+ const PRICING_PATH = "/pricing";
28
+ const ACCOUNT_PLANS_PATH = "/account/plans";
29
+ const CHECKOUT_PATH = "/checkout";
30
+ const CONTACT_SALES_PATH = "/contact-sales";
31
+ const SUBSCRIPTIONS_PATH = "/account/subscriptions";
32
+ const CREDENTIALS_PATH = "/account/credentials";
33
+ const USAGE_PATH = "/account/usage";
34
+ const LOGIN_NAV_PATH = "/signin";
35
+
36
+ //#endregion
23
37
  //#region src/utils/dev-portal-api.ts
24
38
  const DEV_PORTAL_PLACEHOLDER_API_URL = "https://dp-example.azurecontainerapps.io";
25
39
  function resolveDevPortalApiBaseUrl(configuredUrl) {
@@ -41,6 +55,8 @@ function mapPreviewPlans(plans) {
41
55
  sku: plan.sku,
42
56
  displayName: plan.displayName,
43
57
  priceAmount: plan.priceAmount,
58
+ pricingModel: plan.pricingModel,
59
+ priceLabel: plan.priceLabel,
44
60
  priceCurrency: plan.priceCurrency,
45
61
  billingPeriod: plan.billingPeriod,
46
62
  limitsJson: resolveLimitsJson(plan),
@@ -57,6 +73,8 @@ function parseManifestPlans(configPlans) {
57
73
  sku: plan.sku,
58
74
  displayName: plan.displayName,
59
75
  priceAmount: plan.priceAmount ?? 0,
76
+ pricingModel: plan.pricingModel,
77
+ priceLabel: plan.priceLabel,
60
78
  priceCurrency: plan.priceCurrency ?? "usd",
61
79
  billingPeriod: plan.billingPeriod ?? "monthly",
62
80
  limits: plan.limits,
@@ -72,6 +90,8 @@ function parseManifestPlans(configPlans) {
72
90
  sku: plan.sku,
73
91
  displayName: plan.displayName,
74
92
  priceAmount: plan.priceAmount ?? 0,
93
+ pricingModel: plan.pricingModel,
94
+ priceLabel: plan.priceLabel,
75
95
  priceCurrency: plan.priceCurrency ?? "usd",
76
96
  billingPeriod: plan.billingPeriod ?? "monthly",
77
97
  limits: plan.limits,
@@ -86,8 +106,15 @@ function formatActionKey(actionKey) {
86
106
  if (separatorIndex <= 0) return actionKey;
87
107
  return `${actionKey.slice(0, separatorIndex).toUpperCase()} ${actionKey.slice(separatorIndex + 1)}`;
88
108
  }
109
+ /** Custom-priced plans go to a contact form; a negative price is the legacy marker for the same. */
110
+ function isPlanCustomPriced(plan) {
111
+ return plan.pricingModel?.toLowerCase() === "custom" || plan.priceAmount < 0;
112
+ }
113
+ function formatCustomPriceLabel(plan) {
114
+ return plan.priceLabel?.trim() || "Custom";
115
+ }
89
116
  function formatPlanPrice(plan) {
90
- if (plan.priceAmount < 0) return "Custom";
117
+ if (isPlanCustomPriced(plan)) return formatCustomPriceLabel(plan);
91
118
  if (plan.priceAmount <= 0) return "Free";
92
119
  const amount = new Intl.NumberFormat(void 0, {
93
120
  style: "currency",
@@ -100,7 +127,7 @@ function formatPlanPrice(plan) {
100
127
  return amount;
101
128
  }
102
129
  function isPlanFree(plan) {
103
- return plan.priceAmount === 0;
130
+ return !isPlanCustomPriced(plan) && plan.priceAmount <= 0;
104
131
  }
105
132
  function formatBillingPeriod(period) {
106
133
  const normalized = period.toLowerCase();
@@ -179,16 +206,22 @@ function parseMonthlyQuotaCalls(limitsJson) {
179
206
  //#endregion
180
207
  //#region src/components/PricingCard.tsx
181
208
  const publicCtaLabel = (plan) => {
182
- if (plan.priceAmount < 0) return "Contact sales";
209
+ if (isPlanCustomPriced(plan)) return "Contact sales";
183
210
  if (isPlanFree(plan)) return "Start for free";
184
211
  return "Start free trial";
185
212
  };
213
+ const accountCtaLabel = (plan) => {
214
+ if (isPlanCustomPriced(plan)) return "Contact sales";
215
+ return isPlanFree(plan) ? "Get started" : "Subscribe";
216
+ };
217
+ /** Custom-priced plans skip checkout and open the contact-sales form. */
218
+ const planCtaPath = (plan) => `${isPlanCustomPriced(plan) ? CONTACT_SALES_PATH : CHECKOUT_PATH}?planSku=${encodeURIComponent(plan.sku)}`;
186
219
  const PricingCard = ({ plan, mode = "public", subscriptionStatus, isSubscribed = false, onUnsubscribe, isCanceling = false, cancelDisabled = false, featured = false }) => {
187
220
  const isActive = isSubscribed || isActiveSubscription(subscriptionStatus ?? "");
188
221
  const features = listPlanFeatureLines(plan);
189
222
  const priceLabel = formatPlanPrice(plan);
190
- const isCustom = plan.priceAmount < 0;
191
- const displayPrice = isCustom ? "Custom" : isPlanFree(plan) ? "$0" : priceLabel.replace(/\/mo$/, "").replace(/\/yr$/, "") || priceLabel;
223
+ const isCustom = isPlanCustomPriced(plan);
224
+ const displayPrice = isCustom ? formatCustomPriceLabel(plan) : isPlanFree(plan) ? "$0" : priceLabel.replace(/\/mo$/, "").replace(/\/yr$/, "") || priceLabel;
192
225
  return /* @__PURE__ */ jsxs("article", {
193
226
  className: cn("flex h-full w-full flex-col rounded-xl border border-border bg-card p-7", featured && "border-primary/50 shadow-[0_10px_28px_rgba(13,148,136,0.28)] ring-1 ring-primary/25", isActive && mode === "account" && "border-primary/40 ring-1 ring-primary/20"),
194
227
  "data-testid": `pricing-card-${plan.sku}`,
@@ -255,15 +288,15 @@ const PricingCard = ({ plan, mode = "public", subscriptionStatus, isSubscribed =
255
288
  className: "w-full",
256
289
  asChild: true,
257
290
  children: /* @__PURE__ */ jsx(Link$1, {
258
- to: `/checkout?planSku=${encodeURIComponent(plan.sku)}`,
259
- children: isPlanFree(plan) ? "Get started" : "Subscribe"
291
+ to: planCtaPath(plan),
292
+ children: accountCtaLabel(plan)
260
293
  })
261
294
  }) : /* @__PURE__ */ jsx(Button, {
262
295
  variant: featured ? "default" : "outline",
263
296
  className: "w-full",
264
297
  asChild: true,
265
298
  children: /* @__PURE__ */ jsx(Link$1, {
266
- to: `/checkout?planSku=${encodeURIComponent(plan.sku)}`,
299
+ to: planCtaPath(plan),
267
300
  children: publicCtaLabel(plan)
268
301
  })
269
302
  })
@@ -616,7 +649,7 @@ function useCreateApiKey() {
616
649
  })
617
650
  });
618
651
  if (response.status === 409) throw new Error(API_KEY_CAP_REACHED);
619
- if (!response.ok) throw new Error(await readProblemDetail(response) ?? "Failed to create API key.");
652
+ if (!response.ok) throw new Error(await readProblemDetail$1(response) ?? "Failed to create API key.");
620
653
  return await response.json();
621
654
  },
622
655
  onSuccess: async () => {
@@ -635,14 +668,14 @@ function useRevokeApiKey() {
635
668
  headers: { Accept: "application/json" },
636
669
  credentials: "include"
637
670
  });
638
- if (!response.ok && response.status !== 204) throw new Error(await readProblemDetail(response) ?? "Failed to revoke API key.");
671
+ if (!response.ok && response.status !== 204) throw new Error(await readProblemDetail$1(response) ?? "Failed to revoke API key.");
639
672
  },
640
673
  onSuccess: async () => {
641
674
  await queryClient.invalidateQueries({ queryKey: apiKeysQueryKey(apiBaseUrl) });
642
675
  }
643
676
  });
644
677
  }
645
- async function readProblemDetail(response) {
678
+ async function readProblemDetail$1(response) {
646
679
  try {
647
680
  return (await response.json()).detail?.trim() || null;
648
681
  } catch {
@@ -809,16 +842,6 @@ function useUsageRecent(limit = 20) {
809
842
  return liveQuery;
810
843
  }
811
844
 
812
- //#endregion
813
- //#region src/paths.ts
814
- const PRICING_PATH = "/pricing";
815
- const ACCOUNT_PLANS_PATH = "/account/plans";
816
- const CHECKOUT_PATH = "/checkout";
817
- const SUBSCRIPTIONS_PATH = "/account/subscriptions";
818
- const CREDENTIALS_PATH = "/account/credentials";
819
- const USAGE_PATH = "/account/usage";
820
- const LOGIN_NAV_PATH = "/signin";
821
-
822
845
  //#endregion
823
846
  //#region src/pages/OverviewDashboardPage.tsx
824
847
  function StatCard({ label, value, sub, subClassName }) {
@@ -1382,6 +1405,246 @@ const CheckoutPage = () => {
1382
1405
  });
1383
1406
  };
1384
1407
 
1408
+ //#endregion
1409
+ //#region src/hooks/usePlanInquiry.ts
1410
+ const PLAN_INQUIRY_RATE_LIMITED = "Too many messages were sent from your network. Please try again later.";
1411
+ function useSubmitPlanInquiry() {
1412
+ const apiBaseUrl = useDevPortalApiBaseUrl();
1413
+ return useMutation({ mutationFn: async ({ planSku, inquiry }) => {
1414
+ if (!apiBaseUrl) throw new Error("Dev portal API is not configured.");
1415
+ const response = await fetch(`${apiBaseUrl}/api/v1/plans/${encodeURIComponent(planSku)}/inquiries`, {
1416
+ method: "POST",
1417
+ headers: {
1418
+ Accept: "application/json",
1419
+ "Content-Type": "application/json"
1420
+ },
1421
+ credentials: "include",
1422
+ body: JSON.stringify(inquiry)
1423
+ });
1424
+ if (response.status === 429) throw new Error(PLAN_INQUIRY_RATE_LIMITED);
1425
+ if (!response.ok) throw new Error(await readProblemDetail(response) ?? "Your message could not be sent. Please try again later.");
1426
+ } });
1427
+ }
1428
+ async function readProblemDetail(response) {
1429
+ try {
1430
+ const body = await response.json();
1431
+ return (body.errors ? Object.values(body.errors).flat()[0] : void 0) ?? body.detail ?? body.title ?? null;
1432
+ } catch {
1433
+ return null;
1434
+ }
1435
+ }
1436
+
1437
+ //#endregion
1438
+ //#region src/pages/ContactSalesPage.tsx
1439
+ const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1440
+ const validate = (form) => ({
1441
+ ...form.name.trim() ? {} : { name: "Enter your name." },
1442
+ ...EMAIL_PATTERN.test(form.email.trim()) ? {} : { email: "Enter a valid email address." },
1443
+ ...form.message.trim() ? {} : { message: "Tell us what you need." }
1444
+ });
1445
+ const ContactSalesPage = () => {
1446
+ const [searchParams] = useSearchParams();
1447
+ const planSku = searchParams.get("planSku") ?? "";
1448
+ const auth = useAuth();
1449
+ const isPreview = useDevPortalPreviewMode();
1450
+ const isDemoSession = useIsDemoSession();
1451
+ const plans = usePlans();
1452
+ const submit = useSubmitPlanInquiry();
1453
+ const [form, setForm] = useState(() => ({
1454
+ name: auth.profile?.name ?? "",
1455
+ email: auth.profile?.email ?? "",
1456
+ company: "",
1457
+ message: "",
1458
+ website: ""
1459
+ }));
1460
+ const [errors, setErrors] = useState({});
1461
+ const plan = plans.data?.plans.find((p) => p.sku === planSku);
1462
+ const isSimulated = isPreview || isDemoSession;
1463
+ if (!planSku || plans.isSuccess && (!plan || !isPlanCustomPriced(plan))) return /* @__PURE__ */ jsxs("div", {
1464
+ className: "mx-auto max-w-lg px-4 py-16 text-center text-muted-foreground",
1465
+ children: [
1466
+ "This plan is not available for sales inquiries.",
1467
+ " ",
1468
+ /* @__PURE__ */ jsx(Link$1, {
1469
+ to: PRICING_PATH,
1470
+ className: "text-primary underline",
1471
+ children: "Back to pricing"
1472
+ })
1473
+ ]
1474
+ });
1475
+ if (submit.isSuccess) return /* @__PURE__ */ jsxs("div", {
1476
+ className: "mx-auto flex min-h-[50vh] max-w-lg flex-col items-center justify-center gap-4 px-4 text-center",
1477
+ children: [
1478
+ /* @__PURE__ */ jsx("h1", {
1479
+ className: "text-2xl font-semibold tracking-tight",
1480
+ children: "Thanks, we got your message"
1481
+ }),
1482
+ /* @__PURE__ */ jsxs("p", {
1483
+ className: "text-muted-foreground",
1484
+ children: [
1485
+ "The team will reply to ",
1486
+ form.email.trim(),
1487
+ " soon."
1488
+ ]
1489
+ }),
1490
+ /* @__PURE__ */ jsx(Button$2, {
1491
+ asChild: true,
1492
+ variant: "outline",
1493
+ children: /* @__PURE__ */ jsx(Link$1, {
1494
+ to: PRICING_PATH,
1495
+ children: "Back to pricing"
1496
+ })
1497
+ })
1498
+ ]
1499
+ });
1500
+ const update = (field) => (event) => setForm((prev) => ({
1501
+ ...prev,
1502
+ [field]: event.target.value
1503
+ }));
1504
+ const handleSubmit = (event) => {
1505
+ event.preventDefault();
1506
+ const nextErrors = validate(form);
1507
+ setErrors(nextErrors);
1508
+ if (Object.keys(nextErrors).length > 0 || isSimulated) return;
1509
+ submit.mutate({
1510
+ planSku,
1511
+ inquiry: {
1512
+ name: form.name.trim(),
1513
+ email: form.email.trim(),
1514
+ company: form.company.trim() || void 0,
1515
+ message: form.message.trim(),
1516
+ website: form.website || void 0
1517
+ }
1518
+ });
1519
+ };
1520
+ return /* @__PURE__ */ jsxs("div", {
1521
+ className: "mx-auto w-full max-w-lg px-4 py-12",
1522
+ children: [
1523
+ /* @__PURE__ */ jsx("h1", {
1524
+ className: "text-2xl font-semibold tracking-tight",
1525
+ children: "Contact sales"
1526
+ }),
1527
+ /* @__PURE__ */ jsx("p", {
1528
+ className: "mt-2 text-muted-foreground",
1529
+ children: plan ? `Tell us about your needs for the ${plan.displayName} plan and we'll get back to you with pricing.` : "Tell us about your needs and we'll get back to you with pricing."
1530
+ }),
1531
+ isSimulated ? /* @__PURE__ */ jsxs(Alert$1, {
1532
+ className: "mt-6",
1533
+ children: [/* @__PURE__ */ jsx(AlertTitle$1, { children: "Preview only" }), /* @__PURE__ */ jsx(AlertDescription, { children: "Messages are sent once your portal is published to API ToGo." })]
1534
+ }) : null,
1535
+ /* @__PURE__ */ jsxs("form", {
1536
+ className: "mt-6 space-y-4",
1537
+ noValidate: true,
1538
+ onSubmit: handleSubmit,
1539
+ children: [
1540
+ /* @__PURE__ */ jsxs("div", {
1541
+ className: "space-y-1.5",
1542
+ children: [
1543
+ /* @__PURE__ */ jsx(Label, {
1544
+ htmlFor: "contact-name",
1545
+ children: "Name"
1546
+ }),
1547
+ /* @__PURE__ */ jsx(Input$1, {
1548
+ id: "contact-name",
1549
+ autoComplete: "name",
1550
+ maxLength: 200,
1551
+ value: form.name,
1552
+ onChange: update("name"),
1553
+ "aria-invalid": Boolean(errors.name)
1554
+ }),
1555
+ errors.name ? /* @__PURE__ */ jsx("p", {
1556
+ className: "text-sm text-destructive",
1557
+ children: errors.name
1558
+ }) : null
1559
+ ]
1560
+ }),
1561
+ /* @__PURE__ */ jsxs("div", {
1562
+ className: "space-y-1.5",
1563
+ children: [
1564
+ /* @__PURE__ */ jsx(Label, {
1565
+ htmlFor: "contact-email",
1566
+ children: "Work email"
1567
+ }),
1568
+ /* @__PURE__ */ jsx(Input$1, {
1569
+ id: "contact-email",
1570
+ type: "email",
1571
+ autoComplete: "email",
1572
+ maxLength: 320,
1573
+ value: form.email,
1574
+ onChange: update("email"),
1575
+ "aria-invalid": Boolean(errors.email)
1576
+ }),
1577
+ errors.email ? /* @__PURE__ */ jsx("p", {
1578
+ className: "text-sm text-destructive",
1579
+ children: errors.email
1580
+ }) : null
1581
+ ]
1582
+ }),
1583
+ /* @__PURE__ */ jsxs("div", {
1584
+ className: "space-y-1.5",
1585
+ children: [/* @__PURE__ */ jsx(Label, {
1586
+ htmlFor: "contact-company",
1587
+ children: "Company (optional)"
1588
+ }), /* @__PURE__ */ jsx(Input$1, {
1589
+ id: "contact-company",
1590
+ autoComplete: "organization",
1591
+ maxLength: 200,
1592
+ value: form.company,
1593
+ onChange: update("company")
1594
+ })]
1595
+ }),
1596
+ /* @__PURE__ */ jsxs("div", {
1597
+ className: "space-y-1.5",
1598
+ children: [
1599
+ /* @__PURE__ */ jsx(Label, {
1600
+ htmlFor: "contact-message",
1601
+ children: "Message"
1602
+ }),
1603
+ /* @__PURE__ */ jsx(Textarea, {
1604
+ id: "contact-message",
1605
+ rows: 5,
1606
+ maxLength: 5e3,
1607
+ placeholder: "Expected traffic, use case, timeline…",
1608
+ value: form.message,
1609
+ onChange: update("message"),
1610
+ "aria-invalid": Boolean(errors.message)
1611
+ }),
1612
+ errors.message ? /* @__PURE__ */ jsx("p", {
1613
+ className: "text-sm text-destructive",
1614
+ children: errors.message
1615
+ }) : null
1616
+ ]
1617
+ }),
1618
+ /* @__PURE__ */ jsxs("div", {
1619
+ className: "absolute -left-[9999px]",
1620
+ "aria-hidden": "true",
1621
+ children: [/* @__PURE__ */ jsx("label", {
1622
+ htmlFor: "contact-website",
1623
+ children: "Website"
1624
+ }), /* @__PURE__ */ jsx("input", {
1625
+ id: "contact-website",
1626
+ tabIndex: -1,
1627
+ autoComplete: "off",
1628
+ value: form.website,
1629
+ onChange: update("website")
1630
+ })]
1631
+ }),
1632
+ submit.isError ? /* @__PURE__ */ jsx(Alert$1, {
1633
+ variant: "destructive",
1634
+ children: /* @__PURE__ */ jsx(AlertDescription, { children: submit.error.message })
1635
+ }) : null,
1636
+ /* @__PURE__ */ jsx(Button$2, {
1637
+ type: "submit",
1638
+ className: "w-full",
1639
+ disabled: submit.isPending || isSimulated,
1640
+ children: submit.isPending ? "Sending…" : "Send message"
1641
+ })
1642
+ ]
1643
+ })
1644
+ ]
1645
+ });
1646
+ };
1647
+
1385
1648
  //#endregion
1386
1649
  //#region src/hooks/useCredentials.ts
1387
1650
  function useCredentials() {
@@ -1740,7 +2003,7 @@ const PricingPage = () => {
1740
2003
  children: [/* @__PURE__ */ jsx("div", {
1741
2004
  className: "font-medium text-foreground",
1742
2005
  children: row.label
1743
- }), compareHeaders.map((_, index) => /* @__PURE__ */ jsx("div", { children: row.values[index] ?? "—" }, `${row.label}-${index}`))]
2006
+ }), compareHeaders.map((header, index) => /* @__PURE__ */ jsx("div", { children: row.values[index] ?? "—" }, `${row.label}-${header}`))]
1744
2007
  }, row.label))]
1745
2008
  })
1746
2009
  })]
@@ -2289,6 +2552,11 @@ const devPortalBillingPlugin = createPlugin((options = {}) => ({
2289
2552
  handle: { layout: "default" },
2290
2553
  element: /* @__PURE__ */ jsx(CheckoutPage, {})
2291
2554
  },
2555
+ {
2556
+ path: CONTACT_SALES_PATH,
2557
+ handle: { layout: "default" },
2558
+ element: /* @__PURE__ */ jsx(ContactSalesPage, {})
2559
+ },
2292
2560
  {
2293
2561
  path: SUBSCRIPTIONS_PATH,
2294
2562
  handle: { layout: "default" },
@@ -2325,4 +2593,4 @@ const devPortalBillingPlugin = createPlugin((options = {}) => ({
2325
2593
  }));
2326
2594
 
2327
2595
  //#endregion
2328
- export { ACCOUNT_PLANS_PATH, API_KEY_CAP_REACHED, AccountPlansPage, ApiKeysPanel, CHECKOUT_PATH, CREDENTIALS_PATH, OverviewDashboardPage, PRICING_PATH, SUBSCRIPTIONS_PATH, USAGE_PATH, devPortalBillingPlugin, useApiKeys, useCreateApiKey, useRevokeApiKey };
2596
+ export { ACCOUNT_PLANS_PATH, API_KEY_CAP_REACHED, AccountPlansPage, ApiKeysPanel, CHECKOUT_PATH, CONTACT_SALES_PATH, CREDENTIALS_PATH, OverviewDashboardPage, PRICING_PATH, SUBSCRIPTIONS_PATH, USAGE_PATH, devPortalBillingPlugin, useApiKeys, useCreateApiKey, useRevokeApiKey };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lukoweb/apitogo-plugin-dev-portal-billing",
3
- "version": "0.1.120",
3
+ "version": "0.1.127",
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.1.120",
50
- "@lukoweb/apitogo-module-user-panel": "0.1.120"
49
+ "@lukoweb/apitogo": "0.1.127",
50
+ "@lukoweb/apitogo-module-user-panel": "0.1.127"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "react": ">=19.2.0",