@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 +4 -1
- package/dist/index.mjs +292 -24
- package/package.json +3 -3
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
|
|
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
|
|
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
|
|
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
|
|
191
|
-
const displayPrice = isCustom ?
|
|
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:
|
|
259
|
-
children:
|
|
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:
|
|
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((
|
|
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.
|
|
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.
|
|
50
|
-
"@lukoweb/apitogo-module-user-panel": "0.1.
|
|
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",
|