@lukoweb/apitogo-plugin-dev-portal-billing 0.2.31 → 0.2.34
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/AccountPlansPage-CXOfTwlX.mjs +208 -0
- package/dist/ApiKeysPanel-DrArkHXf.mjs +285 -0
- package/dist/CheckoutPage-CW_EBGog.mjs +174 -0
- package/dist/ContactSalesPage-Vjzm-RxO.mjs +256 -0
- package/dist/CredentialsPage-CtIEboEy.mjs +189 -0
- package/dist/DevPortalBillingContext-CR83s4LO.mjs +13 -0
- package/dist/PricingCard-Wra2FUX9.mjs +145 -0
- package/dist/PricingPage-MTslSnCh.mjs +233 -0
- package/dist/SubscriptionsPage-9lXnVks7.mjs +226 -0
- package/dist/UsagePage--mg_NzrA.mjs +203 -0
- package/dist/billing-state-pnT0H1Lm.mjs +37 -0
- package/dist/demo-session-Cez740bi.mjs +65 -0
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +121 -2402
- package/dist/paths-ujy9wjVK.mjs +12 -0
- package/dist/pricing-config-CfhRX9tk.mjs +11 -0
- package/dist/useDevPortalApiBaseUrl-CgygF2z5.mjs +241 -0
- package/dist/useMySubscriptions-CLh3eFY4.mjs +42 -0
- package/dist/usePlans-DUex2tzT.mjs +69 -0
- package/dist/useUsage-Cx2GB3wg.mjs +164 -0
- package/package.json +3 -3
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { o as PRICING_PATH } from "./paths-ujy9wjVK.mjs";
|
|
2
|
+
import { n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl, u as isPlanCustomPriced } from "./useDevPortalApiBaseUrl-CgygF2z5.mjs";
|
|
3
|
+
import "./DevPortalBillingContext-CR83s4LO.mjs";
|
|
4
|
+
import { t as usePlans } from "./usePlans-DUex2tzT.mjs";
|
|
5
|
+
import { Link, useSearchParams } from "@lukoweb/apitogo/router";
|
|
6
|
+
import { useState } from "react";
|
|
7
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { useMutation } from "@lukoweb/apitogo/react-query";
|
|
9
|
+
import { useAuth } from "@lukoweb/apitogo/hooks";
|
|
10
|
+
import { Alert, AlertDescription, AlertTitle } from "@lukoweb/apitogo/ui/Alert";
|
|
11
|
+
import { Button } from "@lukoweb/apitogo/ui/Button";
|
|
12
|
+
import { Input } from "@lukoweb/apitogo/ui/Input";
|
|
13
|
+
import { Label } from "@lukoweb/apitogo/ui/Label";
|
|
14
|
+
import { Textarea } from "@lukoweb/apitogo/ui/Textarea";
|
|
15
|
+
|
|
16
|
+
//#region src/hooks/usePlanInquiry.ts
|
|
17
|
+
const PLAN_INQUIRY_RATE_LIMITED = "Too many messages were sent from your network. Please try again later.";
|
|
18
|
+
function useSubmitPlanInquiry() {
|
|
19
|
+
const apiBaseUrl = useDevPortalApiBaseUrl();
|
|
20
|
+
return useMutation({ mutationFn: async ({ planSku, inquiry }) => {
|
|
21
|
+
if (!apiBaseUrl) throw new Error("Dev portal API is not configured.");
|
|
22
|
+
const response = await fetch(`${apiBaseUrl}/api/v1/plans/${encodeURIComponent(planSku)}/inquiries`, {
|
|
23
|
+
method: "POST",
|
|
24
|
+
headers: {
|
|
25
|
+
Accept: "application/json",
|
|
26
|
+
"Content-Type": "application/json"
|
|
27
|
+
},
|
|
28
|
+
credentials: "include",
|
|
29
|
+
body: JSON.stringify(inquiry)
|
|
30
|
+
});
|
|
31
|
+
if (response.status === 429) throw new Error(PLAN_INQUIRY_RATE_LIMITED);
|
|
32
|
+
if (!response.ok) throw new Error(await readProblemDetail(response) ?? "Your message could not be sent. Please try again later.");
|
|
33
|
+
} });
|
|
34
|
+
}
|
|
35
|
+
async function readProblemDetail(response) {
|
|
36
|
+
try {
|
|
37
|
+
const body = await response.json();
|
|
38
|
+
return (body.errors ? Object.values(body.errors).flat()[0] : void 0) ?? body.detail ?? body.title ?? null;
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/pages/ContactSalesPage.tsx
|
|
46
|
+
const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
47
|
+
const validate = (form) => ({
|
|
48
|
+
...form.name.trim() ? {} : { name: "Enter your name." },
|
|
49
|
+
...EMAIL_PATTERN.test(form.email.trim()) ? {} : { email: "Enter a valid email address." },
|
|
50
|
+
...form.message.trim() ? {} : { message: "Tell us what you need." }
|
|
51
|
+
});
|
|
52
|
+
const ContactSalesPage = () => {
|
|
53
|
+
const [searchParams] = useSearchParams();
|
|
54
|
+
const planSku = searchParams.get("planSku") ?? "";
|
|
55
|
+
const auth = useAuth();
|
|
56
|
+
const isPreview = useDevPortalPreviewMode();
|
|
57
|
+
const isDemoSession = useIsDemoSession();
|
|
58
|
+
const plans = usePlans();
|
|
59
|
+
const submit = useSubmitPlanInquiry();
|
|
60
|
+
const [form, setForm] = useState(() => ({
|
|
61
|
+
name: auth.profile?.name ?? "",
|
|
62
|
+
email: auth.profile?.email ?? "",
|
|
63
|
+
company: "",
|
|
64
|
+
message: "",
|
|
65
|
+
website: ""
|
|
66
|
+
}));
|
|
67
|
+
const [errors, setErrors] = useState({});
|
|
68
|
+
const plan = plans.data?.plans.find((p) => p.sku === planSku);
|
|
69
|
+
const isSimulated = isPreview || isDemoSession;
|
|
70
|
+
if (!planSku || plans.isSuccess && (!plan || !isPlanCustomPriced(plan))) return /* @__PURE__ */ jsxs("div", {
|
|
71
|
+
className: "mx-auto max-w-lg px-4 py-16 text-center text-muted-foreground",
|
|
72
|
+
children: [
|
|
73
|
+
"This plan is not available for sales inquiries.",
|
|
74
|
+
" ",
|
|
75
|
+
/* @__PURE__ */ jsx(Link, {
|
|
76
|
+
to: PRICING_PATH,
|
|
77
|
+
className: "text-primary underline",
|
|
78
|
+
children: "Back to pricing"
|
|
79
|
+
})
|
|
80
|
+
]
|
|
81
|
+
});
|
|
82
|
+
if (submit.isSuccess) return /* @__PURE__ */ jsxs("div", {
|
|
83
|
+
className: "mx-auto flex min-h-[50vh] max-w-lg flex-col items-center justify-center gap-4 px-4 text-center",
|
|
84
|
+
children: [
|
|
85
|
+
/* @__PURE__ */ jsx("h1", {
|
|
86
|
+
className: "text-2xl font-semibold tracking-tight",
|
|
87
|
+
children: "Thanks, we got your message"
|
|
88
|
+
}),
|
|
89
|
+
/* @__PURE__ */ jsxs("p", {
|
|
90
|
+
className: "text-muted-foreground",
|
|
91
|
+
children: [
|
|
92
|
+
"The team will reply to ",
|
|
93
|
+
form.email.trim(),
|
|
94
|
+
" soon."
|
|
95
|
+
]
|
|
96
|
+
}),
|
|
97
|
+
/* @__PURE__ */ jsx(Button, {
|
|
98
|
+
asChild: true,
|
|
99
|
+
variant: "outline",
|
|
100
|
+
children: /* @__PURE__ */ jsx(Link, {
|
|
101
|
+
to: PRICING_PATH,
|
|
102
|
+
children: "Back to pricing"
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
]
|
|
106
|
+
});
|
|
107
|
+
const update = (field) => (event) => setForm((prev) => ({
|
|
108
|
+
...prev,
|
|
109
|
+
[field]: event.target.value
|
|
110
|
+
}));
|
|
111
|
+
const handleSubmit = (event) => {
|
|
112
|
+
event.preventDefault();
|
|
113
|
+
const nextErrors = validate(form);
|
|
114
|
+
setErrors(nextErrors);
|
|
115
|
+
if (Object.keys(nextErrors).length > 0 || isSimulated) return;
|
|
116
|
+
submit.mutate({
|
|
117
|
+
planSku,
|
|
118
|
+
inquiry: {
|
|
119
|
+
name: form.name.trim(),
|
|
120
|
+
email: form.email.trim(),
|
|
121
|
+
company: form.company.trim() || void 0,
|
|
122
|
+
message: form.message.trim(),
|
|
123
|
+
website: form.website || void 0
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
128
|
+
className: "mx-auto w-full max-w-lg px-4 py-12",
|
|
129
|
+
children: [
|
|
130
|
+
/* @__PURE__ */ jsx("h1", {
|
|
131
|
+
className: "text-2xl font-semibold tracking-tight",
|
|
132
|
+
children: "Contact sales"
|
|
133
|
+
}),
|
|
134
|
+
/* @__PURE__ */ jsx("p", {
|
|
135
|
+
className: "mt-2 text-muted-foreground",
|
|
136
|
+
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."
|
|
137
|
+
}),
|
|
138
|
+
isSimulated ? /* @__PURE__ */ jsxs(Alert, {
|
|
139
|
+
className: "mt-6",
|
|
140
|
+
children: [/* @__PURE__ */ jsx(AlertTitle, { children: "Preview only" }), /* @__PURE__ */ jsx(AlertDescription, { children: "Messages are sent once your portal is published to API ToGo." })]
|
|
141
|
+
}) : null,
|
|
142
|
+
/* @__PURE__ */ jsxs("form", {
|
|
143
|
+
className: "mt-6 space-y-4",
|
|
144
|
+
noValidate: true,
|
|
145
|
+
onSubmit: handleSubmit,
|
|
146
|
+
children: [
|
|
147
|
+
/* @__PURE__ */ jsxs("div", {
|
|
148
|
+
className: "space-y-1.5",
|
|
149
|
+
children: [
|
|
150
|
+
/* @__PURE__ */ jsx(Label, {
|
|
151
|
+
htmlFor: "contact-name",
|
|
152
|
+
children: "Name"
|
|
153
|
+
}),
|
|
154
|
+
/* @__PURE__ */ jsx(Input, {
|
|
155
|
+
id: "contact-name",
|
|
156
|
+
autoComplete: "name",
|
|
157
|
+
maxLength: 200,
|
|
158
|
+
value: form.name,
|
|
159
|
+
onChange: update("name"),
|
|
160
|
+
"aria-invalid": Boolean(errors.name)
|
|
161
|
+
}),
|
|
162
|
+
errors.name ? /* @__PURE__ */ jsx("p", {
|
|
163
|
+
className: "text-sm text-destructive",
|
|
164
|
+
children: errors.name
|
|
165
|
+
}) : null
|
|
166
|
+
]
|
|
167
|
+
}),
|
|
168
|
+
/* @__PURE__ */ jsxs("div", {
|
|
169
|
+
className: "space-y-1.5",
|
|
170
|
+
children: [
|
|
171
|
+
/* @__PURE__ */ jsx(Label, {
|
|
172
|
+
htmlFor: "contact-email",
|
|
173
|
+
children: "Work email"
|
|
174
|
+
}),
|
|
175
|
+
/* @__PURE__ */ jsx(Input, {
|
|
176
|
+
id: "contact-email",
|
|
177
|
+
type: "email",
|
|
178
|
+
autoComplete: "email",
|
|
179
|
+
maxLength: 320,
|
|
180
|
+
value: form.email,
|
|
181
|
+
onChange: update("email"),
|
|
182
|
+
"aria-invalid": Boolean(errors.email)
|
|
183
|
+
}),
|
|
184
|
+
errors.email ? /* @__PURE__ */ jsx("p", {
|
|
185
|
+
className: "text-sm text-destructive",
|
|
186
|
+
children: errors.email
|
|
187
|
+
}) : null
|
|
188
|
+
]
|
|
189
|
+
}),
|
|
190
|
+
/* @__PURE__ */ jsxs("div", {
|
|
191
|
+
className: "space-y-1.5",
|
|
192
|
+
children: [/* @__PURE__ */ jsx(Label, {
|
|
193
|
+
htmlFor: "contact-company",
|
|
194
|
+
children: "Company (optional)"
|
|
195
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
196
|
+
id: "contact-company",
|
|
197
|
+
autoComplete: "organization",
|
|
198
|
+
maxLength: 200,
|
|
199
|
+
value: form.company,
|
|
200
|
+
onChange: update("company")
|
|
201
|
+
})]
|
|
202
|
+
}),
|
|
203
|
+
/* @__PURE__ */ jsxs("div", {
|
|
204
|
+
className: "space-y-1.5",
|
|
205
|
+
children: [
|
|
206
|
+
/* @__PURE__ */ jsx(Label, {
|
|
207
|
+
htmlFor: "contact-message",
|
|
208
|
+
children: "Message"
|
|
209
|
+
}),
|
|
210
|
+
/* @__PURE__ */ jsx(Textarea, {
|
|
211
|
+
id: "contact-message",
|
|
212
|
+
rows: 5,
|
|
213
|
+
maxLength: 5e3,
|
|
214
|
+
placeholder: "Expected traffic, use case, timeline…",
|
|
215
|
+
value: form.message,
|
|
216
|
+
onChange: update("message"),
|
|
217
|
+
"aria-invalid": Boolean(errors.message)
|
|
218
|
+
}),
|
|
219
|
+
errors.message ? /* @__PURE__ */ jsx("p", {
|
|
220
|
+
className: "text-sm text-destructive",
|
|
221
|
+
children: errors.message
|
|
222
|
+
}) : null
|
|
223
|
+
]
|
|
224
|
+
}),
|
|
225
|
+
/* @__PURE__ */ jsxs("div", {
|
|
226
|
+
className: "absolute -left-[9999px]",
|
|
227
|
+
"aria-hidden": "true",
|
|
228
|
+
children: [/* @__PURE__ */ jsx("label", {
|
|
229
|
+
htmlFor: "contact-website",
|
|
230
|
+
children: "Website"
|
|
231
|
+
}), /* @__PURE__ */ jsx("input", {
|
|
232
|
+
id: "contact-website",
|
|
233
|
+
tabIndex: -1,
|
|
234
|
+
autoComplete: "off",
|
|
235
|
+
value: form.website,
|
|
236
|
+
onChange: update("website")
|
|
237
|
+
})]
|
|
238
|
+
}),
|
|
239
|
+
submit.isError ? /* @__PURE__ */ jsx(Alert, {
|
|
240
|
+
variant: "destructive",
|
|
241
|
+
children: /* @__PURE__ */ jsx(AlertDescription, { children: submit.error.message })
|
|
242
|
+
}) : null,
|
|
243
|
+
/* @__PURE__ */ jsx(Button, {
|
|
244
|
+
type: "submit",
|
|
245
|
+
className: "w-full",
|
|
246
|
+
disabled: submit.isPending || isSimulated,
|
|
247
|
+
children: submit.isPending ? "Sending…" : "Send message"
|
|
248
|
+
})
|
|
249
|
+
]
|
|
250
|
+
})
|
|
251
|
+
]
|
|
252
|
+
});
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
//#endregion
|
|
256
|
+
export { ContactSalesPage as default };
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { t as ACCOUNT_PLANS_PATH } from "./paths-ujy9wjVK.mjs";
|
|
2
|
+
import { n as useDevPortalPreviewMode, r as useIsDemoSession, t as useDevPortalApiBaseUrl } from "./useDevPortalApiBaseUrl-CgygF2z5.mjs";
|
|
3
|
+
import { n as useDevPortalBillingConfig } from "./DevPortalBillingContext-CR83s4LO.mjs";
|
|
4
|
+
import { n as createDemoCredentials } from "./demo-session-Cez740bi.mjs";
|
|
5
|
+
import { r as useApiKeys, t as ApiKeysPanel } from "./ApiKeysPanel-DrArkHXf.mjs";
|
|
6
|
+
import { Head } from "@lukoweb/apitogo/components";
|
|
7
|
+
import { KeyRoundIcon } from "@lukoweb/apitogo/icons";
|
|
8
|
+
import { Link as Link$1 } from "@lukoweb/apitogo/router";
|
|
9
|
+
import zudokuConfig from "virtual:zudoku-config";
|
|
10
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
import { useQuery } from "@lukoweb/apitogo/react-query";
|
|
12
|
+
import { useAuth } from "@lukoweb/apitogo/hooks";
|
|
13
|
+
import { Button as Button$1 } from "@lukoweb/apitogo/ui/Button";
|
|
14
|
+
import { Value } from "@lukoweb/apitogo/ui/Value.js";
|
|
15
|
+
|
|
16
|
+
//#region src/hooks/useCredentials.ts
|
|
17
|
+
function useCredentials() {
|
|
18
|
+
const auth = useAuth();
|
|
19
|
+
const apiBaseUrl = useDevPortalApiBaseUrl();
|
|
20
|
+
const isPreview = useDevPortalPreviewMode();
|
|
21
|
+
const isDemoSession = useIsDemoSession();
|
|
22
|
+
const { previewPlans } = useDevPortalBillingConfig();
|
|
23
|
+
const liveQuery = useQuery({
|
|
24
|
+
queryKey: ["dev-portal-credentials", apiBaseUrl],
|
|
25
|
+
enabled: auth.isAuthenticated && !isPreview && !isDemoSession && Boolean(apiBaseUrl),
|
|
26
|
+
queryFn: async () => {
|
|
27
|
+
const response = await fetch(`${apiBaseUrl}/api/v1/credentials`, {
|
|
28
|
+
method: "GET",
|
|
29
|
+
headers: { Accept: "application/json" },
|
|
30
|
+
credentials: "include"
|
|
31
|
+
});
|
|
32
|
+
if (response.status === 404) throw new Error("NO_ENTITLEMENT");
|
|
33
|
+
if (!response.ok) throw new Error("Failed to load API credentials.");
|
|
34
|
+
return await response.json();
|
|
35
|
+
},
|
|
36
|
+
retry: (failureCount, error) => error instanceof Error && error.message === "NO_ENTITLEMENT" ? false : failureCount < 2
|
|
37
|
+
});
|
|
38
|
+
if (isDemoSession && auth.isAuthenticated) return {
|
|
39
|
+
...liveQuery,
|
|
40
|
+
data: createDemoCredentials(previewPlans),
|
|
41
|
+
error: null,
|
|
42
|
+
isError: false,
|
|
43
|
+
isFetching: false,
|
|
44
|
+
isLoading: false,
|
|
45
|
+
isPending: false,
|
|
46
|
+
isSuccess: true,
|
|
47
|
+
status: "success"
|
|
48
|
+
};
|
|
49
|
+
return liveQuery;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/pages/CredentialsPage.tsx
|
|
54
|
+
const CredentialsPage = () => {
|
|
55
|
+
const auth = useAuth();
|
|
56
|
+
const isPreview = useDevPortalPreviewMode();
|
|
57
|
+
const isDemoSession = useIsDemoSession();
|
|
58
|
+
const { data: credentials, isLoading, error } = useCredentials();
|
|
59
|
+
const { data: apiKeys, isLoading: keysLoading, error: keysError } = useApiKeys();
|
|
60
|
+
const { gatewayBaseUrl: configuredGatewayBaseUrl } = useDevPortalBillingConfig();
|
|
61
|
+
const gatewayBaseUrl = credentials?.gatewayBaseUrl?.trim() || apiKeys?.gatewayBaseUrl?.trim() || configuredGatewayBaseUrl?.trim() || zudokuConfig.__meta?.gatewayBaseUrl?.trim() || null;
|
|
62
|
+
const header = apiKeys?.header ?? "Ocp-Apim-Subscription-Key";
|
|
63
|
+
const plans = apiKeys?.plans ?? [];
|
|
64
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
65
|
+
className: "mx-auto max-w-screen-lg px-4 pb-16 pt-(--padding-content-top) lg:px-8",
|
|
66
|
+
children: [
|
|
67
|
+
/* @__PURE__ */ jsx(Head, { children: /* @__PURE__ */ jsx("title", { children: "API credentials" }) }),
|
|
68
|
+
/* @__PURE__ */ jsxs("div", {
|
|
69
|
+
className: "mb-8 flex items-start gap-4",
|
|
70
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
71
|
+
className: "flex size-12 shrink-0 items-center justify-center rounded-xl bg-primary/10 text-primary",
|
|
72
|
+
children: /* @__PURE__ */ jsx(KeyRoundIcon, { size: 24 })
|
|
73
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
74
|
+
className: "space-y-1",
|
|
75
|
+
children: [/* @__PURE__ */ jsx("h1", {
|
|
76
|
+
className: "text-3xl font-semibold tracking-tight",
|
|
77
|
+
children: "API credentials"
|
|
78
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
79
|
+
className: "text-muted-foreground",
|
|
80
|
+
children: "Create API keys for each subscribed plan and use them to authenticate requests to the API gateway."
|
|
81
|
+
})]
|
|
82
|
+
})]
|
|
83
|
+
}),
|
|
84
|
+
isDemoSession ? /* @__PURE__ */ jsx("div", {
|
|
85
|
+
className: "mb-6 rounded-xl border border-dashed bg-muted/20 p-4 text-sm text-muted-foreground",
|
|
86
|
+
children: "Local demo credentials. Publish the portal to receive live subscription keys from your dev-portal backend."
|
|
87
|
+
}) : null,
|
|
88
|
+
isPreview && !isDemoSession ? /* @__PURE__ */ jsx("div", {
|
|
89
|
+
className: "rounded-xl border border-dashed p-8 text-center",
|
|
90
|
+
children: /* @__PURE__ */ jsx("p", {
|
|
91
|
+
className: "text-muted-foreground",
|
|
92
|
+
children: "API credentials are available after the developer portal is published."
|
|
93
|
+
})
|
|
94
|
+
}) : null,
|
|
95
|
+
!isPreview && !isDemoSession && !auth.isAuthenticated ? /* @__PURE__ */ jsxs("div", {
|
|
96
|
+
className: "rounded-xl border border-dashed p-8 text-center",
|
|
97
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
98
|
+
className: "text-muted-foreground",
|
|
99
|
+
children: "Sign in to view your subscription keys."
|
|
100
|
+
}), /* @__PURE__ */ jsx(Button$1, {
|
|
101
|
+
className: "mt-4",
|
|
102
|
+
asChild: true,
|
|
103
|
+
children: /* @__PURE__ */ jsx(Link$1, {
|
|
104
|
+
to: ACCOUNT_PLANS_PATH,
|
|
105
|
+
children: "View plans"
|
|
106
|
+
})
|
|
107
|
+
})]
|
|
108
|
+
}) : null,
|
|
109
|
+
!isPreview && !isDemoSession && auth.isAuthenticated && (isLoading || keysLoading) ? /* @__PURE__ */ jsx("p", {
|
|
110
|
+
className: "text-muted-foreground",
|
|
111
|
+
children: "Loading credentials…"
|
|
112
|
+
}) : null,
|
|
113
|
+
!isPreview && !isDemoSession && auth.isAuthenticated && (error || keysError) ? /* @__PURE__ */ jsxs("div", {
|
|
114
|
+
className: "rounded-xl border border-dashed p-8 text-center",
|
|
115
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
116
|
+
className: "text-muted-foreground",
|
|
117
|
+
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."
|
|
118
|
+
}), /* @__PURE__ */ jsx(Button$1, {
|
|
119
|
+
className: "mt-4",
|
|
120
|
+
asChild: true,
|
|
121
|
+
children: /* @__PURE__ */ jsx(Link$1, {
|
|
122
|
+
to: ACCOUNT_PLANS_PATH,
|
|
123
|
+
children: "View plans"
|
|
124
|
+
})
|
|
125
|
+
})]
|
|
126
|
+
}) : null,
|
|
127
|
+
(isDemoSession || !isPreview) && auth.isAuthenticated && !error && !keysError && !isLoading && !keysLoading ? /* @__PURE__ */ jsxs("div", {
|
|
128
|
+
className: "space-y-6",
|
|
129
|
+
children: [plans.length === 0 ? /* @__PURE__ */ jsxs("div", {
|
|
130
|
+
className: "rounded-xl border border-dashed p-8 text-center",
|
|
131
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
132
|
+
className: "text-muted-foreground",
|
|
133
|
+
children: "Choose a plan to get your first API key."
|
|
134
|
+
}), /* @__PURE__ */ jsx(Button$1, {
|
|
135
|
+
className: "mt-4",
|
|
136
|
+
asChild: true,
|
|
137
|
+
children: /* @__PURE__ */ jsx(Link$1, {
|
|
138
|
+
to: ACCOUNT_PLANS_PATH,
|
|
139
|
+
children: "View plans"
|
|
140
|
+
})
|
|
141
|
+
})]
|
|
142
|
+
}) : plans.map((plan) => /* @__PURE__ */ jsx(ApiKeysPanel, {
|
|
143
|
+
plan,
|
|
144
|
+
header,
|
|
145
|
+
readOnly: isDemoSession
|
|
146
|
+
}, plan.planSku)), /* @__PURE__ */ jsxs("article", {
|
|
147
|
+
className: "rounded-xl border bg-card/60 p-6 space-y-4",
|
|
148
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
149
|
+
className: "space-y-1",
|
|
150
|
+
children: [/* @__PURE__ */ jsx("h2", {
|
|
151
|
+
className: "text-lg font-medium",
|
|
152
|
+
children: "Gateway URL"
|
|
153
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
154
|
+
className: "text-sm text-muted-foreground",
|
|
155
|
+
children: "Base URL for API requests through the gateway."
|
|
156
|
+
})]
|
|
157
|
+
}), gatewayBaseUrl ? /* @__PURE__ */ jsx(Value, {
|
|
158
|
+
className: "max-w-full w-full",
|
|
159
|
+
value: gatewayBaseUrl
|
|
160
|
+
}) : /* @__PURE__ */ jsx("p", {
|
|
161
|
+
className: "text-sm text-muted-foreground",
|
|
162
|
+
children: "Available after your API is deployed to the gateway."
|
|
163
|
+
})]
|
|
164
|
+
})]
|
|
165
|
+
}) : null,
|
|
166
|
+
/* @__PURE__ */ jsxs("div", {
|
|
167
|
+
className: "mt-8 flex flex-wrap gap-3",
|
|
168
|
+
children: [/* @__PURE__ */ jsx(Button$1, {
|
|
169
|
+
variant: "outline",
|
|
170
|
+
asChild: true,
|
|
171
|
+
children: /* @__PURE__ */ jsx(Link$1, {
|
|
172
|
+
to: "/account/subscriptions",
|
|
173
|
+
children: "My subscriptions"
|
|
174
|
+
})
|
|
175
|
+
}), /* @__PURE__ */ jsx(Button$1, {
|
|
176
|
+
variant: "outline",
|
|
177
|
+
asChild: true,
|
|
178
|
+
children: /* @__PURE__ */ jsx(Link$1, {
|
|
179
|
+
to: "/api",
|
|
180
|
+
children: "API reference"
|
|
181
|
+
})
|
|
182
|
+
})]
|
|
183
|
+
})
|
|
184
|
+
]
|
|
185
|
+
});
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
//#endregion
|
|
189
|
+
export { CredentialsPage as default };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
|
|
4
|
+
//#region src/DevPortalBillingContext.tsx
|
|
5
|
+
const DevPortalBillingContext = createContext({});
|
|
6
|
+
const DevPortalBillingProvider = ({ options, children }) => /* @__PURE__ */ jsx(DevPortalBillingContext.Provider, {
|
|
7
|
+
value: options,
|
|
8
|
+
children
|
|
9
|
+
});
|
|
10
|
+
const useDevPortalBillingConfig = () => useContext(DevPortalBillingContext);
|
|
11
|
+
|
|
12
|
+
//#endregion
|
|
13
|
+
export { useDevPortalBillingConfig as n, DevPortalBillingProvider as t };
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { n as CHECKOUT_PATH, r as CONTACT_SALES_PATH } from "./paths-ujy9wjVK.mjs";
|
|
2
|
+
import { a as formatPlanDisplayPrice, c as isActiveSubscription, d as isPlanFree, f as listPlanFeatureLines, s as formatPlanPriceUnit, u as isPlanCustomPriced } from "./useDevPortalApiBaseUrl-CgygF2z5.mjs";
|
|
3
|
+
import { Button } from "@lukoweb/apitogo/components";
|
|
4
|
+
import { Link as Link$1 } from "@lukoweb/apitogo/router";
|
|
5
|
+
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@lukoweb/apitogo/ui/AlertDialog.js";
|
|
6
|
+
import { cn } from "@lukoweb/apitogo/ui/util.js";
|
|
7
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
|
|
9
|
+
//#region src/components/PricingCard.tsx
|
|
10
|
+
const publicCtaLabel = (plan) => {
|
|
11
|
+
if (isPlanCustomPriced(plan)) return "Contact sales";
|
|
12
|
+
if (isPlanFree(plan)) return "Get started for free";
|
|
13
|
+
return "Subscribe now";
|
|
14
|
+
};
|
|
15
|
+
const accountCtaLabel = (plan) => {
|
|
16
|
+
if (isPlanCustomPriced(plan)) return "Contact sales";
|
|
17
|
+
return isPlanFree(plan) ? "Get started" : "Subscribe";
|
|
18
|
+
};
|
|
19
|
+
/** Custom-priced plans skip checkout and open the contact-sales form. */
|
|
20
|
+
const planCtaPath = (plan) => `${isPlanCustomPriced(plan) ? CONTACT_SALES_PATH : CHECKOUT_PATH}?planSku=${encodeURIComponent(plan.sku)}`;
|
|
21
|
+
const PricingCard = ({ plan, mode = "public", subscriptionStatus, isSubscribed = false, onUnsubscribe, onSwitch, isSwitching = false, isCanceling = false, cancelDisabled = false, featured = false }) => {
|
|
22
|
+
const isActive = isSubscribed || isActiveSubscription(subscriptionStatus ?? "");
|
|
23
|
+
const features = listPlanFeatureLines(plan);
|
|
24
|
+
const displayPrice = formatPlanDisplayPrice(plan);
|
|
25
|
+
return /* @__PURE__ */ jsxs("article", {
|
|
26
|
+
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"),
|
|
27
|
+
"data-testid": `pricing-card-${plan.sku}`,
|
|
28
|
+
children: [
|
|
29
|
+
/* @__PURE__ */ jsxs("div", {
|
|
30
|
+
className: "flex items-center justify-between gap-2",
|
|
31
|
+
children: [
|
|
32
|
+
/* @__PURE__ */ jsx("h2", {
|
|
33
|
+
className: "text-sm font-bold uppercase tracking-[0.04em]",
|
|
34
|
+
children: plan.displayName
|
|
35
|
+
}),
|
|
36
|
+
featured && !isActive ? /* @__PURE__ */ jsx("span", {
|
|
37
|
+
className: "shrink-0 rounded-md bg-primary/10 px-2.5 py-0.5 text-xs font-semibold text-primary",
|
|
38
|
+
children: "Popular"
|
|
39
|
+
}) : null,
|
|
40
|
+
isActive && mode === "account" ? /* @__PURE__ */ jsx("span", {
|
|
41
|
+
className: "shrink-0 rounded-full bg-primary/10 px-3 py-1 text-xs font-medium text-primary",
|
|
42
|
+
"data-testid": `subscribed-badge-${plan.sku}`,
|
|
43
|
+
children: "Subscribed"
|
|
44
|
+
}) : null
|
|
45
|
+
]
|
|
46
|
+
}),
|
|
47
|
+
/* @__PURE__ */ jsx("p", {
|
|
48
|
+
className: "mt-3.5 text-[2.1rem] font-extrabold tracking-[-0.02em]",
|
|
49
|
+
children: displayPrice
|
|
50
|
+
}),
|
|
51
|
+
/* @__PURE__ */ jsx("p", {
|
|
52
|
+
className: "mb-[18px] mt-0.5 text-[0.78125rem] text-muted-foreground",
|
|
53
|
+
children: formatPlanPriceUnit(plan)
|
|
54
|
+
}),
|
|
55
|
+
/* @__PURE__ */ jsx("div", {
|
|
56
|
+
className: "space-y-2",
|
|
57
|
+
children: mode === "account" ? isActive ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
58
|
+
/* @__PURE__ */ jsx(Button, {
|
|
59
|
+
variant: "secondary",
|
|
60
|
+
className: "w-full",
|
|
61
|
+
disabled: true,
|
|
62
|
+
children: "Subscribed"
|
|
63
|
+
}),
|
|
64
|
+
onUnsubscribe ? /* @__PURE__ */ jsxs(AlertDialog, { children: [/* @__PURE__ */ jsx(AlertDialogTrigger, {
|
|
65
|
+
asChild: true,
|
|
66
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
67
|
+
variant: "outline",
|
|
68
|
+
className: "w-full",
|
|
69
|
+
disabled: cancelDisabled || isCanceling,
|
|
70
|
+
"data-testid": `unsubscribe-${plan.sku}`,
|
|
71
|
+
children: isCanceling ? "Canceling…" : "Unsubscribe"
|
|
72
|
+
})
|
|
73
|
+
}), /* @__PURE__ */ jsxs(AlertDialogContent, { children: [/* @__PURE__ */ jsxs(AlertDialogHeader, { children: [/* @__PURE__ */ jsxs(AlertDialogTitle, { children: [
|
|
74
|
+
"Unsubscribe from ",
|
|
75
|
+
plan.displayName,
|
|
76
|
+
"?"
|
|
77
|
+
] }), /* @__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, {
|
|
78
|
+
onClick: () => onUnsubscribe(plan.sku),
|
|
79
|
+
disabled: isCanceling,
|
|
80
|
+
children: "Unsubscribe"
|
|
81
|
+
})] })] })] }) : null,
|
|
82
|
+
cancelDisabled ? /* @__PURE__ */ jsx("p", {
|
|
83
|
+
className: "text-center text-xs text-muted-foreground",
|
|
84
|
+
children: "Unsubscribe is available after the portal is published."
|
|
85
|
+
}) : null
|
|
86
|
+
] }) : onSwitch && !isPlanCustomPriced(plan) ? /* @__PURE__ */ jsxs(AlertDialog, { children: [/* @__PURE__ */ jsx(AlertDialogTrigger, {
|
|
87
|
+
asChild: true,
|
|
88
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
89
|
+
variant: featured ? "default" : "outline",
|
|
90
|
+
className: "w-full",
|
|
91
|
+
disabled: isSwitching,
|
|
92
|
+
"data-testid": `switch-${plan.sku}`,
|
|
93
|
+
children: isSwitching ? "Switching…" : "Switch to this plan"
|
|
94
|
+
})
|
|
95
|
+
}), /* @__PURE__ */ jsxs(AlertDialogContent, { children: [/* @__PURE__ */ jsxs(AlertDialogHeader, { children: [/* @__PURE__ */ jsxs(AlertDialogTitle, { children: [
|
|
96
|
+
"Switch to ",
|
|
97
|
+
plan.displayName,
|
|
98
|
+
"?"
|
|
99
|
+
] }), /* @__PURE__ */ jsxs(AlertDialogDescription, { children: [
|
|
100
|
+
"Your current plan is replaced by ",
|
|
101
|
+
plan.displayName,
|
|
102
|
+
". The API keys of your current plan stop working. Create a key for",
|
|
103
|
+
" ",
|
|
104
|
+
plan.displayName,
|
|
105
|
+
" on the API keys page."
|
|
106
|
+
] })] }), /* @__PURE__ */ jsxs(AlertDialogFooter, { children: [/* @__PURE__ */ jsx(AlertDialogCancel, { children: "Keep current plan" }), /* @__PURE__ */ jsx(AlertDialogAction, {
|
|
107
|
+
onClick: () => onSwitch(plan.sku),
|
|
108
|
+
disabled: isSwitching,
|
|
109
|
+
children: "Switch plan"
|
|
110
|
+
})] })] })] }) : /* @__PURE__ */ jsx(Button, {
|
|
111
|
+
variant: featured ? "default" : "outline",
|
|
112
|
+
className: "w-full",
|
|
113
|
+
asChild: true,
|
|
114
|
+
children: /* @__PURE__ */ jsx(Link$1, {
|
|
115
|
+
to: planCtaPath(plan),
|
|
116
|
+
children: accountCtaLabel(plan)
|
|
117
|
+
})
|
|
118
|
+
}) : /* @__PURE__ */ jsx(Button, {
|
|
119
|
+
variant: featured ? "default" : "outline",
|
|
120
|
+
className: "w-full",
|
|
121
|
+
asChild: true,
|
|
122
|
+
children: /* @__PURE__ */ jsx(Link$1, {
|
|
123
|
+
to: planCtaPath(plan),
|
|
124
|
+
children: publicCtaLabel(plan)
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
}),
|
|
128
|
+
features.length > 0 ? /* @__PURE__ */ jsx("div", {
|
|
129
|
+
className: "mt-5 flex flex-1 flex-col gap-[11px] border-t border-border pt-[18px]",
|
|
130
|
+
"data-testid": plan.includedActionKeys && plan.includedActionKeys.length > 0 ? `included-endpoints-${plan.sku}` : void 0,
|
|
131
|
+
children: features.map((feat) => /* @__PURE__ */ jsxs("div", {
|
|
132
|
+
className: "flex items-start gap-2.5 text-[0.84375rem] text-muted-foreground",
|
|
133
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
134
|
+
className: "mt-0.5 shrink-0 text-emerald-600",
|
|
135
|
+
"aria-hidden": true,
|
|
136
|
+
children: "✓"
|
|
137
|
+
}), /* @__PURE__ */ jsx("span", { children: feat })]
|
|
138
|
+
}, feat))
|
|
139
|
+
}) : null
|
|
140
|
+
]
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
//#endregion
|
|
145
|
+
export { PricingCard as t };
|