@nozle-js/react 0.10.2 → 0.12.0
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/README.md +50 -1
- package/dist/chunk-NLPONQ5O.js +246 -0
- package/dist/chunk-NLPONQ5O.js.map +1 -0
- package/dist/index.cjs +2691 -1134
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +876 -78
- package/dist/index.d.ts +876 -78
- package/dist/index.js +2254 -964
- package/dist/index.js.map +1 -1
- package/dist/styles/preset.cjs.map +1 -1
- package/dist/styles/preset.d.cts +4 -0
- package/dist/styles/preset.d.ts +4 -0
- package/dist/styles/preset.js.map +1 -1
- package/dist/themes.cjs +230 -0
- package/dist/themes.cjs.map +1 -0
- package/dist/themes.d.cts +27 -0
- package/dist/themes.d.ts +27 -0
- package/dist/themes.js +126 -0
- package/dist/themes.js.map +1 -0
- package/dist/tokens-Bm4VWWb0.d.cts +82 -0
- package/dist/tokens-Bm4VWWb0.d.ts +82 -0
- package/package.json +22 -6
package/dist/index.js
CHANGED
|
@@ -1,9 +1,243 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
"use client";import { T, border, mergeAppearance, toCssVars, text, buttonStyle, table, surface, buttonRadius, primaryButtonBorder } from './chunk-NLPONQ5O.js';
|
|
2
|
+
import React2, { createContext, useContext, useMemo, useState, useRef, useId, useEffect, useCallback } from 'react';
|
|
3
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
4
|
+
import { QueryClient, useQuery, useInfiniteQuery } from '@tanstack/react-query';
|
|
3
5
|
import { EmbeddedCheckoutProvider, EmbeddedCheckout, Elements, useStripe, useElements, PaymentElement } from '@stripe/react-stripe-js';
|
|
4
6
|
import { loadStripe } from '@stripe/stripe-js';
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
var AppearanceContext = createContext(void 0);
|
|
9
|
+
function AppearanceProvider({
|
|
10
|
+
appearance,
|
|
11
|
+
children
|
|
12
|
+
}) {
|
|
13
|
+
return /* @__PURE__ */ jsx(AppearanceContext.Provider, { value: appearance, children });
|
|
14
|
+
}
|
|
15
|
+
function useAppearance(appearance) {
|
|
16
|
+
const inherited = useContext(AppearanceContext);
|
|
17
|
+
return useMemo(() => {
|
|
18
|
+
const { variables, elements, planCardLayout = "card" } = mergeAppearance(inherited, appearance);
|
|
19
|
+
const vars = toCssVars(variables);
|
|
20
|
+
const el = (key, style, className) => {
|
|
21
|
+
const override = elements[key];
|
|
22
|
+
return {
|
|
23
|
+
className: ["nozle-" + key, className, override?.className].filter(Boolean).join(" "),
|
|
24
|
+
style: { ...style, ...override?.style }
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
return {
|
|
28
|
+
el,
|
|
29
|
+
planCardLayout,
|
|
30
|
+
vars,
|
|
31
|
+
root: (key, style, className) => {
|
|
32
|
+
const props = el(key, style, className);
|
|
33
|
+
return { ...props, style: { ...vars, ...props.style } };
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}, [inherited, appearance]);
|
|
37
|
+
}
|
|
38
|
+
function createClient(publishableKey, baseUrl) {
|
|
39
|
+
let end = baseUrl.length;
|
|
40
|
+
while (end > 0 && baseUrl[end - 1] === "/") end--;
|
|
41
|
+
const base = baseUrl.slice(0, end);
|
|
42
|
+
return {
|
|
43
|
+
publishableKey,
|
|
44
|
+
baseUrl: base,
|
|
45
|
+
catalogFetch(path, init) {
|
|
46
|
+
return fetch(`${base}${path}`, {
|
|
47
|
+
...init,
|
|
48
|
+
headers: {
|
|
49
|
+
Authorization: `Bearer ${publishableKey}`,
|
|
50
|
+
"Content-Type": "application/json",
|
|
51
|
+
...init?.headers
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
var BillingRequestError = class extends Error {
|
|
58
|
+
constructor(status, code, requestId) {
|
|
59
|
+
super(`Billing request failed: ${status}${code ? ` ${code}` : ""}`);
|
|
60
|
+
this.status = status;
|
|
61
|
+
this.code = code;
|
|
62
|
+
this.requestId = requestId;
|
|
63
|
+
this.name = "BillingRequestError";
|
|
64
|
+
}
|
|
65
|
+
status;
|
|
66
|
+
code;
|
|
67
|
+
requestId;
|
|
68
|
+
};
|
|
69
|
+
function createBillingFetch(endpoint, headers) {
|
|
70
|
+
let end = endpoint.length;
|
|
71
|
+
while (end > 0 && endpoint[end - 1] === "/") end--;
|
|
72
|
+
const base = endpoint.slice(0, end);
|
|
73
|
+
return async (route, params = {}) => {
|
|
74
|
+
const query = new URLSearchParams();
|
|
75
|
+
for (const [key, value] of Object.entries(params)) {
|
|
76
|
+
if (value !== void 0 && value !== "") query.set(key, String(value));
|
|
77
|
+
}
|
|
78
|
+
const search = query.toString();
|
|
79
|
+
const response = await fetch(`${base}/${route}${search ? `?${search}` : ""}`, {
|
|
80
|
+
credentials: "same-origin",
|
|
81
|
+
headers: { Accept: "application/json", ...headers ? await headers() : {} }
|
|
82
|
+
});
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
const body = await response.json().catch(() => ({}));
|
|
85
|
+
throw new BillingRequestError(response.status, body.code, body.request_id);
|
|
86
|
+
}
|
|
87
|
+
return await response.json();
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function createQueryClient() {
|
|
91
|
+
return new QueryClient({
|
|
92
|
+
defaultOptions: {
|
|
93
|
+
queries: {
|
|
94
|
+
staleTime: 3e4,
|
|
95
|
+
// Retry only failures that may pass on a second try; a 4xx will not.
|
|
96
|
+
retry: (count, error) => count < 2 && !(error instanceof BillingRequestError && error.status < 500)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
var BillingContext = createContext(null);
|
|
102
|
+
function BillingProvider({
|
|
103
|
+
publishableKey,
|
|
104
|
+
createCheckout,
|
|
105
|
+
verifyCheckout,
|
|
106
|
+
getCheckoutStatus,
|
|
107
|
+
baseUrl = "https://api.nozle.app/engine",
|
|
108
|
+
billingEndpoint,
|
|
109
|
+
billingHeaders,
|
|
110
|
+
queryClient,
|
|
111
|
+
appearance,
|
|
112
|
+
children
|
|
113
|
+
}) {
|
|
114
|
+
if (!publishableKey.startsWith("pk_")) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
"BillingProvider publishableKey must be a publishable key (pk_)"
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
const client = useMemo(
|
|
120
|
+
() => createClient(publishableKey, baseUrl),
|
|
121
|
+
[publishableKey, baseUrl]
|
|
122
|
+
);
|
|
123
|
+
const [ownQueryClient] = useState(createQueryClient);
|
|
124
|
+
const activeQueryClient = queryClient ?? ownQueryClient;
|
|
125
|
+
const billingFetch = useMemo(
|
|
126
|
+
() => billingEndpoint ? createBillingFetch(billingEndpoint, billingHeaders) : void 0,
|
|
127
|
+
[billingEndpoint, billingHeaders]
|
|
128
|
+
);
|
|
129
|
+
const contextValue = useMemo(
|
|
130
|
+
() => ({
|
|
131
|
+
client,
|
|
132
|
+
createCheckout,
|
|
133
|
+
verifyCheckout,
|
|
134
|
+
getCheckoutStatus,
|
|
135
|
+
queryClient: activeQueryClient,
|
|
136
|
+
billingFetch,
|
|
137
|
+
billingEndpoint
|
|
138
|
+
}),
|
|
139
|
+
[client, createCheckout, verifyCheckout, getCheckoutStatus, activeQueryClient, billingFetch, billingEndpoint]
|
|
140
|
+
);
|
|
141
|
+
return React2.createElement(
|
|
142
|
+
BillingContext.Provider,
|
|
143
|
+
{ value: contextValue },
|
|
144
|
+
React2.createElement(AppearanceProvider, { appearance, children })
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
function useOptionalBillingContext() {
|
|
148
|
+
return useContext(BillingContext);
|
|
149
|
+
}
|
|
150
|
+
function useBillingContext() {
|
|
151
|
+
const context = useOptionalBillingContext();
|
|
152
|
+
if (!context) {
|
|
153
|
+
throw new Error("useBillingContext must be used within a BillingProvider");
|
|
154
|
+
}
|
|
155
|
+
return context;
|
|
156
|
+
}
|
|
157
|
+
function useNozleClient() {
|
|
158
|
+
return useBillingContext().client;
|
|
159
|
+
}
|
|
160
|
+
var idleQueryClient = new QueryClient();
|
|
161
|
+
function catalogQuery(client) {
|
|
162
|
+
return {
|
|
163
|
+
queryKey: ["nozle", "plans", client?.baseUrl, client?.publishableKey],
|
|
164
|
+
queryFn: async ({ signal }) => {
|
|
165
|
+
const response = await client.catalogFetch("/api/v1/plans", { signal });
|
|
166
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
167
|
+
const data = await response.json();
|
|
168
|
+
return {
|
|
169
|
+
plans: data.plans ?? [],
|
|
170
|
+
families: Array.isArray(data.families) ? data.families : null,
|
|
171
|
+
nozleBranding: data.meta?.nozle_branding === true
|
|
172
|
+
};
|
|
173
|
+
},
|
|
174
|
+
staleTime: 5 * 6e4,
|
|
175
|
+
// The pricing page shows a load error at once; the app can call refetch().
|
|
176
|
+
retry: false
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function usePlans() {
|
|
180
|
+
const { client, queryClient } = useBillingContext();
|
|
181
|
+
const query = useQuery(catalogQuery(client), queryClient);
|
|
182
|
+
return {
|
|
183
|
+
plans: query.data?.plans ?? [],
|
|
184
|
+
families: query.data?.families ?? null,
|
|
185
|
+
isLoading: query.isPending,
|
|
186
|
+
error: query.error ?? null,
|
|
187
|
+
refetch: query.refetch
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// src/components/branding/logo.ts
|
|
192
|
+
var LOGO_VIEW_BOX = "0 0 3208 1122";
|
|
193
|
+
var LOGO_MARK_PATH = "M 1108.89 330.512 C 1131.49 326.989 1184.16 334.391 1207.63 338.298 L 1207.63 511.329 C 1203.66 515.246 1199.46 519.132 1195.4 522.972 L 1286.52 523.006 C 1282.75 519.093 1278.87 515.284 1274.89 511.585 L 1274.92 358.597 C 1311.02 374.213 1344.6 395.09 1374.58 420.548 C 1451.3 486.571 1498.83 580.231 1506.81 681.131 C 1515 782.133 1482.24 882.187 1415.89 958.782 C 1362.73 1020.34 1289.84 1065.92 1209.97 1082.75 C 1185.45 1087.92 1168.82 1089.01 1144.24 1090.82 C 1092.37 1092.55 1040.69 1083.81 992.278 1065.1 C 899.111 1029.72 824.189 955.46 783.642 864.858 C 743.243 772.802 740.63 668.558 776.367 574.594 C 807.572 492.815 865.841 424.187 941.477 380.133 C 955.897 371.807 970.834 365.19 986.004 358.415 L 986.997 511.83 C 982.504 514.698 977.232 518.881 972.988 522.219 C 982.441 521.793 993.966 522.74 1003.69 522.843 L 1066.51 523.012 C 1062.58 519.054 1058.6 514.702 1054 511.616 C 1053.71 494.114 1055.01 476.198 1054.99 458.475 L 1054 337.726 C 1074.93 333.524 1088.73 333.1 1108.89 330.512 z M 1136.49 834.878 C 1198.61 831.142 1246.02 777.893 1242.55 715.759 C 1239.08 653.625 1186.03 605.989 1123.88 609.196 C 1061.36 612.423 1013.37 665.861 1016.87 728.369 C 1020.36 790.878 1074 838.637 1136.49 834.878 z";
|
|
194
|
+
var LOGO_WORDMARK_PATH = "M2807.48 317.546C2812.82 316.693 2819.27 316.959 2824.49 316.891 3043.76 314.061 3223.51 517.798 3205.41 733.935 3204.36 746.553 3203.33 760.715 3203.2 773.228 3175.25 773.336 3148.36 772.242 3120.74 772.167L2889.16 773.329C2863.8 773.347 2650.02 771.22 2643.56 776.302 2643.49 776.742 2643.33 778.022 2643.41 778.366 2650.33 808.489 2668.08 838.036 2690.04 858.674 2726.37 892.826 2776.29 910.3 2825.39 908.765 2887.65 906.818 2929.65 884.535 2971.8 840.731L3187.64 841.821C3176.73 876.79 3145.09 927.003 3121.73 956.25 3057.95 1036.16 2965.03 1087.46 2863.43 1098.86 2764.86 1109.46 2666.11 1080.6 2588.77 1018.59 2504.68 952.597 2450.51 855.672 2438.38 749.464 2415.16 540.793 2579.74 336.515 2790.2 318.706 2795.97 318.218 2801.75 318.181 2807.48 317.546zM2836.5 625.92 3004.04 625.92C2976.56 571.597 2937.23 529.14 2878.08 508.386 2862.06 502.767 2834.68 498.432 2817.69 499.447 2811.66 500.426 2805.72 500.948 2799.66 501.626 2736.71 508.675 2678.99 552.56 2653.08 609.964 2650.95 614.667 2649.24 618.489 2648.22 623.618L2648.73 625.573C2666.65 626.805 2695.47 625.951 2714.08 625.95L2836.5 625.92zM350.026 327.534C351.059 327.376 351.817 327.251 352.866 327.196 440.33 322.588 528.279 352.28 593.965 409.932 660.794 468.083 703.294 549.252 713.018 637.303 717.397 678.934 715.385 718.263 715.162 759.723L714.249 949.463C714.221 978.726 713.328 1009.12 714.526 1038.31 715.585 1064.08 724.618 1074.23 742.925 1090.23 655.133 1089.48 574.896 1088.28 486.53 1091.14 506.936 1074.25 516.198 1062.6 516.991 1034.69 517.509 1016.48 517.681 998.316 517.701 980.062L517.678 871.454 518.327 744.743C518.417 708.921 519.234 677.156 513.6 641.605 504.197 582.258 457.155 536.074 396.911 528.341 387.723 526.897 378.419 526.337 369.124 526.669 330.893 527.948 296.354 540.985 270.002 569.309 252.159 588.394 239.802 611.944 234.238 637.471 228.538 663.791 228.617 711.497 228.796 738.55L229.47 833.874 229.051 964.879C229.043 985.892 228.914 1006.79 229.398 1027.79 230.072 1057 235.364 1070.31 256.803 1090.19 210.541 1092.85 167.594 1091.93 121.604 1091.28L2.29567 1090.29C23.4576 1071.55 31.3577 1057.22 32.9306 1028.58 34.6194 997.827 33.8844 967.966 33.5542 937.249L33.0591 779.859C32.8532 653.775 26.6617 550.025 115.68 447.849 180 374.022 252.997 336.686 350.026 327.534zM1552.98 329.783C1585.23 341.06 1694.79 336.137 1732.02 336.28L1987.95 337.925C2031.97 338.036 2076.24 335.822 2120.7 337.349 2118.21 357.823 2118.97 389.029 2118.96 410.256 2118.6 445.944 2118.97 481.634 2120.07 517.306L1961.06 704.338C1908.01 766.03 1851.33 830.755 1801.51 894.952 1819.57 894.053 1845.55 894.949 1864.37 894.958L1989.79 895.045C2037.99 895.176 2070.48 897.452 2119.04 886.152 2118.97 952.443 2120.33 1018.73 2118.99 1085 2118.85 1091.75 2120.17 1104.35 2118.4 1110.48L2115.98 1111.17C2070.03 1077.75 2034.35 1081.57 1981.3 1081.89L1888.49 1081.97 1544.82 1082.03C1545.37 1024.58 1544.47 968.65 1544.02 911.354 1563.44 886.164 1589.99 857.395 1611.19 832.365L1782.61 629.927C1814.21 592.718 1847.38 556.886 1876.98 518.016L1552 517.984C1551.65 496.963 1554.2 471.241 1553.87 449.778 1553.69 437.667 1553.18 425.595 1553.07 413.5L1552.98 329.783zM2373.44 4.75901C2374.2 55.5907 2372.32 106.262 2372.3 157.309L2372.39 599.153 2373.25 737.25C2373.29 771.822 2369.91 823.05 2379.51 856.818 2384.04 872.719 2405.1 883.099 2420.05 889.245L2420.03 1090.04C2375.69 1091.15 2312.39 1065.98 2279.04 1037.28 2271.95 1032.7 2261.78 1023.12 2255.56 1016.95 2219.14 980.786 2191.86 928.49 2181.81 878.203 2174.98 844.049 2176.85 799.114 2176.92 763.235L2177.02 625.401 2176.97 73.7487C2189.94 73.6898 2202.93 74.1053 2215.89 73.9192 2264.74 73.2173 2312.58 61.8792 2349.93 28.7315 2357.84 21.7113 2365.92 12.4727 2373.44 4.75901z";
|
|
195
|
+
var BrandingScope = createContext(false);
|
|
196
|
+
function useNozleBranding() {
|
|
197
|
+
const billing = useOptionalBillingContext();
|
|
198
|
+
const { data } = useQuery(
|
|
199
|
+
// Reads the catalog request the components already make: mounting this must not refetch a
|
|
200
|
+
// failed catalog, or a table that shows the error would loop back into loading.
|
|
201
|
+
{ ...catalogQuery(billing?.client), enabled: Boolean(billing), refetchOnMount: false, retryOnMount: false },
|
|
202
|
+
billing?.queryClient ?? idleQueryClient
|
|
203
|
+
);
|
|
204
|
+
return data?.nozleBranding === true;
|
|
205
|
+
}
|
|
206
|
+
function PoweredByNozle() {
|
|
207
|
+
const { root } = useAppearance();
|
|
208
|
+
return /* @__PURE__ */ jsxs(
|
|
209
|
+
"a",
|
|
210
|
+
{
|
|
211
|
+
href: "https://nozle.dev",
|
|
212
|
+
target: "_blank",
|
|
213
|
+
rel: "noopener noreferrer",
|
|
214
|
+
...root("poweredBy", {
|
|
215
|
+
...text.meta,
|
|
216
|
+
display: "flex",
|
|
217
|
+
alignItems: "center",
|
|
218
|
+
justifyContent: "center",
|
|
219
|
+
gap: "6px",
|
|
220
|
+
marginTop: "1rem",
|
|
221
|
+
textDecoration: "none"
|
|
222
|
+
}),
|
|
223
|
+
children: [
|
|
224
|
+
"Powered by",
|
|
225
|
+
/* @__PURE__ */ jsxs("svg", { viewBox: LOGO_VIEW_BOX, height: "14", width: "40", role: "img", "aria-label": "Nozle", style: { color: T.foreground }, children: [
|
|
226
|
+
/* @__PURE__ */ jsx("path", { fill: "#F34213", d: LOGO_MARK_PATH }),
|
|
227
|
+
/* @__PURE__ */ jsx("path", { fill: "currentColor", d: LOGO_WORDMARK_PATH })
|
|
228
|
+
] })
|
|
229
|
+
]
|
|
230
|
+
}
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
function NozleBranding({ show, children }) {
|
|
234
|
+
const nested = useContext(BrandingScope);
|
|
235
|
+
const fromCatalog = useNozleBranding();
|
|
236
|
+
return /* @__PURE__ */ jsxs(BrandingScope.Provider, { value: true, children: [
|
|
237
|
+
children,
|
|
238
|
+
!nested && (show ?? fromCatalog) && /* @__PURE__ */ jsx(PoweredByNozle, {})
|
|
239
|
+
] });
|
|
240
|
+
}
|
|
7
241
|
|
|
8
242
|
// src/components/portal/client.ts
|
|
9
243
|
var BillingPortalError = class extends Error {
|
|
@@ -96,7 +330,7 @@ var identityQuery = `query SdkPortalIdentity {
|
|
|
96
330
|
addressLine1 addressLine2 city state zipcode country
|
|
97
331
|
shippingAddress { addressLine1 addressLine2 city state zipcode country }
|
|
98
332
|
}
|
|
99
|
-
customerPortalOrganization { id name }
|
|
333
|
+
customerPortalOrganization { id name nozleBranding }
|
|
100
334
|
}`;
|
|
101
335
|
var subscriptionsQuery = `query SdkPortalSubscriptions($page: Int) {
|
|
102
336
|
customerPortalSubscriptions(status: [active], limit: 20, page: $page) {
|
|
@@ -145,40 +379,55 @@ var topUpMutation = `mutation SdkPortalTopUp($input: CreateCustomerPortalWalletT
|
|
|
145
379
|
}`;
|
|
146
380
|
|
|
147
381
|
// src/components/portal/styles.ts
|
|
382
|
+
var line = `${T.borderWidth} solid ${T.border}`;
|
|
383
|
+
var r = (factor) => `calc(${T.radius} * ${factor})`;
|
|
148
384
|
var portalStyles = `
|
|
149
|
-
.nozle-billing-portal{color
|
|
385
|
+
.nozle-billing-portal{color:${T.foreground};background:${T.background};font:${T.fontBody};box-sizing:border-box;width:100%;min-width:0;padding:clamp(18px,4vw,40px);border-radius:${T.radius}}
|
|
150
386
|
.nozle-billing-portal *{box-sizing:border-box}
|
|
151
387
|
.nozle-billing-portal h2,.nozle-billing-portal h3,.nozle-billing-portal p{margin:0}
|
|
152
|
-
.nozle-billing-portal h2{font
|
|
153
|
-
.nozle-billing-portal h3{font
|
|
388
|
+
.nozle-billing-portal h2{font:${T.fontTitle}}
|
|
389
|
+
.nozle-billing-portal h3{font:${T.fontHeading}}
|
|
154
390
|
.nozle-billing-portal header{margin-bottom:30px}
|
|
155
|
-
.nozle-billing-portal section{margin-top:28px;padding-top:24px;border-top
|
|
156
|
-
.nozle-billing-portal button,.nozle-billing-portal .nzp-link{appearance:none;display:inline-flex;align-items:center;justify-content:center;font
|
|
157
|
-
.nozle-billing-portal button:hover,.nozle-billing-portal .nzp-link:hover{background
|
|
391
|
+
.nozle-billing-portal section{margin-top:28px;padding-top:24px;border-top:${line}}
|
|
392
|
+
.nozle-billing-portal button,.nozle-billing-portal .nzp-link{appearance:none;display:inline-flex;align-items:center;justify-content:center;font:${T.fontAction};border:var(--nozle-button-border-width, ${T.borderWidth}) solid ${T.input};border-radius:var(--nozle-button-radius, ${r(0.6)});background:${T.card};color:${T.foreground};padding:9px 14px;cursor:pointer;text-decoration:none;white-space:normal}
|
|
393
|
+
.nozle-billing-portal button:hover,.nozle-billing-portal .nzp-link:hover{background:${T.muted}}
|
|
158
394
|
.nozle-billing-portal button:disabled{opacity:.55;cursor:wait}
|
|
159
|
-
.nozle-billing-portal :focus-visible{outline:2px solid
|
|
160
|
-
.nozle-billing-portal .nzp-primary{background:var(--nozle-
|
|
161
|
-
.nozle-billing-portal .nzp-primary:hover{filter:brightness(.92);background
|
|
162
|
-
.nozle-billing-portal .nzp-muted{color
|
|
395
|
+
.nozle-billing-portal :focus-visible{outline:2px solid ${T.primary};outline-offset:3px}
|
|
396
|
+
.nozle-billing-portal .nzp-primary{background:${T.primary};border-color:var(--nozle-button-border, ${T.primary});box-shadow:var(--nozle-button-shadow, none);color:${T.primaryForeground}}
|
|
397
|
+
.nozle-billing-portal .nzp-primary:hover{filter:brightness(.92);background:${T.primary}}
|
|
398
|
+
.nozle-billing-portal .nzp-muted{color:${T.mutedForeground}}
|
|
399
|
+
.nozle-billing-portal .nzp-notice{color:${T.successText}}
|
|
163
400
|
.nozle-billing-portal .nzp-row{display:flex;align-items:center;justify-content:space-between;gap:14px;flex-wrap:wrap}
|
|
164
401
|
.nozle-billing-portal .nzp-stack{display:grid;gap:12px;margin-top:16px}
|
|
165
|
-
.nozle-billing-portal .nzp-card{border
|
|
166
|
-
.nozle-billing-portal .nzp-
|
|
167
|
-
.nozle-billing-portal .nzp-
|
|
168
|
-
.nozle-billing-portal .nzp-
|
|
169
|
-
.nozle-billing-portal .nzp-
|
|
170
|
-
.nozle-billing-portal .nzp-
|
|
171
|
-
.nozle-billing-portal
|
|
172
|
-
.nozle-billing-portal
|
|
173
|
-
.nozle-billing-portal
|
|
174
|
-
.nozle-billing-portal
|
|
175
|
-
.nozle-billing-portal
|
|
402
|
+
.nozle-billing-portal .nzp-card{background:${T.card};border:${line};border-radius:${r(0.85)};box-shadow:${T.shadow};padding:20px;min-width:0}
|
|
403
|
+
.nozle-billing-portal .nzp-card-actions{display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin-top:16px;padding-top:16px;border-top:${line}}
|
|
404
|
+
.nozle-billing-portal .nzp-card-actions>.nozle-plan-change,.nozle-billing-portal .nzp-card-actions>.nozle-cancellation,.nozle-billing-portal .nzp-card-actions .nzpc-actions{display:contents}
|
|
405
|
+
.nozle-billing-portal .nzp-card-actions>.nozle-plan-change>:not(.nzpc-actions),.nozle-billing-portal .nzp-card-actions>.nozle-cancellation>:not(button){flex-basis:100%;margin:0}
|
|
406
|
+
.nozle-billing-portal .nzp-card-actions>.nozle-cancellation>button{margin-left:auto}
|
|
407
|
+
.nozle-billing-portal .nzp-price{font:${T.fontMetric};font-variant-numeric:tabular-nums;margin:12px 0 4px}
|
|
408
|
+
.nozle-billing-portal .nzp-price .nzp-muted{font:${T.fontMeta}}
|
|
409
|
+
.nozle-billing-portal .nzp-badge{display:inline-block;font:${T.labelFont};letter-spacing:${T.labelSpacing};text-transform:${T.labelTransform};border-radius:${r(0.4)};background:${T.accent};color:${T.accentForeground};padding:3px 7px}
|
|
410
|
+
.nozle-billing-portal .nzp-empty{border:${T.borderWidth} dashed ${T.border};border-radius:${r(0.65)};padding:22px;text-align:center;color:${T.mutedForeground};margin-top:16px}
|
|
411
|
+
.nozle-billing-portal .nzp-error{color:${T.dangerText};background:${T.dangerSoft};border:${T.borderWidth} solid ${T.dangerBorder};border-radius:${r(0.65)};padding:14px;margin:16px 0;display:flex;gap:12px;align-items:center;flex-wrap:wrap}
|
|
412
|
+
.nozle-billing-portal .nzp-table{overflow-x:auto;margin-top:16px;background:${T.card};border:${line};border-radius:${T.radius}}
|
|
413
|
+
.nozle-billing-portal table{width:100%;border-collapse:collapse;text-align:left;font:inherit}
|
|
414
|
+
.nozle-billing-portal th{height:44px;padding:0 20px;font:${T.labelFont};letter-spacing:${T.labelSpacing};text-transform:${T.labelTransform};color:${T.mutedForeground};background:${T.tableHeader};border-bottom:${line};white-space:nowrap}
|
|
415
|
+
.nozle-billing-portal td{height:48px;padding:14px 20px;border-bottom:${T.borderWidth} solid ${T.rowLine};white-space:nowrap}
|
|
416
|
+
.nozle-billing-portal tbody tr:last-child td{border-bottom:0}
|
|
417
|
+
.nozle-billing-portal .nzp-num{text-align:right}
|
|
418
|
+
.nozle-billing-portal td.nzp-num{font-family:${T.fontNumbers};font-variant-numeric:tabular-nums}
|
|
419
|
+
.nozle-billing-portal .nzp-search{display:flex;font-weight:400;align-items:center;gap:8px;flex:1;min-width:min(100%,200px);height:40px;padding:0 12px;background:${T.card};border:${T.borderWidth} solid ${T.input};border-radius:${r(0.7)};color:${T.mutedForeground}}
|
|
420
|
+
.nozle-billing-portal .nzp-search:focus-within{border-color:${T.foreground}}
|
|
421
|
+
.nozle-billing-portal .nzp-search input{flex:1;height:100%;border:0;padding:0;background:transparent;color:${T.foreground};outline:none}
|
|
422
|
+
.nozle-billing-portal .nzp-search input::placeholder{color:${T.mutedForeground}}
|
|
423
|
+
.nozle-billing-portal label{display:grid;gap:6px;font-weight:500;min-width:0}
|
|
424
|
+
.nozle-billing-portal input,.nozle-billing-portal select{width:100%;min-width:0;font:inherit;color:inherit;background:${T.card};border:${line};border-radius:${r(0.5)};padding:9px 10px}
|
|
176
425
|
.nozle-billing-portal .nzp-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,220px),1fr));gap:16px;margin:20px 0}
|
|
177
426
|
.nozle-billing-portal .nzp-actions{display:flex;align-items:flex-end;gap:10px;flex-wrap:wrap;margin-top:20px}
|
|
178
427
|
.nozle-billing-portal dl{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,210px),1fr));gap:18px;margin:16px 0 0}
|
|
179
|
-
.nozle-billing-portal dt{font
|
|
428
|
+
.nozle-billing-portal dt{font:${T.fontMeta};color:${T.mutedForeground}}
|
|
180
429
|
.nozle-billing-portal dd{margin:3px 0 0;overflow-wrap:anywhere}
|
|
181
|
-
.nozle-billing-portal footer{margin-top:30px;text-align:center;color
|
|
430
|
+
.nozle-billing-portal footer{margin-top:30px;text-align:center;color:${T.mutedForeground};font:${T.fontMeta}}
|
|
182
431
|
`;
|
|
183
432
|
|
|
184
433
|
// src/components/portal/timezones.ts
|
|
@@ -362,6 +611,25 @@ function compareDecimalStrings(left, right) {
|
|
|
362
611
|
}
|
|
363
612
|
return magnitude * a.sign;
|
|
364
613
|
}
|
|
614
|
+
function formatDecimalString(value, maximumFractionDigits = 12) {
|
|
615
|
+
const parsed = parseDecimal(value);
|
|
616
|
+
if (!parsed) return value;
|
|
617
|
+
const fractionLimit = Math.max(
|
|
618
|
+
0,
|
|
619
|
+
Math.min(30, Math.trunc(maximumFractionDigits))
|
|
620
|
+
);
|
|
621
|
+
const firstGroupLength = parsed.integer.length % 3 || 3;
|
|
622
|
+
const groups = [parsed.integer.slice(0, firstGroupLength)];
|
|
623
|
+
for (let index = firstGroupLength; index < parsed.integer.length; index += 3) {
|
|
624
|
+
groups.push(parsed.integer.slice(index, index + 3));
|
|
625
|
+
}
|
|
626
|
+
const groupedInteger = groups.join(",");
|
|
627
|
+
const fraction = parsed.fraction.slice(0, fractionLimit);
|
|
628
|
+
return `${parsed.sign < 0 ? "-" : ""}${groupedInteger}${fraction ? `.${fraction}` : ""}`;
|
|
629
|
+
}
|
|
630
|
+
function creditUnitLabel(value, unitName) {
|
|
631
|
+
return compareDecimalStrings(value, "1") === 0 ? unitName : `${unitName}s`;
|
|
632
|
+
}
|
|
365
633
|
|
|
366
634
|
// src/components/portal/amounts.ts
|
|
367
635
|
function walletAmountCents(credits, rate, currencyDigits) {
|
|
@@ -382,16 +650,16 @@ function walletAmountCents(credits, rate, currencyDigits) {
|
|
|
382
650
|
return `${padded.slice(0, -scale)}.${padded.slice(-scale)}`;
|
|
383
651
|
}
|
|
384
652
|
var styles = `
|
|
385
|
-
.nozle-cancellation{font
|
|
386
|
-
.nozle-cancellation button{font
|
|
653
|
+
.nozle-cancellation{font:${T.fontBody};color:${T.foreground}}
|
|
654
|
+
.nozle-cancellation button{font:${T.fontAction};padding:9px 14px;border:var(--nozle-button-border-width, ${T.borderWidth}) solid ${T.input};border-radius:var(--nozle-button-radius, calc(${T.radius} * 0.6));background:${T.card};color:inherit;cursor:pointer}
|
|
387
655
|
.nozle-cancellation button:disabled{opacity:.55;cursor:wait}
|
|
388
|
-
.nozle-cancellation :focus-visible{outline:2px solid
|
|
656
|
+
.nozle-cancellation :focus-visible{outline:2px solid ${T.primary};outline-offset:3px}
|
|
389
657
|
.nozle-cancellation .nzc-backdrop{position:fixed;inset:0;background:#0006;z-index:1000;display:flex;align-items:center;justify-content:center;padding:20px}
|
|
390
|
-
.nozle-cancellation .nzc-dialog{box-sizing:border-box;background
|
|
391
|
-
.nozle-cancellation .nzc-dialog h3{margin:0 0 16px;font-
|
|
392
|
-
.nozle-cancellation .nzc-dialog p{margin:12px 0
|
|
658
|
+
.nozle-cancellation .nzc-dialog{box-sizing:border-box;background:${T.card};border:${T.borderWidth} solid ${T.border};border-radius:${T.radius};padding:24px;width:100%;max-width:440px;max-height:90dvh;overflow:auto;box-shadow:${T.shadow},0 12px 48px #0003}
|
|
659
|
+
.nozle-cancellation .nzc-dialog h3{margin:0 0 16px;font:${T.fontTitle};letter-spacing:normal;color:${T.foreground}}
|
|
660
|
+
.nozle-cancellation .nzc-dialog p{margin:12px 0}
|
|
393
661
|
.nozle-cancellation .nzc-buttons{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:10px;margin-top:20px}
|
|
394
|
-
.nozle-cancellation [role=alert]{color
|
|
662
|
+
.nozle-cancellation [role=alert],.nozle-cancellation .nzc-danger{color:${T.dangerText}}
|
|
395
663
|
`;
|
|
396
664
|
function formatDate(value, locale, timezone) {
|
|
397
665
|
const timeZone = timezone.startsWith("TZ_") ? portalTimezones[timezone] ?? "UTC" : timezone;
|
|
@@ -421,8 +689,10 @@ function CancellationFlow({
|
|
|
421
689
|
onError,
|
|
422
690
|
locale = "en-US",
|
|
423
691
|
timezone = "UTC",
|
|
424
|
-
nonce
|
|
692
|
+
nonce,
|
|
693
|
+
appearance
|
|
425
694
|
}) {
|
|
695
|
+
const { root } = useAppearance(appearance);
|
|
426
696
|
const [open, setOpen] = useState(false);
|
|
427
697
|
const [busy, setBusy] = useState(false);
|
|
428
698
|
const [preview, setPreview] = useState();
|
|
@@ -609,7 +879,7 @@ function CancellationFlow({
|
|
|
609
879
|
);
|
|
610
880
|
});
|
|
611
881
|
}
|
|
612
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
882
|
+
return /* @__PURE__ */ jsxs("div", { ...root("cancellationControl", void 0, "nozle-cancellation"), children: [
|
|
613
883
|
/* @__PURE__ */ jsx("style", { nonce, children: styles }),
|
|
614
884
|
notice && /* @__PURE__ */ jsx("p", { role: "status", children: notice }),
|
|
615
885
|
active && !expired && /* @__PURE__ */ jsx("button", { ref: trigger, type: "button", onClick: () => void loadPreview(), children: ending ? "Keep my subscription" : "Cancel subscription" }),
|
|
@@ -694,6 +964,7 @@ function CancellationFlow({
|
|
|
694
964
|
"button",
|
|
695
965
|
{
|
|
696
966
|
type: "button",
|
|
967
|
+
className: preview.operation === "cancel" ? "nzc-danger" : void 0,
|
|
697
968
|
disabled: busy,
|
|
698
969
|
onClick: () => void confirm(),
|
|
699
970
|
children: request.current ? "Check status / retry" : preview.operation === "cancel" ? "Confirm cancellation" : "Confirm keep subscription"
|
|
@@ -705,66 +976,6 @@ function CancellationFlow({
|
|
|
705
976
|
) })
|
|
706
977
|
] });
|
|
707
978
|
}
|
|
708
|
-
function createClient(publishableKey, baseUrl) {
|
|
709
|
-
let end = baseUrl.length;
|
|
710
|
-
while (end > 0 && baseUrl[end - 1] === "/") end--;
|
|
711
|
-
const base = baseUrl.slice(0, end);
|
|
712
|
-
return {
|
|
713
|
-
publishableKey,
|
|
714
|
-
baseUrl: base,
|
|
715
|
-
catalogFetch(path, init) {
|
|
716
|
-
return fetch(`${base}${path}`, {
|
|
717
|
-
...init,
|
|
718
|
-
headers: {
|
|
719
|
-
Authorization: `Bearer ${publishableKey}`,
|
|
720
|
-
"Content-Type": "application/json",
|
|
721
|
-
...init?.headers
|
|
722
|
-
}
|
|
723
|
-
});
|
|
724
|
-
}
|
|
725
|
-
};
|
|
726
|
-
}
|
|
727
|
-
var BillingContext = createContext(null);
|
|
728
|
-
function BillingProvider({
|
|
729
|
-
publishableKey,
|
|
730
|
-
createCheckout,
|
|
731
|
-
verifyCheckout,
|
|
732
|
-
getCheckoutStatus,
|
|
733
|
-
baseUrl = "https://api.nozle.app/engine",
|
|
734
|
-
children
|
|
735
|
-
}) {
|
|
736
|
-
if (!publishableKey.startsWith("pk_")) {
|
|
737
|
-
throw new Error(
|
|
738
|
-
"BillingProvider publishableKey must be a publishable key (pk_)"
|
|
739
|
-
);
|
|
740
|
-
}
|
|
741
|
-
const client = useMemo(
|
|
742
|
-
() => createClient(publishableKey, baseUrl),
|
|
743
|
-
[publishableKey, baseUrl]
|
|
744
|
-
);
|
|
745
|
-
const contextValue = useMemo(
|
|
746
|
-
() => ({ client, createCheckout, verifyCheckout, getCheckoutStatus }),
|
|
747
|
-
[client, createCheckout, verifyCheckout, getCheckoutStatus]
|
|
748
|
-
);
|
|
749
|
-
return React.createElement(
|
|
750
|
-
BillingContext.Provider,
|
|
751
|
-
{ value: contextValue },
|
|
752
|
-
children
|
|
753
|
-
);
|
|
754
|
-
}
|
|
755
|
-
function useOptionalBillingContext() {
|
|
756
|
-
return useContext(BillingContext);
|
|
757
|
-
}
|
|
758
|
-
function useBillingContext() {
|
|
759
|
-
const context = useOptionalBillingContext();
|
|
760
|
-
if (!context) {
|
|
761
|
-
throw new Error("useBillingContext must be used within a BillingProvider");
|
|
762
|
-
}
|
|
763
|
-
return context;
|
|
764
|
-
}
|
|
765
|
-
function useNozleClient() {
|
|
766
|
-
return useBillingContext().client;
|
|
767
|
-
}
|
|
768
979
|
|
|
769
980
|
// src/components/billing/razorpay-checkout.ts
|
|
770
981
|
var loadingScript;
|
|
@@ -979,8 +1190,10 @@ function CheckoutButton({
|
|
|
979
1190
|
onStripeClientSecret,
|
|
980
1191
|
onSuccess,
|
|
981
1192
|
onComplete,
|
|
982
|
-
onScheduled
|
|
1193
|
+
onScheduled,
|
|
1194
|
+
appearance
|
|
983
1195
|
}) {
|
|
1196
|
+
const { root, vars } = useAppearance(appearance);
|
|
984
1197
|
const { createCheckout, verifyCheckout, getCheckoutStatus } = useBillingContext();
|
|
985
1198
|
const attempt = useRef(void 0);
|
|
986
1199
|
const inFlight = useRef(false);
|
|
@@ -1043,24 +1256,13 @@ function CheckoutButton({
|
|
|
1043
1256
|
type: "button",
|
|
1044
1257
|
onClick: () => void handleClick(),
|
|
1045
1258
|
disabled: loading,
|
|
1046
|
-
className,
|
|
1047
|
-
style: {
|
|
1048
|
-
padding: "0.75rem 1.5rem",
|
|
1049
|
-
borderRadius: "var(--nozle-radius, 0.5rem)",
|
|
1050
|
-
border: "none",
|
|
1051
|
-
background: "var(--nozle-primary, var(--primary))",
|
|
1052
|
-
color: "var(--nozle-primary-foreground, var(--primary-foreground))",
|
|
1053
|
-
cursor: loading ? "not-allowed" : "pointer",
|
|
1054
|
-
fontWeight: 500,
|
|
1055
|
-
opacity: loading ? 0.7 : 1,
|
|
1056
|
-
...style
|
|
1057
|
-
},
|
|
1259
|
+
...root("checkoutButton", { ...buttonStyle("primary", loading), ...style }, className),
|
|
1058
1260
|
"aria-busy": loading,
|
|
1059
1261
|
children: loading ? "Loading..." : label
|
|
1060
1262
|
}
|
|
1061
1263
|
),
|
|
1062
|
-
processing && /* @__PURE__ */ jsx("span", { role: "status", children: "Payment is processing. Confirmation may take a little while." }),
|
|
1063
|
-
error && /* @__PURE__ */ jsx("span", { role: "alert", children: error.message })
|
|
1264
|
+
processing && /* @__PURE__ */ jsx("span", { role: "status", style: { ...vars, ...text.body }, children: "Payment is processing. Confirmation may take a little while." }),
|
|
1265
|
+
error && /* @__PURE__ */ jsx("span", { role: "alert", style: { ...vars, ...text.body, color: T.dangerText }, children: error.message })
|
|
1064
1266
|
] });
|
|
1065
1267
|
}
|
|
1066
1268
|
var CheckoutContext = createContext(null);
|
|
@@ -1071,23 +1273,62 @@ function useCheckout() {
|
|
|
1071
1273
|
}
|
|
1072
1274
|
return ctx;
|
|
1073
1275
|
}
|
|
1074
|
-
function
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1276
|
+
function rgb(color) {
|
|
1277
|
+
const match = color?.match(/^rgba?\(\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)/);
|
|
1278
|
+
return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
|
|
1279
|
+
}
|
|
1280
|
+
function stripeColor(color) {
|
|
1281
|
+
return rgb(color) ? color : color?.startsWith("#") ? color : void 0;
|
|
1282
|
+
}
|
|
1283
|
+
function toStripeAppearance(colors) {
|
|
1284
|
+
const background = rgb(colors.background);
|
|
1285
|
+
const dark = background ? (0.2126 * background[0] + 0.7152 * background[1] + 0.0722 * background[2]) / 255 < 0.5 : false;
|
|
1286
|
+
const variables = Object.fromEntries(
|
|
1287
|
+
Object.entries({
|
|
1288
|
+
colorPrimary: stripeColor(colors.primary),
|
|
1289
|
+
colorBackground: stripeColor(colors.background),
|
|
1290
|
+
colorText: stripeColor(colors.foreground),
|
|
1291
|
+
colorDanger: stripeColor(colors.danger),
|
|
1292
|
+
borderRadius: colors.radius,
|
|
1293
|
+
fontFamily: colors.font
|
|
1294
|
+
}).filter(([, value]) => value)
|
|
1295
|
+
);
|
|
1296
|
+
return { theme: dark ? "night" : "stripe", variables };
|
|
1297
|
+
}
|
|
1298
|
+
function useStripeAppearance(appearanceKey) {
|
|
1299
|
+
const probe = useRef(null);
|
|
1300
|
+
const [stripeAppearance, setStripeAppearance] = useState(null);
|
|
1301
|
+
useEffect(() => {
|
|
1302
|
+
if (!probe.current) return;
|
|
1303
|
+
const style = getComputedStyle(probe.current);
|
|
1304
|
+
setStripeAppearance(
|
|
1305
|
+
toStripeAppearance({
|
|
1306
|
+
primary: style.color,
|
|
1307
|
+
background: style.backgroundColor,
|
|
1308
|
+
foreground: style.borderTopColor,
|
|
1309
|
+
danger: style.borderBottomColor,
|
|
1310
|
+
radius: style.borderTopLeftRadius,
|
|
1311
|
+
font: style.fontFamily
|
|
1312
|
+
})
|
|
1313
|
+
);
|
|
1314
|
+
}, [appearanceKey]);
|
|
1315
|
+
const probeElement = /* @__PURE__ */ jsx(
|
|
1316
|
+
"span",
|
|
1317
|
+
{
|
|
1318
|
+
ref: probe,
|
|
1319
|
+
"aria-hidden": "true",
|
|
1320
|
+
style: {
|
|
1321
|
+
display: "none",
|
|
1322
|
+
color: T.primary,
|
|
1323
|
+
backgroundColor: T.card,
|
|
1324
|
+
borderTop: `1px solid ${T.foreground}`,
|
|
1325
|
+
borderBottom: `1px solid ${T.danger}`,
|
|
1326
|
+
borderRadius: T.radius,
|
|
1327
|
+
fontFamily: T.font
|
|
1328
|
+
}
|
|
1089
1329
|
}
|
|
1090
|
-
|
|
1330
|
+
);
|
|
1331
|
+
return { probeElement, stripeAppearance };
|
|
1091
1332
|
}
|
|
1092
1333
|
function CheckoutInner({
|
|
1093
1334
|
submitLabel,
|
|
@@ -1097,10 +1338,12 @@ function CheckoutInner({
|
|
|
1097
1338
|
className,
|
|
1098
1339
|
style,
|
|
1099
1340
|
returnUrl,
|
|
1100
|
-
children
|
|
1341
|
+
children,
|
|
1342
|
+
appearance
|
|
1101
1343
|
}) {
|
|
1102
1344
|
const stripe = useStripe();
|
|
1103
1345
|
const elements = useElements();
|
|
1346
|
+
const { el } = useAppearance(appearance);
|
|
1104
1347
|
const [isProcessing, setIsProcessing] = useState(false);
|
|
1105
1348
|
const [error, setError] = useState(null);
|
|
1106
1349
|
const [isReady, setIsReady] = useState(false);
|
|
@@ -1134,7 +1377,7 @@ function CheckoutInner({
|
|
|
1134
1377
|
setIsProcessing(false);
|
|
1135
1378
|
}
|
|
1136
1379
|
}
|
|
1137
|
-
return /* @__PURE__ */ jsx(CheckoutContext.Provider, { value: { confirmPayment, isProcessing, error }, children: /* @__PURE__ */ jsxs("div", {
|
|
1380
|
+
return /* @__PURE__ */ jsx(CheckoutContext.Provider, { value: { confirmPayment, isProcessing, error }, children: /* @__PURE__ */ jsxs("div", { children: [
|
|
1138
1381
|
/* @__PURE__ */ jsx("style", { children: `@keyframes nozle-skeleton-pulse { 0%{background-position:200% 0} 100%{background-position:-200% 0} }` }),
|
|
1139
1382
|
!isReady && /* @__PURE__ */ jsx(
|
|
1140
1383
|
"div",
|
|
@@ -1142,8 +1385,8 @@ function CheckoutInner({
|
|
|
1142
1385
|
"data-testid": "checkout-skeleton",
|
|
1143
1386
|
style: {
|
|
1144
1387
|
height: "200px",
|
|
1145
|
-
borderRadius:
|
|
1146
|
-
background:
|
|
1388
|
+
borderRadius: T.radius,
|
|
1389
|
+
background: `linear-gradient(90deg, ${T.muted} 25%, ${T.skeleton} 50%, ${T.muted} 75%)`,
|
|
1147
1390
|
backgroundSize: "200% 100%",
|
|
1148
1391
|
animation: "nozle-skeleton-pulse 1.5s ease-in-out infinite"
|
|
1149
1392
|
}
|
|
@@ -1158,16 +1401,7 @@ function CheckoutInner({
|
|
|
1158
1401
|
}
|
|
1159
1402
|
}
|
|
1160
1403
|
),
|
|
1161
|
-
error !== null && /* @__PURE__ */ jsx(
|
|
1162
|
-
"p",
|
|
1163
|
-
{
|
|
1164
|
-
style: {
|
|
1165
|
-
color: "var(--nozle-destructive, #dc2626)",
|
|
1166
|
-
margin: "0.5rem 0 0"
|
|
1167
|
-
},
|
|
1168
|
-
children: error.message
|
|
1169
|
-
}
|
|
1170
|
-
),
|
|
1404
|
+
error !== null && /* @__PURE__ */ jsx("p", { ...el("checkout__error", { color: T.dangerText, margin: "0.5rem 0 0" }), children: error.message }),
|
|
1171
1405
|
children ?? /* @__PURE__ */ jsx(
|
|
1172
1406
|
"button",
|
|
1173
1407
|
{
|
|
@@ -1175,18 +1409,7 @@ function CheckoutInner({
|
|
|
1175
1409
|
onClick: () => void confirmPayment(),
|
|
1176
1410
|
disabled: isProcessing || !stripe,
|
|
1177
1411
|
"aria-busy": isProcessing,
|
|
1178
|
-
|
|
1179
|
-
padding: "0.75rem 1.5rem",
|
|
1180
|
-
borderRadius: "var(--nozle-radius, 0.5rem)",
|
|
1181
|
-
border: "none",
|
|
1182
|
-
background: "var(--nozle-primary, var(--primary))",
|
|
1183
|
-
color: "var(--nozle-primary-foreground, var(--primary-foreground))",
|
|
1184
|
-
cursor: isProcessing ? "not-allowed" : "pointer",
|
|
1185
|
-
fontWeight: 500,
|
|
1186
|
-
opacity: isProcessing ? 0.7 : 1,
|
|
1187
|
-
marginTop: "1rem",
|
|
1188
|
-
width: "100%"
|
|
1189
|
-
},
|
|
1412
|
+
...el("checkout__submit", { ...buttonStyle("primary", isProcessing), marginTop: "1rem", width: "100%" }),
|
|
1190
1413
|
children: isProcessing ? "Processing..." : submitLabel ?? "Pay now"
|
|
1191
1414
|
}
|
|
1192
1415
|
)
|
|
@@ -1204,19 +1427,21 @@ function StripeCheckout({
|
|
|
1204
1427
|
onReady,
|
|
1205
1428
|
className,
|
|
1206
1429
|
style,
|
|
1207
|
-
children
|
|
1430
|
+
children,
|
|
1431
|
+
appearance: themeAppearance
|
|
1208
1432
|
}) {
|
|
1433
|
+
const { root } = useAppearance(themeAppearance);
|
|
1434
|
+
const { probeElement, stripeAppearance } = useStripeAppearance(JSON.stringify(root("checkout").style));
|
|
1209
1435
|
const stripePromise = useMemo(
|
|
1210
1436
|
() => loadStripe(publishableKey, stripeAccount ? { stripeAccount } : void 0),
|
|
1211
1437
|
[publishableKey, stripeAccount]
|
|
1212
1438
|
);
|
|
1213
|
-
const appearance = useMemo(() => buildStripeAppearance(), []);
|
|
1214
1439
|
const embeddedOptions = useMemo(
|
|
1215
1440
|
() => ({ clientSecret, onComplete }),
|
|
1216
1441
|
[clientSecret, onComplete]
|
|
1217
1442
|
);
|
|
1218
1443
|
if (clientSecret.startsWith("cs_")) {
|
|
1219
|
-
return /* @__PURE__ */ jsx("div", {
|
|
1444
|
+
return /* @__PURE__ */ jsx("div", { ...root("checkout", { ...text.body, ...style }, className), children: /* @__PURE__ */ jsx(
|
|
1220
1445
|
EmbeddedCheckoutProvider,
|
|
1221
1446
|
{
|
|
1222
1447
|
stripe: stripePromise,
|
|
@@ -1225,23 +1450,26 @@ function StripeCheckout({
|
|
|
1225
1450
|
}
|
|
1226
1451
|
) });
|
|
1227
1452
|
}
|
|
1228
|
-
const options = { clientSecret, appearance };
|
|
1229
|
-
return /* @__PURE__ */
|
|
1230
|
-
|
|
1231
|
-
{
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1453
|
+
const options = stripeAppearance && { clientSecret, appearance: stripeAppearance };
|
|
1454
|
+
return /* @__PURE__ */ jsxs("div", { ...root("checkout", { ...text.body, ...style }, className), children: [
|
|
1455
|
+
probeElement,
|
|
1456
|
+
options && /* @__PURE__ */ jsx(Elements, { stripe: stripePromise, options, children: /* @__PURE__ */ jsx(
|
|
1457
|
+
CheckoutInner,
|
|
1458
|
+
{
|
|
1459
|
+
submitLabel,
|
|
1460
|
+
onSuccess,
|
|
1461
|
+
onError,
|
|
1462
|
+
onReady,
|
|
1463
|
+
returnUrl,
|
|
1464
|
+
children,
|
|
1465
|
+
appearance: themeAppearance
|
|
1466
|
+
}
|
|
1467
|
+
) }, clientSecret)
|
|
1468
|
+
] });
|
|
1242
1469
|
}
|
|
1243
1470
|
function ProviderCheckout(props) {
|
|
1244
1471
|
const billing = useOptionalBillingContext();
|
|
1472
|
+
const { root, el } = useAppearance(props.appearance);
|
|
1245
1473
|
const [busy, setBusy] = useState(false);
|
|
1246
1474
|
const [error, setError] = useState(null);
|
|
1247
1475
|
const [processing, setProcessing] = useState(false);
|
|
@@ -1291,17 +1519,19 @@ function ProviderCheckout(props) {
|
|
|
1291
1519
|
submitLabel: props.submitLabel,
|
|
1292
1520
|
onSuccess: props.onSuccess,
|
|
1293
1521
|
onComplete: props.onComplete,
|
|
1294
|
-
onError: props.onError
|
|
1522
|
+
onError: props.onError,
|
|
1523
|
+
className: props.className,
|
|
1524
|
+
appearance: props.appearance
|
|
1295
1525
|
}
|
|
1296
1526
|
);
|
|
1297
1527
|
}
|
|
1298
|
-
return /* @__PURE__ */ jsx("p", { role: "alert", children: "Payment setup is incomplete. Contact support to continue." });
|
|
1528
|
+
return /* @__PURE__ */ jsx("p", { ...root("checkout", { ...text.body, color: T.dangerText }, props.className), role: "alert", children: "Payment setup is incomplete. Contact support to continue." });
|
|
1299
1529
|
}
|
|
1300
1530
|
return /* @__PURE__ */ jsx(
|
|
1301
1531
|
CheckoutContext.Provider,
|
|
1302
1532
|
{
|
|
1303
1533
|
value: { confirmPayment: pay, isProcessing: busy, error },
|
|
1304
|
-
children: /* @__PURE__ */ jsxs("div", {
|
|
1534
|
+
children: /* @__PURE__ */ jsxs("div", { ...root("checkout", text.body, props.className), children: [
|
|
1305
1535
|
/* @__PURE__ */ jsx(
|
|
1306
1536
|
"button",
|
|
1307
1537
|
{
|
|
@@ -1309,28 +1539,30 @@ function ProviderCheckout(props) {
|
|
|
1309
1539
|
disabled: busy,
|
|
1310
1540
|
"aria-busy": busy,
|
|
1311
1541
|
onClick: () => void pay(),
|
|
1542
|
+
...el("checkout__submit", buttonStyle("primary", busy)),
|
|
1312
1543
|
children: busy ? "Processing\u2026" : props.submitLabel || "Pay now"
|
|
1313
1544
|
}
|
|
1314
1545
|
),
|
|
1315
1546
|
processing && /* @__PURE__ */ jsx("p", { role: "status", children: "Payment is processing. Confirmation may take a little while." }),
|
|
1316
|
-
error && /* @__PURE__ */ jsx("p", { role: "alert", children: error.message })
|
|
1547
|
+
error && /* @__PURE__ */ jsx("p", { role: "alert", ...el("checkout__error", { color: T.dangerText }), children: error.message })
|
|
1317
1548
|
] })
|
|
1318
1549
|
}
|
|
1319
1550
|
);
|
|
1320
1551
|
}
|
|
1321
1552
|
function Checkout(props) {
|
|
1322
|
-
return "checkout" in props ? /* @__PURE__ */ jsx(ProviderCheckout, { ...props }) : /* @__PURE__ */ jsx(StripeCheckout, { ...props });
|
|
1553
|
+
return /* @__PURE__ */ jsx(NozleBranding, { children: "checkout" in props ? /* @__PURE__ */ jsx(ProviderCheckout, { ...props }) : /* @__PURE__ */ jsx(StripeCheckout, { ...props }) });
|
|
1323
1554
|
}
|
|
1324
1555
|
var styles2 = `
|
|
1325
|
-
.nozle-
|
|
1326
|
-
.nozle-
|
|
1327
|
-
.nozle-
|
|
1328
|
-
.nozle-
|
|
1329
|
-
.nozle-
|
|
1330
|
-
.nozle-
|
|
1331
|
-
.nozle-
|
|
1332
|
-
.nozle-
|
|
1333
|
-
.nozle-
|
|
1556
|
+
.nozle-upgradeModal{position:fixed;inset:0;background:#0006;display:flex;align-items:center;justify-content:center;z-index:1000;padding:20px;color:${T.foreground};font:${T.fontBody}}
|
|
1557
|
+
.nozle-upgradeModal .nozle-upgradeModal__dialog{box-sizing:border-box;background:${T.card};border:${T.borderWidth} solid ${T.border};border-radius:${T.radius};padding:24px;max-width:480px;width:100%;max-height:90dvh;overflow:auto;box-shadow:${T.shadow},0 12px 48px #0003}
|
|
1558
|
+
.nozle-upgradeModal h2{font:${T.fontTitle};letter-spacing:normal;color:${T.foreground};margin:0 0 16px}
|
|
1559
|
+
.nozle-upgradeModal button{font:${T.fontAction};padding:9px 14px;border:var(--nozle-button-border-width, ${T.borderWidth}) solid ${T.input};border-radius:var(--nozle-button-radius, calc(${T.radius} * 0.6));background:${T.card};color:inherit;cursor:pointer}
|
|
1560
|
+
.nozle-upgradeModal button.nzu-primary{background:${T.primary};border-color:var(--nozle-button-border, ${T.primary});box-shadow:var(--nozle-button-shadow, none);color:${T.primaryForeground}}
|
|
1561
|
+
.nozle-upgradeModal button:disabled{opacity:.55;cursor:wait}
|
|
1562
|
+
.nozle-upgradeModal :focus-visible{outline:2px solid ${T.primary};outline-offset:3px}
|
|
1563
|
+
.nozle-upgradeModal .nozle-upgradeModal__actions{display:flex;flex-wrap:wrap;gap:10px;justify-content:flex-end;margin-top:20px}
|
|
1564
|
+
.nozle-upgradeModal .nzu-back{margin-right:auto}
|
|
1565
|
+
.nozle-upgradeModal [role=alert]{color:${T.dangerText}}
|
|
1334
1566
|
`;
|
|
1335
1567
|
function UpgradeModal(props) {
|
|
1336
1568
|
const billing = useOptionalBillingContext();
|
|
@@ -1341,6 +1573,7 @@ function UpgradeModal(props) {
|
|
|
1341
1573
|
const dialog = useRef(null);
|
|
1342
1574
|
const titleId = useId();
|
|
1343
1575
|
const busy = Boolean(props.busy || confirming);
|
|
1576
|
+
const { root, el } = useAppearance(props.appearance);
|
|
1344
1577
|
useEffect(() => {
|
|
1345
1578
|
if (!props.isOpen) return;
|
|
1346
1579
|
const previous = document.activeElement;
|
|
@@ -1400,17 +1633,17 @@ function UpgradeModal(props) {
|
|
|
1400
1633
|
}
|
|
1401
1634
|
}
|
|
1402
1635
|
if (!props.isOpen) return null;
|
|
1403
|
-
const
|
|
1636
|
+
const money3 = (value) => new Intl.NumberFormat(props.locale ?? "en-US", {
|
|
1404
1637
|
style: "currency",
|
|
1405
1638
|
currency: props.preview?.currency ?? "USD"
|
|
1406
1639
|
}).format(value);
|
|
1407
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
1640
|
+
return /* @__PURE__ */ jsxs("div", { ...root("upgradeModal", void 0, "nozle-upgrade-modal"), children: [
|
|
1408
1641
|
/* @__PURE__ */ jsx("style", { nonce: props.nonce, children: styles2 }),
|
|
1409
1642
|
/* @__PURE__ */ jsxs(
|
|
1410
1643
|
"div",
|
|
1411
1644
|
{
|
|
1412
1645
|
ref: dialog,
|
|
1413
|
-
|
|
1646
|
+
...el("upgradeModal__dialog"),
|
|
1414
1647
|
role: "dialog",
|
|
1415
1648
|
"aria-modal": "true",
|
|
1416
1649
|
"aria-labelledby": titleId,
|
|
@@ -1448,15 +1681,15 @@ function UpgradeModal(props) {
|
|
|
1448
1681
|
props.preview && /* @__PURE__ */ jsxs("div", { children: [
|
|
1449
1682
|
props.preview.credit > 0 && /* @__PURE__ */ jsxs("p", { children: [
|
|
1450
1683
|
"Credits applied: ",
|
|
1451
|
-
|
|
1684
|
+
money3(-props.preview.credit)
|
|
1452
1685
|
] }),
|
|
1453
1686
|
/* @__PURE__ */ jsxs("p", { children: [
|
|
1454
1687
|
"New plan charge: ",
|
|
1455
|
-
|
|
1688
|
+
money3(props.preview.debit)
|
|
1456
1689
|
] }),
|
|
1457
1690
|
/* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsxs("strong", { children: [
|
|
1458
1691
|
"Due today: ",
|
|
1459
|
-
|
|
1692
|
+
money3(props.preview.net)
|
|
1460
1693
|
] }) }),
|
|
1461
1694
|
/* @__PURE__ */ jsxs("p", { children: [
|
|
1462
1695
|
"Next billing date:",
|
|
@@ -1468,18 +1701,21 @@ function UpgradeModal(props) {
|
|
|
1468
1701
|
] })
|
|
1469
1702
|
] }),
|
|
1470
1703
|
props.children,
|
|
1471
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1704
|
+
/* @__PURE__ */ jsxs("div", { ...el("upgradeModal__actions"), children: [
|
|
1705
|
+
props.onBack && /* @__PURE__ */ jsx("button", { type: "button", className: "nzu-back", onClick: props.onBack, disabled: busy, children: "Back" }),
|
|
1472
1706
|
/* @__PURE__ */ jsx("button", { type: "button", onClick: props.onCancel, disabled: busy, children: "Close" }),
|
|
1473
1707
|
/* @__PURE__ */ jsx(
|
|
1474
1708
|
"button",
|
|
1475
1709
|
{
|
|
1476
1710
|
type: "button",
|
|
1711
|
+
className: "nzu-primary",
|
|
1477
1712
|
onClick: () => void handleConfirm(),
|
|
1478
1713
|
disabled: busy || props.confirmDisabled,
|
|
1479
1714
|
children: busy ? "Checking subscription\u2026" : props.confirmLabel ?? "Confirm"
|
|
1480
1715
|
}
|
|
1481
1716
|
)
|
|
1482
|
-
] })
|
|
1717
|
+
] }),
|
|
1718
|
+
/* @__PURE__ */ jsx(NozleBranding, {})
|
|
1483
1719
|
]
|
|
1484
1720
|
}
|
|
1485
1721
|
)
|
|
@@ -1487,7 +1723,7 @@ function UpgradeModal(props) {
|
|
|
1487
1723
|
}
|
|
1488
1724
|
|
|
1489
1725
|
// src/components/billing/amounts.ts
|
|
1490
|
-
function formatMinorAmount(value, currency, locale = "en-US") {
|
|
1726
|
+
function formatMinorAmount(value, currency, locale = "en-US", options = {}) {
|
|
1491
1727
|
const raw = String(value);
|
|
1492
1728
|
if (!/^-?\d+$/.test(raw) || typeof value === "number" && !Number.isSafeInteger(value)) {
|
|
1493
1729
|
return "Amount unavailable";
|
|
@@ -1503,12 +1739,16 @@ function formatMinorAmount(value, currency, locale = "en-US") {
|
|
|
1503
1739
|
const scale = 10n ** BigInt(digits);
|
|
1504
1740
|
const whole = absolute / scale;
|
|
1505
1741
|
const signed = minor < 0n ? whole === 0n ? -0 : -whole : whole;
|
|
1506
|
-
const
|
|
1742
|
+
const remainder = absolute % scale;
|
|
1743
|
+
const trim = options.trimZeroFraction === true && remainder === 0n;
|
|
1744
|
+
const decimal = String(remainder).padStart(digits, "0");
|
|
1507
1745
|
const digitFormatter = new Intl.NumberFormat(locale, {
|
|
1508
1746
|
useGrouping: false
|
|
1509
1747
|
});
|
|
1510
1748
|
const fraction = [...decimal].map((digit) => digitFormatter.format(Number(digit))).join("");
|
|
1511
|
-
return formatter.formatToParts(signed).
|
|
1749
|
+
return formatter.formatToParts(signed).filter(
|
|
1750
|
+
(part) => !trim || part.type !== "fraction" && part.type !== "decimal"
|
|
1751
|
+
).map((part) => part.type === "fraction" ? fraction : part.value).join("");
|
|
1512
1752
|
} catch {
|
|
1513
1753
|
return "Amount unavailable";
|
|
1514
1754
|
}
|
|
@@ -1527,17 +1767,181 @@ function formatBillingDate(value, locale = "en-US", timezone) {
|
|
|
1527
1767
|
}).format(new Date(value));
|
|
1528
1768
|
}
|
|
1529
1769
|
}
|
|
1770
|
+
|
|
1771
|
+
// src/components/pricing/plan-features.ts
|
|
1772
|
+
function number(value) {
|
|
1773
|
+
const parsed = Number(value);
|
|
1774
|
+
return Number.isFinite(parsed) ? parsed.toLocaleString("en-US", { maximumFractionDigits: 6 }) : String(value);
|
|
1775
|
+
}
|
|
1776
|
+
function money(amount, currency) {
|
|
1777
|
+
const parsed = Number(amount);
|
|
1778
|
+
if (!Number.isFinite(parsed)) return `${amount} ${currency}`;
|
|
1779
|
+
try {
|
|
1780
|
+
return new Intl.NumberFormat("en-US", { style: "currency", currency, maximumFractionDigits: 6 }).format(parsed);
|
|
1781
|
+
} catch {
|
|
1782
|
+
return `${currency} ${number(amount)}`;
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
function words(code) {
|
|
1786
|
+
return code.replace(/_/g, " ");
|
|
1787
|
+
}
|
|
1788
|
+
function entitlementRows(entitlement) {
|
|
1789
|
+
if (entitlement.feature_type === "config") {
|
|
1790
|
+
return (entitlement.privileges ?? []).flatMap((privilege) => {
|
|
1791
|
+
if (privilege.value === null || privilege.value === false) return [];
|
|
1792
|
+
if (privilege.value === true) return [`${entitlement.name}: ${privilege.name}`];
|
|
1793
|
+
return [`${entitlement.name}: ${privilege.value}`];
|
|
1794
|
+
});
|
|
1795
|
+
}
|
|
1796
|
+
if (entitlement.feature_type === "metered" && entitlement.usage_limit != null) {
|
|
1797
|
+
return [`${number(entitlement.usage_limit)} ${entitlement.name}`];
|
|
1798
|
+
}
|
|
1799
|
+
return [entitlement.name];
|
|
1800
|
+
}
|
|
1801
|
+
function creditGrantRow(rule) {
|
|
1802
|
+
const credits = `${number(rule.amount)} ${words(rule.credit_system_code)}`;
|
|
1803
|
+
if (rule.interval === "lifetime") return `${credits} (one time)`;
|
|
1804
|
+
const count = rule.interval_count ?? 1;
|
|
1805
|
+
return count === 1 ? `${credits} every ${rule.interval}` : `${credits} every ${count} ${rule.interval}s`;
|
|
1806
|
+
}
|
|
1807
|
+
function isZero(amount) {
|
|
1808
|
+
return amount === void 0 || Number(amount) === 0;
|
|
1809
|
+
}
|
|
1810
|
+
function rangeLabel(range, unit) {
|
|
1811
|
+
const from = number(range.from_value);
|
|
1812
|
+
return range.to_value === null ? `${from}+ ${unit}` : `${from} to ${number(range.to_value)} ${unit}`;
|
|
1813
|
+
}
|
|
1814
|
+
function rangeRows(ranges, unit, currency) {
|
|
1815
|
+
return ranges.map((range) => {
|
|
1816
|
+
const parts = [];
|
|
1817
|
+
if (!isZero(range.flat_amount)) parts.push(`${money(range.flat_amount, currency)} flat`);
|
|
1818
|
+
if (!isZero(range.per_unit_amount)) parts.push(`${money(range.per_unit_amount, currency)} each`);
|
|
1819
|
+
if (!isZero(range.rate)) parts.push(`${number(range.rate)}%`);
|
|
1820
|
+
return `${rangeLabel(range, unit)}: ${parts.length ? parts.join(" + ") : "free"}`;
|
|
1821
|
+
});
|
|
1822
|
+
}
|
|
1823
|
+
function chargeRows(charge, currency) {
|
|
1824
|
+
const properties = charge.properties ?? {};
|
|
1825
|
+
const unit = charge.feature_code ? words(charge.feature_code) : "unit";
|
|
1826
|
+
switch (charge.charge_model) {
|
|
1827
|
+
case "standard":
|
|
1828
|
+
return properties.amount === void 0 ? [] : [`${money(properties.amount, currency)} per ${unit}`];
|
|
1829
|
+
case "package": {
|
|
1830
|
+
if (properties.amount === void 0 || !properties.package_size) return [];
|
|
1831
|
+
const free = properties.free_units ? `, first ${number(properties.free_units)} free` : "";
|
|
1832
|
+
return [`${money(properties.amount, currency)} per ${number(properties.package_size)} ${unit}${free}`];
|
|
1833
|
+
}
|
|
1834
|
+
case "graduated":
|
|
1835
|
+
return rangeRows(properties.graduated_ranges ?? [], unit, currency);
|
|
1836
|
+
case "volume":
|
|
1837
|
+
return rangeRows(properties.volume_ranges ?? [], unit, currency);
|
|
1838
|
+
case "graduated_percentage":
|
|
1839
|
+
return rangeRows(properties.graduated_percentage_ranges ?? [], unit, currency);
|
|
1840
|
+
case "percentage": {
|
|
1841
|
+
if (properties.rate === void 0) return [];
|
|
1842
|
+
const fixed = isZero(properties.fixed_amount) ? "" : ` + ${money(properties.fixed_amount, currency)} per ${unit}`;
|
|
1843
|
+
return [`${number(properties.rate)}% of each ${unit}${fixed}`];
|
|
1844
|
+
}
|
|
1845
|
+
default:
|
|
1846
|
+
return [];
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
function planFeatures(plan) {
|
|
1850
|
+
const rows = (plan.entitlements ?? []).flatMap(entitlementRows);
|
|
1851
|
+
rows.push(...(plan.credit_grant_rules ?? []).map(creditGrantRow));
|
|
1852
|
+
for (const charge of plan.charges ?? []) rows.push(...chargeRows(charge, plan.amount_currency));
|
|
1853
|
+
return rows;
|
|
1854
|
+
}
|
|
1855
|
+
var INTERVAL_LABELS = {
|
|
1856
|
+
weekly: "week",
|
|
1857
|
+
monthly: "month",
|
|
1858
|
+
quarterly: "quarter",
|
|
1859
|
+
semiannual: "6 months",
|
|
1860
|
+
yearly: "year"
|
|
1861
|
+
};
|
|
1862
|
+
function intervalLabel(interval) {
|
|
1863
|
+
return INTERVAL_LABELS[interval] ?? interval;
|
|
1864
|
+
}
|
|
1865
|
+
function formatPlanPrice(amountCents, currency) {
|
|
1866
|
+
const amount = amountCents / 100;
|
|
1867
|
+
try {
|
|
1868
|
+
return new Intl.NumberFormat("en-US", {
|
|
1869
|
+
style: "currency",
|
|
1870
|
+
currency,
|
|
1871
|
+
minimumFractionDigits: Number.isInteger(amount) ? 0 : 2,
|
|
1872
|
+
maximumFractionDigits: 2
|
|
1873
|
+
}).format(amount);
|
|
1874
|
+
} catch {
|
|
1875
|
+
return `${currency} ${amount}`;
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
function entitlementValue(entitlement) {
|
|
1879
|
+
if (entitlement.feature_type === "config") {
|
|
1880
|
+
const values = (entitlement.privileges ?? []).filter((privilege) => privilege.value !== null && privilege.value !== false).map((privilege) => privilege.value === true ? privilege.name : String(privilege.value));
|
|
1881
|
+
return values.length ? values.join(", ") : false;
|
|
1882
|
+
}
|
|
1883
|
+
if (entitlement.feature_type === "metered" && entitlement.usage_limit != null) {
|
|
1884
|
+
return number(entitlement.usage_limit);
|
|
1885
|
+
}
|
|
1886
|
+
return true;
|
|
1887
|
+
}
|
|
1888
|
+
function comparisonRows(plans) {
|
|
1889
|
+
const rows = /* @__PURE__ */ new Map();
|
|
1890
|
+
const row = (key, label) => {
|
|
1891
|
+
let existing = rows.get(key);
|
|
1892
|
+
if (!existing) {
|
|
1893
|
+
existing = { key, label, values: {} };
|
|
1894
|
+
rows.set(key, existing);
|
|
1895
|
+
}
|
|
1896
|
+
return existing;
|
|
1897
|
+
};
|
|
1898
|
+
for (const plan of plans) {
|
|
1899
|
+
for (const entitlement of plan.entitlements ?? []) {
|
|
1900
|
+
row(`entitlement:${entitlement.code}`, entitlement.name).values[plan.code] = entitlementValue(entitlement);
|
|
1901
|
+
}
|
|
1902
|
+
for (const rule of plan.credit_grant_rules ?? []) {
|
|
1903
|
+
row(`credit:${rule.credit_system_code}`, words(rule.credit_system_code)).values[plan.code] = creditGrantRow(rule);
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
const all = [...rows.values()];
|
|
1907
|
+
for (const item of all) {
|
|
1908
|
+
for (const plan of plans) item.values[plan.code] ??= false;
|
|
1909
|
+
}
|
|
1910
|
+
return all;
|
|
1911
|
+
}
|
|
1912
|
+
function sortPlansByPrice(plans) {
|
|
1913
|
+
const currencyRank = /* @__PURE__ */ new Map();
|
|
1914
|
+
for (const plan of plans) {
|
|
1915
|
+
if (!currencyRank.has(plan.amount_currency)) currencyRank.set(plan.amount_currency, currencyRank.size);
|
|
1916
|
+
}
|
|
1917
|
+
return plans.map((plan, index) => ({ plan, index })).sort(
|
|
1918
|
+
(a, b) => currencyRank.get(a.plan.amount_currency) - currencyRank.get(b.plan.amount_currency) || a.plan.amount_cents - b.plan.amount_cents || a.index - b.index
|
|
1919
|
+
).map(({ plan }) => plan);
|
|
1920
|
+
}
|
|
1530
1921
|
var styles3 = `
|
|
1531
|
-
.nozle-plan-change{font
|
|
1532
|
-
.nozle-plan-change button{font
|
|
1922
|
+
.nozle-plan-change{font:${T.fontBody};color:${T.foreground};margin-top:12px}
|
|
1923
|
+
.nozle-plan-change button{font:${T.fontAction};padding:9px 14px;border:var(--nozle-button-border-width, ${T.borderWidth}) solid ${T.input};border-radius:var(--nozle-button-radius, calc(${T.radius} * 0.6));background:${T.card};color:inherit;cursor:pointer}
|
|
1533
1924
|
.nozle-plan-change button:disabled{opacity:.55;cursor:wait}
|
|
1534
|
-
.nozle-plan-change :focus-visible{outline:2px solid
|
|
1535
|
-
.nozle-plan-change [role=alert]{color
|
|
1925
|
+
.nozle-plan-change :focus-visible{outline:2px solid ${T.primary};outline-offset:3px}
|
|
1926
|
+
.nozle-plan-change [role=alert]{color:${T.dangerText}}
|
|
1536
1927
|
.nozle-plan-change .nzpc-actions{display:flex;gap:10px;flex-wrap:wrap}
|
|
1537
|
-
.nozle-upgrade-modal select{box-sizing:border-box;width:100%;font:inherit;color:inherit;padding:10px;margin:8px 0;border
|
|
1928
|
+
.nozle-upgrade-modal select{box-sizing:border-box;width:100%;font:inherit;color:inherit;padding:10px;margin:8px 0;border:${T.borderWidth} solid ${T.border};border-radius:calc(${T.radius} * 0.6);background:${T.card}}
|
|
1538
1929
|
.nozle-upgrade-modal dl{display:block;margin:16px 0;padding:0}
|
|
1539
1930
|
.nozle-upgrade-modal dl div{display:flex;justify-content:space-between;gap:16px;margin:12px 0}
|
|
1931
|
+
.nozle-upgrade-modal dt{font:${T.fontMeta};color:${T.mutedForeground}}
|
|
1540
1932
|
.nozle-upgrade-modal dd{margin:0;text-align:right}
|
|
1933
|
+
.nozle-upgrade-modal label{font:${T.fontBody};font-weight:500}
|
|
1934
|
+
.nozle-plan-change .nzpc-plans{display:grid;gap:10px}
|
|
1935
|
+
.nozle-plan-change label.nzpc-plan{display:flex;gap:12px;align-items:flex-start;padding:14px 16px;border:${T.borderWidth} solid ${T.border};border-radius:calc(${T.radius} * 0.75);background:${T.card};cursor:pointer;font:${T.fontBody};color:${T.foreground}}
|
|
1936
|
+
.nozle-plan-change label.nzpc-plan[data-selected]{border-color:${T.primary};background:${T.primarySoft}}
|
|
1937
|
+
.nozle-plan-change label.nzpc-plan input{flex:none;width:auto;margin:4px 0 0;padding:0;accent-color:${T.primary}}
|
|
1938
|
+
.nozle-plan-change .nzpc-plan-body{display:grid;gap:4px;min-width:0;flex:1}
|
|
1939
|
+
.nozle-plan-change .nzpc-plan-head{display:flex;justify-content:space-between;align-items:baseline;gap:8px}
|
|
1940
|
+
.nozle-plan-change .nzpc-plan-name{font:${T.fontHeading}}
|
|
1941
|
+
.nozle-plan-change .nzpc-plan-op{font:${T.labelFont};letter-spacing:${T.labelSpacing};text-transform:${T.labelTransform};color:${T.mutedForeground}}
|
|
1942
|
+
.nozle-plan-change .nzpc-plan-price{font-variant-numeric:tabular-nums}
|
|
1943
|
+
.nozle-plan-change .nzpc-plan-muted{font:${T.fontMeta};color:${T.mutedForeground};margin:0}
|
|
1944
|
+
.nozle-plan-change .nzpc-plan ul{list-style:none;margin:2px 0 0;padding:0;display:grid;gap:2px;font:${T.fontMeta};color:${T.mutedForeground}}
|
|
1541
1945
|
`;
|
|
1542
1946
|
var paymentPending = (state) => ["awaiting_payment", "processing", "needs_review"].includes(
|
|
1543
1947
|
state.checkoutStatus
|
|
@@ -1564,8 +1968,10 @@ function PlanChangeFlow({
|
|
|
1564
1968
|
returnUrl,
|
|
1565
1969
|
locale = "en-US",
|
|
1566
1970
|
timezone = "UTC",
|
|
1567
|
-
nonce
|
|
1971
|
+
nonce,
|
|
1972
|
+
appearance
|
|
1568
1973
|
}) {
|
|
1974
|
+
const { root } = useAppearance(appearance);
|
|
1569
1975
|
const owner = useMemo(
|
|
1570
1976
|
() => ({ controller: new AbortController() }),
|
|
1571
1977
|
[subscriptionId, actions]
|
|
@@ -1580,6 +1986,11 @@ function PlanChangeFlow({
|
|
|
1580
1986
|
null
|
|
1581
1987
|
);
|
|
1582
1988
|
const [target, setTarget] = useState("");
|
|
1989
|
+
const billing = useOptionalBillingContext();
|
|
1990
|
+
const catalog = useQuery(
|
|
1991
|
+
{ ...catalogQuery(billing?.client), enabled: Boolean(billing) },
|
|
1992
|
+
billing?.queryClient ?? idleQueryClient
|
|
1993
|
+
);
|
|
1583
1994
|
const [quote, setQuote] = useState();
|
|
1584
1995
|
const [checkout, setCheckout] = useState(null);
|
|
1585
1996
|
const [withdrawTarget, setWithdrawTarget] = useState();
|
|
@@ -1847,7 +2258,7 @@ function PlanChangeFlow({
|
|
|
1847
2258
|
await reconcile(signal);
|
|
1848
2259
|
});
|
|
1849
2260
|
const selected = state?.eligiblePlans.find((plan) => plan.code === target);
|
|
1850
|
-
const
|
|
2261
|
+
const money3 = (amount) => formatMinorAmount(
|
|
1851
2262
|
amount,
|
|
1852
2263
|
quote?.currency ?? state?.currentPlan.currency ?? "USD",
|
|
1853
2264
|
locale
|
|
@@ -1855,7 +2266,7 @@ function PlanChangeFlow({
|
|
|
1855
2266
|
const blocked = Boolean(
|
|
1856
2267
|
state?.endingAt || state?.pendingChange || state && paymentPending(state) || state?.blockedReason || pending.current?.accepted
|
|
1857
2268
|
);
|
|
1858
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
2269
|
+
return /* @__PURE__ */ jsxs("div", { ...root("planChangeControl", void 0, "nozle-plan-change"), children: [
|
|
1859
2270
|
/* @__PURE__ */ jsx("style", { nonce, children: styles3 }),
|
|
1860
2271
|
!state ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1861
2272
|
/* @__PURE__ */ jsx("p", { role: "status", children: "Loading plan changes\u2026" }),
|
|
@@ -1953,6 +2364,7 @@ function PlanChangeFlow({
|
|
|
1953
2364
|
/* @__PURE__ */ jsxs(
|
|
1954
2365
|
UpgradeModal,
|
|
1955
2366
|
{
|
|
2367
|
+
appearance,
|
|
1956
2368
|
isOpen: mode !== null,
|
|
1957
2369
|
planCode: target,
|
|
1958
2370
|
title: mode === "withdraw" ? "Keep your current plan?" : mode === "choose" ? "Change plan" : `Confirm ${quote?.operation ?? "plan change"}`,
|
|
@@ -1961,42 +2373,55 @@ function PlanChangeFlow({
|
|
|
1961
2373
|
confirmDisabled: mode === "choose" && (!target || blocked) || mode === "confirm" && !quote,
|
|
1962
2374
|
confirmLabel: pending.current?.attempted ? "Check status / retry" : mode === "choose" ? "Review change" : mode === "withdraw" ? "Confirm keep current plan" : quote?.operation === "downgrade" ? "Confirm downgrade" : "Confirm upgrade",
|
|
1963
2375
|
onCancel: () => setMode(null),
|
|
2376
|
+
onBack: mode === "confirm" ? () => {
|
|
2377
|
+
setMode("choose");
|
|
2378
|
+
setQuote(void 0);
|
|
2379
|
+
setFailure(void 0);
|
|
2380
|
+
} : void 0,
|
|
1964
2381
|
onSubmit: confirm,
|
|
1965
2382
|
children: [
|
|
1966
2383
|
failure && /* @__PURE__ */ jsx("p", { role: "alert", children: failure }),
|
|
1967
|
-
mode === "choose" && /* @__PURE__ */
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
2384
|
+
mode === "choose" && /* @__PURE__ */ jsx("div", { className: "nzpc-plans", role: "radiogroup", "aria-label": "Choose a plan", children: state.eligiblePlans.map((plan) => {
|
|
2385
|
+
const details = catalog.data?.plans.find((entry) => entry.code === plan.code);
|
|
2386
|
+
const features = details ? planFeatures(details) : [];
|
|
2387
|
+
return /* @__PURE__ */ jsxs(
|
|
2388
|
+
"label",
|
|
1971
2389
|
{
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
setTarget(event.target.value);
|
|
1975
|
-
setQuote(void 0);
|
|
1976
|
-
},
|
|
1977
|
-
disabled: busy,
|
|
2390
|
+
className: "nzpc-plan",
|
|
2391
|
+
"data-selected": target === plan.code ? "" : void 0,
|
|
1978
2392
|
children: [
|
|
1979
|
-
/* @__PURE__ */ jsx(
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
plan.
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
"
|
|
1995
|
-
|
|
2393
|
+
/* @__PURE__ */ jsx(
|
|
2394
|
+
"input",
|
|
2395
|
+
{
|
|
2396
|
+
type: "radio",
|
|
2397
|
+
name: `nozle-plan-${subscriptionId}`,
|
|
2398
|
+
value: plan.code,
|
|
2399
|
+
checked: target === plan.code,
|
|
2400
|
+
disabled: busy,
|
|
2401
|
+
onChange: () => {
|
|
2402
|
+
setTarget(plan.code);
|
|
2403
|
+
setQuote(void 0);
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
),
|
|
2407
|
+
/* @__PURE__ */ jsxs("span", { className: "nzpc-plan-body", children: [
|
|
2408
|
+
/* @__PURE__ */ jsxs("span", { className: "nzpc-plan-head", children: [
|
|
2409
|
+
/* @__PURE__ */ jsx("span", { className: "nzpc-plan-name", children: plan.name }),
|
|
2410
|
+
/* @__PURE__ */ jsx("span", { className: "nzpc-plan-op", children: plan.operation === "upgrade" ? "Upgrade" : "Downgrade" })
|
|
2411
|
+
] }),
|
|
2412
|
+
/* @__PURE__ */ jsxs("span", { className: "nzpc-plan-price", children: [
|
|
2413
|
+
formatMinorAmount(plan.amountCents, plan.currency, locale),
|
|
2414
|
+
" / ",
|
|
2415
|
+
plan.interval
|
|
2416
|
+
] }),
|
|
2417
|
+
details?.description && /* @__PURE__ */ jsx("p", { className: "nzpc-plan-muted", children: details.description }),
|
|
2418
|
+
features.length > 0 && /* @__PURE__ */ jsx("ul", { children: features.map((feature) => /* @__PURE__ */ jsx("li", { children: feature }, feature)) })
|
|
2419
|
+
] })
|
|
1996
2420
|
]
|
|
1997
|
-
}
|
|
1998
|
-
|
|
1999
|
-
|
|
2421
|
+
},
|
|
2422
|
+
plan.code
|
|
2423
|
+
);
|
|
2424
|
+
}) }),
|
|
2000
2425
|
mode === "withdraw" && /* @__PURE__ */ jsxs("p", { children: [
|
|
2001
2426
|
"Remove the scheduled change to ",
|
|
2002
2427
|
withdrawTarget?.name,
|
|
@@ -2025,19 +2450,19 @@ function PlanChangeFlow({
|
|
|
2025
2450
|
/* @__PURE__ */ jsxs("dl", { children: [
|
|
2026
2451
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
2027
2452
|
/* @__PURE__ */ jsx("dt", { children: "New plan charge" }),
|
|
2028
|
-
/* @__PURE__ */ jsx("dd", { children:
|
|
2453
|
+
/* @__PURE__ */ jsx("dd", { children: money3(quote.debitAmountCents) })
|
|
2029
2454
|
] }),
|
|
2030
2455
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
2031
2456
|
/* @__PURE__ */ jsx("dt", { children: "Credits applied" }),
|
|
2032
|
-
/* @__PURE__ */ jsx("dd", { children:
|
|
2457
|
+
/* @__PURE__ */ jsx("dd", { children: money3(quote.creditAmountCents) })
|
|
2033
2458
|
] }),
|
|
2034
2459
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
2035
2460
|
/* @__PURE__ */ jsx("dt", { children: "Due now" }),
|
|
2036
|
-
/* @__PURE__ */ jsx("dd", { children:
|
|
2461
|
+
/* @__PURE__ */ jsx("dd", { children: money3(quote.amountDueNowCents) })
|
|
2037
2462
|
] }),
|
|
2038
2463
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
2039
2464
|
/* @__PURE__ */ jsx("dt", { children: "Due at effective date" }),
|
|
2040
|
-
/* @__PURE__ */ jsx("dd", { children:
|
|
2465
|
+
/* @__PURE__ */ jsx("dd", { children: money3(quote.amountDueAtEffectiveCents) })
|
|
2041
2466
|
] })
|
|
2042
2467
|
] }),
|
|
2043
2468
|
/* @__PURE__ */ jsxs("p", { children: [
|
|
@@ -2056,6 +2481,19 @@ function PlanChangeFlow({
|
|
|
2056
2481
|
] })
|
|
2057
2482
|
] });
|
|
2058
2483
|
}
|
|
2484
|
+
var PortalAppearance = createContext({
|
|
2485
|
+
el: (key) => ({ className: "nozle-" + key })
|
|
2486
|
+
});
|
|
2487
|
+
function Part({
|
|
2488
|
+
as: Tag = "div",
|
|
2489
|
+
part,
|
|
2490
|
+
className,
|
|
2491
|
+
style,
|
|
2492
|
+
...rest
|
|
2493
|
+
}) {
|
|
2494
|
+
const themed = useContext(PortalAppearance).el(part, style, className);
|
|
2495
|
+
return /* @__PURE__ */ jsx(Tag, { ...rest, ...themed });
|
|
2496
|
+
}
|
|
2059
2497
|
function asError(error) {
|
|
2060
2498
|
return error instanceof BillingPortalError ? error : new BillingPortalError("request");
|
|
2061
2499
|
}
|
|
@@ -2067,8 +2505,10 @@ function BillingPortal({
|
|
|
2067
2505
|
className = "",
|
|
2068
2506
|
style,
|
|
2069
2507
|
nonce,
|
|
2070
|
-
onError
|
|
2508
|
+
onError,
|
|
2509
|
+
appearance
|
|
2071
2510
|
}) {
|
|
2511
|
+
const { root, el } = useAppearance(appearance);
|
|
2072
2512
|
const [attempt, setAttempt] = useState(0);
|
|
2073
2513
|
const [state, setState] = useState();
|
|
2074
2514
|
const errorCallback = useRef(onError);
|
|
@@ -2105,11 +2545,10 @@ function BillingPortal({
|
|
|
2105
2545
|
}, [createSession, attempt]);
|
|
2106
2546
|
const current = state?.source === createSession && state.attempt === attempt ? state : void 0;
|
|
2107
2547
|
const reconnect = () => setAttempt((value) => value + 1);
|
|
2108
|
-
return /* @__PURE__ */ jsxs(
|
|
2548
|
+
return /* @__PURE__ */ jsx(PortalAppearance.Provider, { value: { el, appearance }, children: /* @__PURE__ */ jsxs(
|
|
2109
2549
|
"div",
|
|
2110
2550
|
{
|
|
2111
|
-
|
|
2112
|
-
style,
|
|
2551
|
+
...root("billingPortal", style, `nozle-billing-portal ${className}`),
|
|
2113
2552
|
"aria-label": "Nozle billing portal",
|
|
2114
2553
|
children: [
|
|
2115
2554
|
/* @__PURE__ */ jsx("style", { nonce, children: portalStyles }),
|
|
@@ -2128,9 +2567,9 @@ function BillingPortal({
|
|
|
2128
2567
|
) : current?.error ? /* @__PURE__ */ jsx(Failure, { error: current.error, retry: reconnect }) : /* @__PURE__ */ jsx(Loading, { label: "Starting billing session\u2026" })
|
|
2129
2568
|
]
|
|
2130
2569
|
}
|
|
2131
|
-
);
|
|
2570
|
+
) });
|
|
2132
2571
|
}
|
|
2133
|
-
function
|
|
2572
|
+
function useQuery4(client, query, variables, onError, retainDuringRefresh = false) {
|
|
2134
2573
|
const [attempt, setAttempt] = useState(0);
|
|
2135
2574
|
const key = JSON.stringify(variables);
|
|
2136
2575
|
const [state, setState] = useState();
|
|
@@ -2195,12 +2634,12 @@ function Failure({
|
|
|
2195
2634
|
reconnect
|
|
2196
2635
|
}) {
|
|
2197
2636
|
const action = error.code === "unauthorized" ? reconnect ?? retry : retry;
|
|
2198
|
-
return /* @__PURE__ */ jsxs(
|
|
2637
|
+
return /* @__PURE__ */ jsxs(Part, { role: "alert", className: "nzp-error", part: "billingPortal__error", children: [
|
|
2199
2638
|
/* @__PURE__ */ jsx("span", { children: error.message }),
|
|
2200
2639
|
action && /* @__PURE__ */ jsx("button", { type: "button", onClick: action, children: error.code === "unauthorized" ? "Reconnect" : "Try again" })
|
|
2201
2640
|
] });
|
|
2202
2641
|
}
|
|
2203
|
-
function
|
|
2642
|
+
function money2(cents, currency, locale) {
|
|
2204
2643
|
const formatter = new Intl.NumberFormat(locale, {
|
|
2205
2644
|
style: "currency",
|
|
2206
2645
|
currency
|
|
@@ -2227,7 +2666,7 @@ var intervals = {
|
|
|
2227
2666
|
};
|
|
2228
2667
|
function Portal(props) {
|
|
2229
2668
|
const { client, locale, reconnect, onError } = props;
|
|
2230
|
-
const { data, error, retry } =
|
|
2669
|
+
const { data, error, retry } = useQuery4(
|
|
2231
2670
|
client,
|
|
2232
2671
|
identityQuery,
|
|
2233
2672
|
{},
|
|
@@ -2240,7 +2679,7 @@ function Portal(props) {
|
|
|
2240
2679
|
return /* @__PURE__ */ jsx(Failure, { error, retry, reconnect });
|
|
2241
2680
|
if (!data) return /* @__PURE__ */ jsx(Loading, {});
|
|
2242
2681
|
const customer = data.customerPortalUser;
|
|
2243
|
-
return /* @__PURE__ */ jsxs(
|
|
2682
|
+
return /* @__PURE__ */ jsxs(NozleBranding, { show: data.customerPortalOrganization.nozleBranding === true, children: [
|
|
2244
2683
|
/* @__PURE__ */ jsxs("header", { children: [
|
|
2245
2684
|
/* @__PURE__ */ jsxs("div", { className: "nzp-row", children: [
|
|
2246
2685
|
/* @__PURE__ */ jsx("span", { className: "nzp-muted", children: data.customerPortalOrganization.name }),
|
|
@@ -2249,7 +2688,7 @@ function Portal(props) {
|
|
|
2249
2688
|
/* @__PURE__ */ jsx("h2", { children: "Billing portal" }),
|
|
2250
2689
|
/* @__PURE__ */ jsx("p", { className: "nzp-muted", children: customer.name || [customer.firstname, customer.lastname].filter(Boolean).join(" ") || customer.email || "Your account" })
|
|
2251
2690
|
] }),
|
|
2252
|
-
notice && /* @__PURE__ */ jsx("p", { role: "status", children: notice }),
|
|
2691
|
+
notice && /* @__PURE__ */ jsx("p", { className: "nzp-notice", role: "status", children: notice }),
|
|
2253
2692
|
view.kind !== "overview" && /* @__PURE__ */ jsx("button", { type: "button", onClick: back, children: "\u2190 Back to overview" }),
|
|
2254
2693
|
view.kind === "overview" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2255
2694
|
/* @__PURE__ */ jsx(
|
|
@@ -2267,7 +2706,7 @@ function Portal(props) {
|
|
|
2267
2706
|
onWallet: (wallet) => setView({ kind: "wallet", wallet })
|
|
2268
2707
|
}
|
|
2269
2708
|
),
|
|
2270
|
-
/* @__PURE__ */ jsxs("section",
|
|
2709
|
+
/* @__PURE__ */ jsxs(Part, { as: "section", part: "billingPortal__section", "aria-label": "Billing information", children: [
|
|
2271
2710
|
/* @__PURE__ */ jsxs("div", { className: "nzp-row", children: [
|
|
2272
2711
|
/* @__PURE__ */ jsx("h3", { children: "Billing information" }),
|
|
2273
2712
|
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => setView({ kind: "edit" }), children: "Edit billing information" })
|
|
@@ -2337,8 +2776,7 @@ function Portal(props) {
|
|
|
2337
2776
|
back();
|
|
2338
2777
|
}
|
|
2339
2778
|
}
|
|
2340
|
-
)
|
|
2341
|
-
/* @__PURE__ */ jsx("footer", { children: "Powered by Nozle" })
|
|
2779
|
+
)
|
|
2342
2780
|
] });
|
|
2343
2781
|
}
|
|
2344
2782
|
function Pager({
|
|
@@ -2385,17 +2823,20 @@ function Subscriptions({
|
|
|
2385
2823
|
planChangeActions,
|
|
2386
2824
|
nonce
|
|
2387
2825
|
}) {
|
|
2826
|
+
const portalAppearance = useContext(PortalAppearance).appearance;
|
|
2388
2827
|
const [page, setPage] = useState(1);
|
|
2389
2828
|
const [updated, setUpdated] = useState({});
|
|
2390
|
-
const { data, error, retry } =
|
|
2829
|
+
const { data, error, retry } = useQuery4(client, subscriptionsQuery, { page }, onError, true);
|
|
2391
2830
|
const result = data?.customerPortalSubscriptions;
|
|
2392
2831
|
useEffect(() => setUpdated({}), [result]);
|
|
2393
|
-
return /* @__PURE__ */ jsxs("section",
|
|
2832
|
+
return /* @__PURE__ */ jsxs(Part, { as: "section", part: "billingPortal__section", "aria-label": "Subscriptions", children: [
|
|
2394
2833
|
/* @__PURE__ */ jsx("h3", { children: "Subscriptions" }),
|
|
2395
2834
|
error ? /* @__PURE__ */ jsx(Failure, { error, retry, reconnect }) : !result ? /* @__PURE__ */ jsx(Loading, {}) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2396
2835
|
result.collection.length === 0 ? /* @__PURE__ */ jsx("p", { className: "nzp-empty", children: "No active subscriptions." }) : /* @__PURE__ */ jsx("div", { className: "nzp-stack", children: result.collection.map((item) => updated[item.id] ?? item).map((subscription) => /* @__PURE__ */ jsxs(
|
|
2397
|
-
|
|
2836
|
+
Part,
|
|
2398
2837
|
{
|
|
2838
|
+
as: "article",
|
|
2839
|
+
part: "billingPortal__card",
|
|
2399
2840
|
className: "nzp-card",
|
|
2400
2841
|
"data-subscription-id": subscription.externalId,
|
|
2401
2842
|
children: [
|
|
@@ -2404,7 +2845,7 @@ function Subscriptions({
|
|
|
2404
2845
|
/* @__PURE__ */ jsx("span", { className: "nzp-badge", children: subscription.endingAt ? "Scheduled to end" : "Active" })
|
|
2405
2846
|
] }),
|
|
2406
2847
|
/* @__PURE__ */ jsxs("div", { className: "nzp-price", children: [
|
|
2407
|
-
|
|
2848
|
+
money2(
|
|
2408
2849
|
subscription.plan.amountCents,
|
|
2409
2850
|
subscription.plan.amountCurrency,
|
|
2410
2851
|
locale
|
|
@@ -2416,25 +2857,15 @@ function Subscriptions({
|
|
|
2416
2857
|
intervals[subscription.plan.interval] || subscription.plan.interval
|
|
2417
2858
|
] })
|
|
2418
2859
|
] }),
|
|
2419
|
-
/* @__PURE__ */ jsxs("
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
timezone
|
|
2429
|
-
)
|
|
2430
|
-
] }),
|
|
2431
|
-
/* @__PURE__ */ jsx(
|
|
2432
|
-
"button",
|
|
2433
|
-
{
|
|
2434
|
-
type: "button",
|
|
2435
|
-
onClick: () => onUsage(subscription),
|
|
2436
|
-
children: "View usage"
|
|
2437
|
-
}
|
|
2860
|
+
/* @__PURE__ */ jsxs("p", { className: "nzp-muted", children: [
|
|
2861
|
+
"Base price, excluding tax and usage",
|
|
2862
|
+
/* @__PURE__ */ jsx("br", {}),
|
|
2863
|
+
"Current period ends",
|
|
2864
|
+
" ",
|
|
2865
|
+
date(
|
|
2866
|
+
subscription.currentBillingPeriodEndingAt,
|
|
2867
|
+
locale,
|
|
2868
|
+
timezone
|
|
2438
2869
|
)
|
|
2439
2870
|
] }),
|
|
2440
2871
|
subscription.endingAt && /* @__PURE__ */ jsxs("p", { children: [
|
|
@@ -2447,48 +2878,60 @@ function Subscriptions({
|
|
|
2447
2878
|
" ",
|
|
2448
2879
|
subscription.nextSubscription.plan.name
|
|
2449
2880
|
] }),
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2881
|
+
/* @__PURE__ */ jsxs("div", { className: "nzp-card-actions", children: [
|
|
2882
|
+
/* @__PURE__ */ jsx(
|
|
2883
|
+
"button",
|
|
2884
|
+
{
|
|
2885
|
+
type: "button",
|
|
2886
|
+
onClick: () => onUsage(subscription),
|
|
2887
|
+
children: "View usage"
|
|
2888
|
+
}
|
|
2889
|
+
),
|
|
2890
|
+
planChangeActions && subscription.externalId && /* @__PURE__ */ jsx(
|
|
2891
|
+
PlanChangeControl,
|
|
2892
|
+
{
|
|
2893
|
+
subscriptionId: subscription.externalId,
|
|
2894
|
+
refreshKey: `${subscription.id}:${subscription.endingAt ?? ""}:${subscription.nextSubscription?.id ?? ""}:${subscription.plan.code}`,
|
|
2895
|
+
actions: planChangeActions,
|
|
2896
|
+
appearance: portalAppearance,
|
|
2897
|
+
locale,
|
|
2898
|
+
timezone,
|
|
2899
|
+
nonce,
|
|
2900
|
+
onError,
|
|
2901
|
+
onChanged: retry
|
|
2902
|
+
}
|
|
2903
|
+
),
|
|
2904
|
+
cancellationActions && subscription.externalId && /* @__PURE__ */ jsx(
|
|
2905
|
+
CancellationControl,
|
|
2906
|
+
{
|
|
2907
|
+
subscription: cancellationState(subscription),
|
|
2908
|
+
actions: cancellationActions,
|
|
2909
|
+
appearance: portalAppearance,
|
|
2910
|
+
locale,
|
|
2911
|
+
timezone,
|
|
2912
|
+
nonce,
|
|
2913
|
+
onError,
|
|
2914
|
+
onChanged: retry,
|
|
2915
|
+
refresh: async (signal) => {
|
|
2916
|
+
const result2 = await client.request(
|
|
2917
|
+
subscriptionQuery,
|
|
2918
|
+
{ id: subscription.id },
|
|
2919
|
+
signal
|
|
2920
|
+
);
|
|
2921
|
+
if (!signal.aborted && result2.customerPortalSubscription) {
|
|
2922
|
+
const fresh = result2.customerPortalSubscription;
|
|
2923
|
+
setUpdated((previous) => ({
|
|
2924
|
+
...previous,
|
|
2925
|
+
[fresh.id]: fresh
|
|
2926
|
+
}));
|
|
2927
|
+
}
|
|
2928
|
+
return result2.customerPortalSubscription ? cancellationState(
|
|
2929
|
+
result2.customerPortalSubscription
|
|
2930
|
+
) : null;
|
|
2472
2931
|
}
|
|
2473
|
-
return result2.customerPortalSubscription ? cancellationState(
|
|
2474
|
-
result2.customerPortalSubscription
|
|
2475
|
-
) : null;
|
|
2476
2932
|
}
|
|
2477
|
-
|
|
2478
|
-
)
|
|
2479
|
-
planChangeActions && subscription.externalId && /* @__PURE__ */ jsx(
|
|
2480
|
-
PlanChangeControl,
|
|
2481
|
-
{
|
|
2482
|
-
subscriptionId: subscription.externalId,
|
|
2483
|
-
refreshKey: `${subscription.id}:${subscription.endingAt ?? ""}:${subscription.nextSubscription?.id ?? ""}:${subscription.plan.code}`,
|
|
2484
|
-
actions: planChangeActions,
|
|
2485
|
-
locale,
|
|
2486
|
-
timezone,
|
|
2487
|
-
nonce,
|
|
2488
|
-
onError,
|
|
2489
|
-
onChanged: retry
|
|
2490
|
-
}
|
|
2491
|
-
)
|
|
2933
|
+
)
|
|
2934
|
+
] })
|
|
2492
2935
|
]
|
|
2493
2936
|
},
|
|
2494
2937
|
subscription.externalId || subscription.id
|
|
@@ -2522,9 +2965,9 @@ function UsageDetails({
|
|
|
2522
2965
|
subscription,
|
|
2523
2966
|
timezone
|
|
2524
2967
|
}) {
|
|
2525
|
-
const { data, error, retry } =
|
|
2968
|
+
const { data, error, retry } = useQuery4(client, usageQuery, { subscriptionId: subscription.id }, onError);
|
|
2526
2969
|
const usage = data?.customerPortalCustomerUsage;
|
|
2527
|
-
return /* @__PURE__ */ jsxs("section",
|
|
2970
|
+
return /* @__PURE__ */ jsxs(Part, { as: "section", part: "billingPortal__section", "aria-label": "Subscription usage", children: [
|
|
2528
2971
|
/* @__PURE__ */ jsxs("h3", { children: [
|
|
2529
2972
|
planName(subscription),
|
|
2530
2973
|
" \xB7 Usage"
|
|
@@ -2536,18 +2979,18 @@ function UsageDetails({
|
|
|
2536
2979
|
" ",
|
|
2537
2980
|
date(usage.toDatetime, locale, timezone)
|
|
2538
2981
|
] }),
|
|
2539
|
-
/* @__PURE__ */ jsx("p", { className: "nzp-price", children:
|
|
2982
|
+
/* @__PURE__ */ jsx("p", { className: "nzp-price", children: money2(usage.amountCents, usage.currency, locale) }),
|
|
2540
2983
|
/* @__PURE__ */ jsx("p", { className: "nzp-muted", children: "Current usage, excluding tax" }),
|
|
2541
|
-
usage.chargesUsage.length ? /* @__PURE__ */ jsx(
|
|
2984
|
+
usage.chargesUsage.length ? /* @__PURE__ */ jsx(Part, { className: "nzp-table", part: "billingPortal__table", children: /* @__PURE__ */ jsxs("table", { children: [
|
|
2542
2985
|
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
2543
2986
|
/* @__PURE__ */ jsx("th", { children: "Feature" }),
|
|
2544
|
-
/* @__PURE__ */ jsx("th", { children: "Units" }),
|
|
2545
|
-
/* @__PURE__ */ jsx("th", { children: "Amount" })
|
|
2987
|
+
/* @__PURE__ */ jsx("th", { className: "nzp-num", children: "Units" }),
|
|
2988
|
+
/* @__PURE__ */ jsx("th", { className: "nzp-num", children: "Amount" })
|
|
2546
2989
|
] }) }),
|
|
2547
2990
|
/* @__PURE__ */ jsx("tbody", { children: usage.chargesUsage.map((charge, index) => /* @__PURE__ */ jsxs("tr", { children: [
|
|
2548
2991
|
/* @__PURE__ */ jsx("td", { children: charge.charge?.invoiceDisplayName || charge.feature?.name || charge.feature?.code || "Usage" }),
|
|
2549
|
-
/* @__PURE__ */ jsx("td", { children: charge.units }),
|
|
2550
|
-
/* @__PURE__ */ jsx("td", { children:
|
|
2992
|
+
/* @__PURE__ */ jsx("td", { className: "nzp-num", children: charge.units }),
|
|
2993
|
+
/* @__PURE__ */ jsx("td", { className: "nzp-num", children: money2(charge.amountCents, usage.currency, locale) })
|
|
2551
2994
|
] }, `${charge.id}-${index}`)) })
|
|
2552
2995
|
] }) }) : /* @__PURE__ */ jsx("p", { className: "nzp-empty", children: "No usage recorded for this billing period." })
|
|
2553
2996
|
] })
|
|
@@ -2557,25 +3000,31 @@ function Invoices(props) {
|
|
|
2557
3000
|
const { client, locale, reconnect, onError } = props;
|
|
2558
3001
|
const [search, setSearch] = useState("");
|
|
2559
3002
|
const [filter, setFilter] = useState({ page: 1, searchTerm: "" });
|
|
2560
|
-
const { data, error, retry } =
|
|
3003
|
+
const { data, error, retry } = useQuery4(client, invoicesQuery, filter, onError);
|
|
2561
3004
|
const result = data?.customerPortalInvoices;
|
|
2562
|
-
return /* @__PURE__ */ jsxs("section",
|
|
3005
|
+
return /* @__PURE__ */ jsxs(Part, { as: "section", part: "billingPortal__section", "aria-label": "Invoice history", children: [
|
|
2563
3006
|
/* @__PURE__ */ jsx("h3", { children: "Invoice history" }),
|
|
2564
3007
|
/* @__PURE__ */ jsxs(
|
|
2565
3008
|
"form",
|
|
2566
3009
|
{
|
|
2567
3010
|
className: "nzp-actions",
|
|
3011
|
+
role: "search",
|
|
2568
3012
|
onSubmit: (event) => {
|
|
2569
3013
|
event.preventDefault();
|
|
2570
3014
|
setFilter({ page: 1, searchTerm: search.trim() });
|
|
2571
3015
|
},
|
|
2572
3016
|
children: [
|
|
2573
|
-
/* @__PURE__ */ jsxs("label", { children: [
|
|
2574
|
-
"
|
|
3017
|
+
/* @__PURE__ */ jsxs("label", { className: "nzp-search", children: [
|
|
3018
|
+
/* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.25", strokeLinecap: "round", "aria-hidden": "true", children: [
|
|
3019
|
+
/* @__PURE__ */ jsx("circle", { cx: "11", cy: "11", r: "7" }),
|
|
3020
|
+
/* @__PURE__ */ jsx("path", { d: "m20 20-3.5-3.5" })
|
|
3021
|
+
] }),
|
|
2575
3022
|
/* @__PURE__ */ jsx(
|
|
2576
3023
|
"input",
|
|
2577
3024
|
{
|
|
2578
3025
|
type: "search",
|
|
3026
|
+
"aria-label": "Search invoices",
|
|
3027
|
+
placeholder: "Search invoices",
|
|
2579
3028
|
value: search,
|
|
2580
3029
|
onChange: (event) => setSearch(event.target.value)
|
|
2581
3030
|
}
|
|
@@ -2586,18 +3035,18 @@ function Invoices(props) {
|
|
|
2586
3035
|
}
|
|
2587
3036
|
),
|
|
2588
3037
|
error ? /* @__PURE__ */ jsx(Failure, { error, retry, reconnect }) : !result ? /* @__PURE__ */ jsx(Loading, {}) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2589
|
-
!result.collection.length ? /* @__PURE__ */ jsx("p", { className: "nzp-empty", children: filter.searchTerm ? "No invoices match your search." : "No invoices yet." }) : /* @__PURE__ */ jsx(
|
|
3038
|
+
!result.collection.length ? /* @__PURE__ */ jsx("p", { className: "nzp-empty", children: filter.searchTerm ? "No invoices match your search." : "No invoices yet." }) : /* @__PURE__ */ jsx(Part, { className: "nzp-table", part: "billingPortal__table", children: /* @__PURE__ */ jsxs("table", { children: [
|
|
2590
3039
|
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
2591
3040
|
/* @__PURE__ */ jsx("th", { children: "Invoice" }),
|
|
2592
3041
|
/* @__PURE__ */ jsx("th", { children: "Date" }),
|
|
2593
|
-
/* @__PURE__ */ jsx("th", { children: "Amount" }),
|
|
3042
|
+
/* @__PURE__ */ jsx("th", { className: "nzp-num", children: "Amount" }),
|
|
2594
3043
|
/* @__PURE__ */ jsx("th", { children: "Status" }),
|
|
2595
3044
|
/* @__PURE__ */ jsx("th", { children: "PDF" })
|
|
2596
3045
|
] }) }),
|
|
2597
3046
|
/* @__PURE__ */ jsx("tbody", { children: result.collection.map((invoice) => /* @__PURE__ */ jsxs("tr", { children: [
|
|
2598
3047
|
/* @__PURE__ */ jsx("td", { children: invoice.number }),
|
|
2599
3048
|
/* @__PURE__ */ jsx("td", { children: date(invoice.issuingDate, locale) }),
|
|
2600
|
-
/* @__PURE__ */ jsx("td", { children:
|
|
3049
|
+
/* @__PURE__ */ jsx("td", { className: "nzp-num", children: money2(
|
|
2601
3050
|
invoice.totalAmountCents,
|
|
2602
3051
|
invoice.currency,
|
|
2603
3052
|
locale
|
|
@@ -2701,7 +3150,7 @@ function CustomerForm({
|
|
|
2701
3150
|
if (!signal.aborted) onSaved();
|
|
2702
3151
|
});
|
|
2703
3152
|
};
|
|
2704
|
-
return /* @__PURE__ */ jsxs("section",
|
|
3153
|
+
return /* @__PURE__ */ jsxs(Part, { as: "section", part: "billingPortal__section", "aria-label": "Edit billing information", children: [
|
|
2705
3154
|
/* @__PURE__ */ jsx("h3", { children: "Edit billing information" }),
|
|
2706
3155
|
/* @__PURE__ */ jsxs("form", { onSubmit: submit, children: [
|
|
2707
3156
|
/* @__PURE__ */ jsx("div", { className: "nzp-fields", children: customerFields.map(([key, label]) => /* @__PURE__ */ jsxs("label", { children: [
|
|
@@ -2743,15 +3192,15 @@ function Wallets({
|
|
|
2743
3192
|
onWallet
|
|
2744
3193
|
}) {
|
|
2745
3194
|
const [page, setPage] = useState(1);
|
|
2746
|
-
const { data, error, retry } =
|
|
3195
|
+
const { data, error, retry } = useQuery4(client, walletsQuery, { page }, onError);
|
|
2747
3196
|
const result = data?.customerPortalWallets;
|
|
2748
3197
|
if (result && !result.collection.length) return null;
|
|
2749
|
-
return /* @__PURE__ */ jsxs("section",
|
|
3198
|
+
return /* @__PURE__ */ jsxs(Part, { as: "section", part: "billingPortal__section", "aria-label": "Wallets", children: [
|
|
2750
3199
|
/* @__PURE__ */ jsx("h3", { children: "Wallets" }),
|
|
2751
3200
|
error ? /* @__PURE__ */ jsx(Failure, { error, retry, reconnect }) : !result ? /* @__PURE__ */ jsx(Loading, {}) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2752
|
-
/* @__PURE__ */ jsx("div", { className: "nzp-stack", children: result.collection.map((wallet) => /* @__PURE__ */ jsxs("article",
|
|
3201
|
+
/* @__PURE__ */ jsx("div", { className: "nzp-stack", children: result.collection.map((wallet) => /* @__PURE__ */ jsxs(Part, { as: "article", part: "billingPortal__card", className: "nzp-card", children: [
|
|
2753
3202
|
/* @__PURE__ */ jsx("h3", { children: wallet.name || "Prepaid credits" }),
|
|
2754
|
-
/* @__PURE__ */ jsx("div", { className: "nzp-price", children:
|
|
3203
|
+
/* @__PURE__ */ jsx("div", { className: "nzp-price", children: money2(wallet.balanceCents, wallet.currency, locale) }),
|
|
2755
3204
|
/* @__PURE__ */ jsxs("div", { className: "nzp-row", children: [
|
|
2756
3205
|
/* @__PURE__ */ jsxs("p", { className: "nzp-muted", children: [
|
|
2757
3206
|
wallet.creditsBalance,
|
|
@@ -2796,7 +3245,7 @@ function WalletTopUp({
|
|
|
2796
3245
|
amountCents,
|
|
2797
3246
|
String(wallet.paidTopUpMaxAmountCents)
|
|
2798
3247
|
) <= 0);
|
|
2799
|
-
return /* @__PURE__ */ jsxs("section",
|
|
3248
|
+
return /* @__PURE__ */ jsxs(Part, { as: "section", part: "billingPortal__section", "aria-label": "Add credits", children: [
|
|
2800
3249
|
/* @__PURE__ */ jsxs("h3", { children: [
|
|
2801
3250
|
"Add credits \xB7 ",
|
|
2802
3251
|
wallet.name || "Wallet"
|
|
@@ -2833,19 +3282,19 @@ function WalletTopUp({
|
|
|
2833
3282
|
)
|
|
2834
3283
|
] }) }),
|
|
2835
3284
|
/* @__PURE__ */ jsxs("p", { children: [
|
|
2836
|
-
amountCents !== null ?
|
|
3285
|
+
amountCents !== null ? money2(amountCents, wallet.currency, locale) : "\u2014",
|
|
2837
3286
|
" ",
|
|
2838
3287
|
"excluding tax"
|
|
2839
3288
|
] }),
|
|
2840
3289
|
wallet.paidTopUpMinAmountCents != null && /* @__PURE__ */ jsxs("p", { className: "nzp-muted", children: [
|
|
2841
3290
|
"Minimum",
|
|
2842
3291
|
" ",
|
|
2843
|
-
|
|
3292
|
+
money2(wallet.paidTopUpMinAmountCents, wallet.currency, locale)
|
|
2844
3293
|
] }),
|
|
2845
3294
|
wallet.paidTopUpMaxAmountCents != null && /* @__PURE__ */ jsxs("p", { className: "nzp-muted", children: [
|
|
2846
3295
|
"Maximum",
|
|
2847
3296
|
" ",
|
|
2848
|
-
|
|
3297
|
+
money2(wallet.paidTopUpMaxAmountCents, wallet.currency, locale)
|
|
2849
3298
|
] }),
|
|
2850
3299
|
action.error && /* @__PURE__ */ jsx(Failure, { error: action.error, reconnect }),
|
|
2851
3300
|
/* @__PURE__ */ jsx("div", { className: "nzp-actions", children: /* @__PURE__ */ jsx(
|
|
@@ -2862,38 +3311,112 @@ function WalletTopUp({
|
|
|
2862
3311
|
)
|
|
2863
3312
|
] });
|
|
2864
3313
|
}
|
|
2865
|
-
function
|
|
2866
|
-
const {
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
3314
|
+
function useBilling() {
|
|
3315
|
+
const { billingFetch, billingEndpoint, queryClient } = useBillingContext();
|
|
3316
|
+
if (!billingFetch || !billingEndpoint) {
|
|
3317
|
+
throw new Error("Billing hooks need BillingProvider billingEndpoint, a backend route served by createBillingHandler");
|
|
3318
|
+
}
|
|
3319
|
+
return { fetchRoute: billingFetch, queryClient, endpoint: billingEndpoint };
|
|
3320
|
+
}
|
|
3321
|
+
function useSubscription() {
|
|
3322
|
+
const { fetchRoute, queryClient, endpoint } = useBilling();
|
|
3323
|
+
return useQuery(
|
|
3324
|
+
{ queryKey: ["nozle", endpoint, "subscription"], queryFn: () => fetchRoute("subscription") },
|
|
3325
|
+
queryClient
|
|
3326
|
+
);
|
|
3327
|
+
}
|
|
3328
|
+
function useEntitlement(featureCode, options = {}) {
|
|
3329
|
+
const { fetchRoute, queryClient, endpoint } = useBilling();
|
|
3330
|
+
return useQuery(
|
|
3331
|
+
{
|
|
3332
|
+
queryKey: ["nozle", endpoint, "entitlement", featureCode],
|
|
3333
|
+
queryFn: () => fetchRoute("entitlement", { feature: featureCode }),
|
|
3334
|
+
enabled: Boolean(featureCode) && options.enabled !== false,
|
|
3335
|
+
staleTime: 1e4
|
|
3336
|
+
},
|
|
3337
|
+
queryClient
|
|
3338
|
+
);
|
|
3339
|
+
}
|
|
3340
|
+
function useCreditBalance(creditSystemCode) {
|
|
3341
|
+
const { fetchRoute, queryClient, endpoint } = useBilling();
|
|
3342
|
+
return useQuery(
|
|
3343
|
+
{
|
|
3344
|
+
queryKey: ["nozle", endpoint, "credits"],
|
|
3345
|
+
queryFn: () => fetchRoute("credits"),
|
|
3346
|
+
select: (data) => {
|
|
3347
|
+
const balances = data.balances ?? [];
|
|
3348
|
+
if (creditSystemCode) return balances.find((b) => b.credit_system === creditSystemCode) ?? null;
|
|
3349
|
+
return balances.find((b) => Number(b.available) > 0) ?? balances[0] ?? null;
|
|
2888
3350
|
}
|
|
2889
|
-
}
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
3351
|
+
},
|
|
3352
|
+
queryClient
|
|
3353
|
+
);
|
|
3354
|
+
}
|
|
3355
|
+
function useCreditHistory(options = {}) {
|
|
3356
|
+
const { fetchRoute, queryClient, endpoint } = useBilling();
|
|
3357
|
+
const query = useInfiniteQuery(
|
|
3358
|
+
{
|
|
3359
|
+
queryKey: ["nozle", endpoint, "credit-history", options.creditSystemCode, options.limit],
|
|
3360
|
+
queryFn: ({ pageParam }) => fetchRoute("credit-history", {
|
|
3361
|
+
credit_system: options.creditSystemCode,
|
|
3362
|
+
limit: options.limit,
|
|
3363
|
+
cursor: pageParam
|
|
3364
|
+
}),
|
|
3365
|
+
initialPageParam: void 0,
|
|
3366
|
+
getNextPageParam: (page) => page.next_cursor ?? void 0
|
|
3367
|
+
},
|
|
3368
|
+
queryClient
|
|
3369
|
+
);
|
|
3370
|
+
return { ...query, operations: query.data?.pages.flatMap((page) => page.operations) ?? [] };
|
|
3371
|
+
}
|
|
3372
|
+
function useUsage(options = {}) {
|
|
3373
|
+
const { fetchRoute, queryClient, endpoint } = useBilling();
|
|
3374
|
+
const period = options.period ?? "current";
|
|
3375
|
+
return useQuery(
|
|
3376
|
+
{
|
|
3377
|
+
queryKey: ["nozle", endpoint, "usage", period, options.periods],
|
|
3378
|
+
queryFn: () => fetchRoute("usage", {
|
|
3379
|
+
period,
|
|
3380
|
+
periods: options.periods
|
|
3381
|
+
}),
|
|
3382
|
+
select: (data) => period === "past" ? data.periods ?? [] : data.usage ?? null
|
|
3383
|
+
},
|
|
3384
|
+
queryClient
|
|
3385
|
+
);
|
|
3386
|
+
}
|
|
3387
|
+
function useInvoices(options = {}) {
|
|
3388
|
+
const { fetchRoute, queryClient, endpoint } = useBilling();
|
|
3389
|
+
const page = options.page ?? 1;
|
|
3390
|
+
return useQuery(
|
|
3391
|
+
{
|
|
3392
|
+
queryKey: ["nozle", endpoint, "invoices", page],
|
|
3393
|
+
queryFn: () => fetchRoute("invoices", { page })
|
|
3394
|
+
},
|
|
3395
|
+
queryClient
|
|
3396
|
+
);
|
|
3397
|
+
}
|
|
3398
|
+
function useInvoiceDownload() {
|
|
3399
|
+
const { fetchRoute } = useBilling();
|
|
3400
|
+
return useCallback(
|
|
3401
|
+
async (invoiceId) => (await fetchRoute("invoice-download", { id: invoiceId })).url,
|
|
3402
|
+
[fetchRoute]
|
|
3403
|
+
);
|
|
3404
|
+
}
|
|
3405
|
+
function useUpgradePreview(planCode) {
|
|
3406
|
+
const { fetchRoute, queryClient, endpoint } = useBilling();
|
|
3407
|
+
return useQuery(
|
|
3408
|
+
{
|
|
3409
|
+
queryKey: ["nozle", endpoint, "preview", planCode],
|
|
3410
|
+
queryFn: () => fetchRoute("preview", { plan: planCode }),
|
|
3411
|
+
enabled: Boolean(planCode),
|
|
3412
|
+
staleTime: 0
|
|
3413
|
+
},
|
|
3414
|
+
queryClient
|
|
3415
|
+
);
|
|
3416
|
+
}
|
|
3417
|
+
function useBillingRefresh() {
|
|
3418
|
+
const { queryClient } = useBillingContext();
|
|
3419
|
+
return useCallback(() => queryClient.invalidateQueries({ queryKey: ["nozle"] }), [queryClient]);
|
|
2897
3420
|
}
|
|
2898
3421
|
function useCheckout2() {
|
|
2899
3422
|
const { createCheckout } = useBillingContext();
|
|
@@ -2941,77 +3464,539 @@ function useCheckout2() {
|
|
|
2941
3464
|
);
|
|
2942
3465
|
return { fetchClientSecret, checkout, isLoading, error };
|
|
2943
3466
|
}
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
hl: v("--nozle-pricing-highlight", v("--nozle-primary", "#6366f1")),
|
|
2960
|
-
border: v("--nozle-pricing-border", v("--nozle-border", "#e5e7eb")),
|
|
2961
|
-
radius: v("--nozle-pricing-radius", v("--nozle-radius", "12px")),
|
|
2962
|
-
text: v("--nozle-foreground", "#111827"),
|
|
2963
|
-
muted: v("--nozle-muted-foreground", "#6b7280"),
|
|
2964
|
-
mutedBg: v("--nozle-muted", "#f3f4f6"),
|
|
2965
|
-
hlFg: v("--nozle-primary-foreground", "#ffffff")
|
|
3467
|
+
|
|
3468
|
+
// src/components/pricing/catalog.ts
|
|
3469
|
+
var PRICING_INTERVALS = [
|
|
3470
|
+
"weekly",
|
|
3471
|
+
"monthly",
|
|
3472
|
+
"quarterly",
|
|
3473
|
+
"semiannual",
|
|
3474
|
+
"yearly"
|
|
3475
|
+
];
|
|
3476
|
+
var INTERVAL_LABELS2 = {
|
|
3477
|
+
weekly: "Weekly",
|
|
3478
|
+
monthly: "Monthly",
|
|
3479
|
+
quarterly: "Quarterly",
|
|
3480
|
+
semiannual: "Every 6 months",
|
|
3481
|
+
yearly: "Annual"
|
|
2966
3482
|
};
|
|
3483
|
+
var BILLED_WORDS = {
|
|
3484
|
+
weekly: "weekly",
|
|
3485
|
+
monthly: "monthly",
|
|
3486
|
+
quarterly: "quarterly",
|
|
3487
|
+
semiannual: "every 6 months",
|
|
3488
|
+
yearly: "yearly"
|
|
3489
|
+
};
|
|
3490
|
+
var PERIODS_PER_YEAR = {
|
|
3491
|
+
weekly: 52n,
|
|
3492
|
+
monthly: 12n,
|
|
3493
|
+
quarterly: 4n,
|
|
3494
|
+
semiannual: 2n,
|
|
3495
|
+
yearly: 1n
|
|
3496
|
+
};
|
|
3497
|
+
function isPricingInterval(value) {
|
|
3498
|
+
return typeof value === "string" && PRICING_INTERVALS.includes(value);
|
|
3499
|
+
}
|
|
3500
|
+
function intervalRank(interval) {
|
|
3501
|
+
return PRICING_INTERVALS.indexOf(interval);
|
|
3502
|
+
}
|
|
3503
|
+
function normalizePricingInterval(value) {
|
|
3504
|
+
if (value === "annual") return "yearly";
|
|
3505
|
+
return isPricingInterval(value) ? value : null;
|
|
3506
|
+
}
|
|
3507
|
+
function hasPosition(value) {
|
|
3508
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
3509
|
+
}
|
|
3510
|
+
function comparePositions(a, b) {
|
|
3511
|
+
const aSet = hasPosition(a);
|
|
3512
|
+
const bSet = hasPosition(b);
|
|
3513
|
+
if (aSet && bSet) return a - b;
|
|
3514
|
+
if (aSet) return -1;
|
|
3515
|
+
if (bSet) return 1;
|
|
3516
|
+
return 0;
|
|
3517
|
+
}
|
|
3518
|
+
var DEFAULT_LOCALE = "en-US";
|
|
3519
|
+
function resolvePricingLocale(locale) {
|
|
3520
|
+
if (typeof locale !== "string" || locale.trim() === "") return DEFAULT_LOCALE;
|
|
3521
|
+
try {
|
|
3522
|
+
Intl.getCanonicalLocales(locale);
|
|
3523
|
+
return locale;
|
|
3524
|
+
} catch {
|
|
3525
|
+
return DEFAULT_LOCALE;
|
|
3526
|
+
}
|
|
3527
|
+
}
|
|
3528
|
+
function isLive(plan) {
|
|
3529
|
+
return plan.pending_deletion !== true;
|
|
3530
|
+
}
|
|
3531
|
+
function hasFamilies(catalog) {
|
|
3532
|
+
return Array.isArray(catalog.families);
|
|
3533
|
+
}
|
|
3534
|
+
function getPricingGroups(catalog) {
|
|
3535
|
+
const plans = catalog.plans ?? [];
|
|
3536
|
+
if (!hasFamilies(catalog)) {
|
|
3537
|
+
return plans.filter(isLive).map((plan) => ({ key: plan.code, family: null, plans: [plan] }));
|
|
3538
|
+
}
|
|
3539
|
+
const byCode = /* @__PURE__ */ new Map();
|
|
3540
|
+
for (const plan of plans) {
|
|
3541
|
+
if (isLive(plan) && !byCode.has(plan.code)) byCode.set(plan.code, plan);
|
|
3542
|
+
}
|
|
3543
|
+
return catalog.families.map((family, index) => ({ family, index })).filter(({ family }) => family.show_on_pricing !== false).sort(
|
|
3544
|
+
(a, b) => comparePositions(a.family.pricing_position, b.family.pricing_position) || a.index - b.index
|
|
3545
|
+
).map(({ family }) => {
|
|
3546
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3547
|
+
const familyPlans = [];
|
|
3548
|
+
for (const code of family.plan_codes ?? []) {
|
|
3549
|
+
const plan = byCode.get(code);
|
|
3550
|
+
if (plan && !seen.has(code)) {
|
|
3551
|
+
seen.add(code);
|
|
3552
|
+
familyPlans.push(plan);
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3555
|
+
return { key: family.code, family, plans: familyPlans };
|
|
3556
|
+
}).filter((group) => group.plans.length > 0);
|
|
3557
|
+
}
|
|
3558
|
+
function toMinor(value) {
|
|
3559
|
+
const raw = String(value);
|
|
3560
|
+
if (!/^-?\d+$/.test(raw)) return null;
|
|
3561
|
+
if (typeof value === "number" && !Number.isSafeInteger(value)) return null;
|
|
3562
|
+
return BigInt(raw);
|
|
3563
|
+
}
|
|
3564
|
+
function divideRounded(numerator, denominator) {
|
|
3565
|
+
if (numerator < 0n) return -divideRounded(-numerator, denominator);
|
|
3566
|
+
return (2n * numerator + denominator) / (2n * denominator);
|
|
3567
|
+
}
|
|
3568
|
+
function perMonthMinor(amount, interval) {
|
|
3569
|
+
return divideRounded(amount * PERIODS_PER_YEAR[interval], 12n);
|
|
3570
|
+
}
|
|
3571
|
+
function fmt(amount, currency, locale) {
|
|
3572
|
+
return formatMinorAmount(String(amount), currency, locale, {
|
|
3573
|
+
trimZeroFraction: true
|
|
3574
|
+
});
|
|
3575
|
+
}
|
|
3576
|
+
function savingsAgainstMonthly(plan, monthly) {
|
|
3577
|
+
if (!monthly || monthly === plan) return null;
|
|
3578
|
+
if (!isPricingInterval(plan.interval) || plan.interval === "monthly") return null;
|
|
3579
|
+
if (monthly.amount_currency !== plan.amount_currency) return null;
|
|
3580
|
+
const monthlyAmount = toMinor(monthly.amount_cents);
|
|
3581
|
+
const amount = toMinor(plan.amount_cents);
|
|
3582
|
+
if (monthlyAmount === null || amount === null) return null;
|
|
3583
|
+
const base = monthlyAmount * 12n;
|
|
3584
|
+
const cost = amount * PERIODS_PER_YEAR[plan.interval];
|
|
3585
|
+
if (base <= 0n || cost >= base) return null;
|
|
3586
|
+
const percent = Number(divideRounded(100n * (base - cost), base));
|
|
3587
|
+
return percent > 0 ? percent : null;
|
|
3588
|
+
}
|
|
3589
|
+
function monthlyPlanOf(group) {
|
|
3590
|
+
return group.family ? group.plans.find((plan) => plan.interval === "monthly") : void 0;
|
|
3591
|
+
}
|
|
3592
|
+
function getAvailableIntervals(catalog) {
|
|
3593
|
+
const present = /* @__PURE__ */ new Set();
|
|
3594
|
+
for (const group of getPricingGroups(catalog)) {
|
|
3595
|
+
for (const plan of group.plans) present.add(plan.interval);
|
|
3596
|
+
}
|
|
3597
|
+
return PRICING_INTERVALS.filter((interval) => present.has(interval));
|
|
3598
|
+
}
|
|
3599
|
+
function getPricingIntervalOptions(catalog) {
|
|
3600
|
+
const groups = getPricingGroups(catalog);
|
|
3601
|
+
return getAvailableIntervals(catalog).map((interval) => {
|
|
3602
|
+
let best = null;
|
|
3603
|
+
if (interval !== "monthly") {
|
|
3604
|
+
for (const group of groups) {
|
|
3605
|
+
const plan = group.plans.find((p) => p.interval === interval);
|
|
3606
|
+
const saving = plan ? savingsAgainstMonthly(plan, monthlyPlanOf(group)) : null;
|
|
3607
|
+
if (saving !== null && (best === null || saving > best)) best = saving;
|
|
3608
|
+
}
|
|
3609
|
+
}
|
|
3610
|
+
return { interval, label: INTERVAL_LABELS2[interval], savingsPercent: best };
|
|
3611
|
+
});
|
|
3612
|
+
}
|
|
3613
|
+
function getDefaultInterval(catalog, preferred) {
|
|
3614
|
+
const available = getAvailableIntervals(catalog);
|
|
3615
|
+
const wanted = normalizePricingInterval(preferred);
|
|
3616
|
+
if (wanted && available.includes(wanted)) return wanted;
|
|
3617
|
+
if (available.includes("monthly")) return "monthly";
|
|
3618
|
+
return available[0] ?? null;
|
|
3619
|
+
}
|
|
3620
|
+
function pickPlan(plans, interval) {
|
|
3621
|
+
if (interval === null) return plans[0];
|
|
3622
|
+
const exact = plans.find((plan) => plan.interval === interval);
|
|
3623
|
+
if (exact) return exact;
|
|
3624
|
+
const target = intervalRank(interval);
|
|
3625
|
+
let best = plans[0];
|
|
3626
|
+
let bestScore = Number.POSITIVE_INFINITY;
|
|
3627
|
+
for (const plan of plans) {
|
|
3628
|
+
const rank = intervalRank(plan.interval);
|
|
3629
|
+
if (rank < 0) continue;
|
|
3630
|
+
const score = Math.abs(rank - target) * 2 + (rank > target ? 1 : 0);
|
|
3631
|
+
if (score < bestScore) {
|
|
3632
|
+
best = plan;
|
|
3633
|
+
bestScore = score;
|
|
3634
|
+
}
|
|
3635
|
+
}
|
|
3636
|
+
return best;
|
|
3637
|
+
}
|
|
3638
|
+
function isTrueBoolean(privilege) {
|
|
3639
|
+
const { value } = privilege;
|
|
3640
|
+
if (value === true) return true;
|
|
3641
|
+
return privilege.value_type === "boolean" && typeof value === "string" && value.toLowerCase() === "true";
|
|
3642
|
+
}
|
|
3643
|
+
function formatPrivilegeValue(privilege, numberFormat) {
|
|
3644
|
+
const { value } = privilege;
|
|
3645
|
+
if (value === null || value === void 0) return null;
|
|
3646
|
+
if (isTrueBoolean(privilege)) return null;
|
|
3647
|
+
if (value === false) return "No";
|
|
3648
|
+
if (typeof value === "number") {
|
|
3649
|
+
return Number.isFinite(value) ? numberFormat.format(value) : null;
|
|
3650
|
+
}
|
|
3651
|
+
const text2 = String(value).trim();
|
|
3652
|
+
if (text2 === "") return null;
|
|
3653
|
+
if (privilege.value_type === "boolean" && text2.toLowerCase() === "false") {
|
|
3654
|
+
return "No";
|
|
3655
|
+
}
|
|
3656
|
+
if (privilege.value_type === "integer" && /^-?\d+$/.test(text2)) {
|
|
3657
|
+
const parsed = Number(text2);
|
|
3658
|
+
if (Number.isSafeInteger(parsed)) return numberFormat.format(parsed);
|
|
3659
|
+
}
|
|
3660
|
+
return text2;
|
|
3661
|
+
}
|
|
3662
|
+
function featureLabel(name, entitlement, numberFormat) {
|
|
3663
|
+
const values = (entitlement?.privileges ?? []).map((privilege) => ({
|
|
3664
|
+
privilege,
|
|
3665
|
+
text: formatPrivilegeValue(privilege, numberFormat)
|
|
3666
|
+
})).filter(
|
|
3667
|
+
(entry) => entry.text !== null
|
|
3668
|
+
);
|
|
3669
|
+
if (values.length === 1) return `${name}: ${values[0].text}`;
|
|
3670
|
+
if (values.length > 1) {
|
|
3671
|
+
const parts = values.map(({ privilege, text: text2 }) => {
|
|
3672
|
+
const privilegeName = privilege.name?.trim() || privilege.code;
|
|
3673
|
+
return `${privilegeName}: ${text2}`;
|
|
3674
|
+
});
|
|
3675
|
+
return `${name} (${parts.join(", ")})`;
|
|
3676
|
+
}
|
|
3677
|
+
const limit = entitlement?.usage_limit;
|
|
3678
|
+
if (typeof limit === "number" && Number.isFinite(limit)) {
|
|
3679
|
+
return `${name}: ${numberFormat.format(limit)}`;
|
|
3680
|
+
}
|
|
3681
|
+
return name;
|
|
3682
|
+
}
|
|
3683
|
+
function trimmed(value) {
|
|
3684
|
+
return typeof value === "string" ? value.trim() : "";
|
|
3685
|
+
}
|
|
3686
|
+
function familyFeatures(family, plan, locale) {
|
|
3687
|
+
const numberFormat = new Intl.NumberFormat(locale);
|
|
3688
|
+
const entitlements = /* @__PURE__ */ new Map();
|
|
3689
|
+
for (const entitlement of plan.entitlements ?? []) {
|
|
3690
|
+
if (!entitlements.has(entitlement.code)) {
|
|
3691
|
+
entitlements.set(entitlement.code, entitlement);
|
|
3692
|
+
}
|
|
3693
|
+
}
|
|
3694
|
+
return (family.features ?? []).filter((feature) => feature.show_on_pricing !== false).map((feature, index) => {
|
|
3695
|
+
const entitlement = entitlements.get(feature.code);
|
|
3696
|
+
const name = trimmed(feature.name) || trimmed(entitlement?.name) || feature.code;
|
|
3697
|
+
return { feature, entitlement, index, name };
|
|
3698
|
+
}).sort(
|
|
3699
|
+
(a, b) => comparePositions(a.feature.display_position, b.feature.display_position) || a.name.localeCompare(b.name, locale) || a.index - b.index
|
|
3700
|
+
).map(({ feature, entitlement, name }) => {
|
|
3701
|
+
const label = featureLabel(name, entitlement, numberFormat);
|
|
3702
|
+
const description = trimmed(feature.description);
|
|
3703
|
+
return feature.show_description === true && description ? { label, description } : { label };
|
|
3704
|
+
});
|
|
3705
|
+
}
|
|
3706
|
+
function planCardFeatures(plan) {
|
|
3707
|
+
return planFeatures(plan).map((label) => ({ label }));
|
|
3708
|
+
}
|
|
3709
|
+
function normalizeFeatures(items) {
|
|
3710
|
+
return items.map((item) => typeof item === "string" ? { label: item } : item).filter(
|
|
3711
|
+
(item) => Boolean(item) && typeof item.label === "string"
|
|
3712
|
+
);
|
|
3713
|
+
}
|
|
3714
|
+
function overrideFeatures(overrides, group, plan, cardIndex) {
|
|
3715
|
+
if (!overrides) return null;
|
|
3716
|
+
if (Array.isArray(overrides)) {
|
|
3717
|
+
const entry = overrides[cardIndex];
|
|
3718
|
+
return entry ? normalizeFeatures(entry) : null;
|
|
3719
|
+
}
|
|
3720
|
+
const keys = [group.key, plan.code, ...group.plans.map((p) => p.code)];
|
|
3721
|
+
for (const key of keys) {
|
|
3722
|
+
if (Object.prototype.hasOwnProperty.call(overrides, key)) {
|
|
3723
|
+
return normalizeFeatures(overrides[key] ?? []);
|
|
3724
|
+
}
|
|
3725
|
+
}
|
|
3726
|
+
return null;
|
|
3727
|
+
}
|
|
3728
|
+
function yearlyMinor(plan) {
|
|
3729
|
+
const amount = toMinor(plan.amount_cents);
|
|
3730
|
+
if (amount === null || !isPricingInterval(plan.interval)) return null;
|
|
3731
|
+
return amount * PERIODS_PER_YEAR[plan.interval];
|
|
3732
|
+
}
|
|
3733
|
+
function planPriceStrings(plan, locale) {
|
|
3734
|
+
const amount = toMinor(plan.amount_cents);
|
|
3735
|
+
return {
|
|
3736
|
+
price: fmt(amount ?? plan.amount_cents, plan.amount_currency, locale),
|
|
3737
|
+
pricePeriod: plan.interval ? `/${intervalLabel(plan.interval)}` : "",
|
|
3738
|
+
billing: "",
|
|
3739
|
+
savingsPercent: null
|
|
3740
|
+
};
|
|
3741
|
+
}
|
|
3742
|
+
function priceStrings(plan, monthly, locale) {
|
|
3743
|
+
const amount = toMinor(plan.amount_cents);
|
|
3744
|
+
const currency = plan.amount_currency;
|
|
3745
|
+
if (!isPricingInterval(plan.interval) || amount === null) {
|
|
3746
|
+
return {
|
|
3747
|
+
price: fmt(amount ?? plan.amount_cents, currency, locale),
|
|
3748
|
+
pricePeriod: "",
|
|
3749
|
+
billing: plan.interval ? `Billed ${plan.interval}` : "",
|
|
3750
|
+
savingsPercent: null
|
|
3751
|
+
};
|
|
3752
|
+
}
|
|
3753
|
+
const price = fmt(perMonthMinor(amount, plan.interval), currency, locale);
|
|
3754
|
+
if (plan.interval === "monthly") {
|
|
3755
|
+
return { price, pricePeriod: "/mo", billing: "Billed monthly", savingsPercent: null };
|
|
3756
|
+
}
|
|
3757
|
+
const savingsPercent = savingsAgainstMonthly(plan, monthly);
|
|
3758
|
+
const billed = `Billed ${fmt(amount, currency, locale)} ${BILLED_WORDS[plan.interval]}`;
|
|
3759
|
+
return {
|
|
3760
|
+
price,
|
|
3761
|
+
pricePeriod: "/mo",
|
|
3762
|
+
billing: savingsPercent === null ? billed : `${billed} \xB7 save ${savingsPercent}%`,
|
|
3763
|
+
savingsPercent
|
|
3764
|
+
};
|
|
3765
|
+
}
|
|
3766
|
+
function getPricingCards(catalog, options = {}) {
|
|
3767
|
+
const locale = resolvePricingLocale(options.locale);
|
|
3768
|
+
const families = hasFamilies(catalog);
|
|
3769
|
+
const requested = normalizePricingInterval(options.interval ?? null);
|
|
3770
|
+
const interval = requested ?? getDefaultInterval(catalog);
|
|
3771
|
+
const contactSales = new Set(options.contactSales ?? []);
|
|
3772
|
+
const isContactSales = (group) => contactSales.has(group.key) || group.plans.some((plan) => contactSales.has(plan.code));
|
|
3773
|
+
let groups = getPricingGroups(catalog);
|
|
3774
|
+
if (!families) {
|
|
3775
|
+
if (options.sort) {
|
|
3776
|
+
const sort = options.sort;
|
|
3777
|
+
groups = groups.map((group, index) => ({ group, index })).sort((a, b) => sort(a.group.plans[0], b.group.plans[0]) || a.index - b.index).map(({ group }) => group);
|
|
3778
|
+
} else {
|
|
3779
|
+
const sales = groups.filter(isContactSales);
|
|
3780
|
+
const byPlan = new Map(groups.map((group) => [group.plans[0], group]));
|
|
3781
|
+
groups = [
|
|
3782
|
+
...sortPlansByPrice(groups.filter((group) => !isContactSales(group)).map((group) => group.plans[0])).map((plan) => byPlan.get(plan)),
|
|
3783
|
+
...sales
|
|
3784
|
+
];
|
|
3785
|
+
}
|
|
3786
|
+
if (requested) {
|
|
3787
|
+
groups = groups.filter(
|
|
3788
|
+
(group) => group.plans[0].interval === requested || isContactSales(group)
|
|
3789
|
+
);
|
|
3790
|
+
}
|
|
3791
|
+
}
|
|
3792
|
+
const highlight = options.highlight ?? null;
|
|
3793
|
+
const highlightedKey = highlight !== null ? groups.find(
|
|
3794
|
+
(group) => group.key === highlight || group.plans.some((plan) => plan.code === highlight)
|
|
3795
|
+
)?.key : groups.find((group) => group.family?.pricing_highlighted === true)?.key;
|
|
3796
|
+
const badge = (family) => options.badge !== void 0 ? options.badge === null ? null : trimmed(options.badge) || null : trimmed(family?.badge_text) || "Most Popular";
|
|
3797
|
+
return groups.map((group, cardIndex) => {
|
|
3798
|
+
const plan = pickPlan(group.plans, interval);
|
|
3799
|
+
const family = group.family;
|
|
3800
|
+
const name = trimmed(family?.name) || plan.name;
|
|
3801
|
+
const description = trimmed(family?.description) || trimmed(plan.description) || null;
|
|
3802
|
+
const highlighted = highlightedKey !== void 0 && highlightedKey === group.key;
|
|
3803
|
+
const features = overrideFeatures(options.features, group, plan, cardIndex) ?? (family ? familyFeatures(family, plan, locale) : planCardFeatures(plan));
|
|
3804
|
+
return {
|
|
3805
|
+
key: group.key,
|
|
3806
|
+
family,
|
|
3807
|
+
plans: group.plans,
|
|
3808
|
+
plan,
|
|
3809
|
+
isFallbackInterval: families && interval !== null && plan.interval !== interval,
|
|
3810
|
+
name,
|
|
3811
|
+
description,
|
|
3812
|
+
...family ? priceStrings(plan, monthlyPlanOf(group), locale) : planPriceStrings(plan, locale),
|
|
3813
|
+
features,
|
|
3814
|
+
highlighted,
|
|
3815
|
+
badge: highlighted ? badge(family) : null,
|
|
3816
|
+
cta: trimmed(family?.cta_text) || `Get ${name}`,
|
|
3817
|
+
enterprise: isContactSales(group)
|
|
3818
|
+
};
|
|
3819
|
+
});
|
|
3820
|
+
}
|
|
3821
|
+
function PanelPlanCard({
|
|
3822
|
+
frame,
|
|
3823
|
+
el,
|
|
3824
|
+
name,
|
|
3825
|
+
description,
|
|
3826
|
+
price,
|
|
3827
|
+
interval,
|
|
3828
|
+
priceNote,
|
|
3829
|
+
features,
|
|
3830
|
+
isCurrent,
|
|
3831
|
+
isHighlighted,
|
|
3832
|
+
filled: filledStyle,
|
|
3833
|
+
highlightLabel,
|
|
3834
|
+
children
|
|
3835
|
+
}) {
|
|
3836
|
+
const filled = isHighlighted && filledStyle;
|
|
3837
|
+
const text2 = filled ? T.highlightForeground : T.foreground;
|
|
3838
|
+
const muted5 = filled ? `color-mix(in srgb, ${T.highlightForeground} 75%, ${T.highlight})` : T.mutedForeground;
|
|
3839
|
+
const edge = isHighlighted ? T.highlight : isCurrent ? T.foreground : T.border;
|
|
3840
|
+
const label = isCurrent ? "Active" : isHighlighted ? highlightLabel : null;
|
|
3841
|
+
return /* @__PURE__ */ jsxs(
|
|
3842
|
+
"div",
|
|
3843
|
+
{
|
|
3844
|
+
...frame({
|
|
3845
|
+
display: "flex",
|
|
3846
|
+
flexDirection: "column",
|
|
3847
|
+
overflow: "hidden",
|
|
3848
|
+
...text.body,
|
|
3849
|
+
background: filled ? T.highlight : T.card,
|
|
3850
|
+
color: text2,
|
|
3851
|
+
border: border(edge),
|
|
3852
|
+
borderRadius: T.radius,
|
|
3853
|
+
boxShadow: T.shadow
|
|
3854
|
+
}),
|
|
3855
|
+
children: [
|
|
3856
|
+
/* @__PURE__ */ jsxs(
|
|
3857
|
+
"div",
|
|
3858
|
+
{
|
|
3859
|
+
...el("planCard__header", {
|
|
3860
|
+
display: "flex",
|
|
3861
|
+
alignItems: "center",
|
|
3862
|
+
justifyContent: "space-between",
|
|
3863
|
+
gap: "0.75rem",
|
|
3864
|
+
padding: "0.875rem 1.25rem",
|
|
3865
|
+
borderBottom: border(filled ? `color-mix(in srgb, ${T.highlightForeground} 30%, ${T.highlight})` : T.border)
|
|
3866
|
+
}),
|
|
3867
|
+
children: [
|
|
3868
|
+
/* @__PURE__ */ jsx("h3", { ...el("planCard__name", { ...text.heading, color: text2, margin: 0 }), children: name }),
|
|
3869
|
+
label && /* @__PURE__ */ jsx(
|
|
3870
|
+
"span",
|
|
3871
|
+
{
|
|
3872
|
+
...el("planCard__badge", {
|
|
3873
|
+
...text.label,
|
|
3874
|
+
whiteSpace: "nowrap",
|
|
3875
|
+
// The current plan's label takes the colour of its button.
|
|
3876
|
+
color: filled ? text2 : isCurrent ? T.mutedForeground : T.highlight
|
|
3877
|
+
}),
|
|
3878
|
+
children: label
|
|
3879
|
+
}
|
|
3880
|
+
)
|
|
3881
|
+
]
|
|
3882
|
+
}
|
|
3883
|
+
),
|
|
3884
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "1rem", padding: "1.25rem", flex: 1 }, children: [
|
|
3885
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
3886
|
+
/* @__PURE__ */ jsxs("p", { ...el("planCard__price", { ...text.metric, color: text2, margin: 0 }), children: [
|
|
3887
|
+
price,
|
|
3888
|
+
interval && /* @__PURE__ */ jsxs("span", { style: { ...text.meta, color: muted5 }, children: [
|
|
3889
|
+
" / ",
|
|
3890
|
+
interval
|
|
3891
|
+
] })
|
|
3892
|
+
] }),
|
|
3893
|
+
priceNote && /* @__PURE__ */ jsx("p", { "data-testid": "pricing-billing", style: { ...text.meta, color: muted5, margin: "0.25rem 0 0" }, children: priceNote }),
|
|
3894
|
+
description && /* @__PURE__ */ jsx("p", { ...el("planCard__description", { ...text.secondary, color: muted5, margin: "0.25rem 0 0" }), children: description })
|
|
3895
|
+
] }),
|
|
3896
|
+
/* @__PURE__ */ jsx(
|
|
3897
|
+
"ul",
|
|
3898
|
+
{
|
|
3899
|
+
...el("planCard__features", {
|
|
3900
|
+
listStyle: "none",
|
|
3901
|
+
margin: 0,
|
|
3902
|
+
padding: 0,
|
|
3903
|
+
display: "flex",
|
|
3904
|
+
flexDirection: "column",
|
|
3905
|
+
gap: "0.375rem",
|
|
3906
|
+
flex: 1,
|
|
3907
|
+
...text.secondary,
|
|
3908
|
+
color: muted5
|
|
3909
|
+
}),
|
|
3910
|
+
children: features.map((feature, index) => {
|
|
3911
|
+
const item = typeof feature === "string" ? { label: feature } : feature;
|
|
3912
|
+
return /* @__PURE__ */ jsxs("li", { ...el("planCard__feature"), children: [
|
|
3913
|
+
item.label,
|
|
3914
|
+
item.description && /* @__PURE__ */ jsx("span", { style: { display: "block", ...text.meta, color: muted5 }, children: item.description })
|
|
3915
|
+
] }, `${index}:${item.label}`);
|
|
3916
|
+
})
|
|
3917
|
+
}
|
|
3918
|
+
),
|
|
3919
|
+
children
|
|
3920
|
+
] })
|
|
3921
|
+
]
|
|
3922
|
+
}
|
|
3923
|
+
);
|
|
3924
|
+
}
|
|
2967
3925
|
function Check() {
|
|
2968
3926
|
return /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", style: { flexShrink: 0 }, children: /* @__PURE__ */ jsx("path", { d: "M3.5 8.5L6.5 11.5L12.5 4.5", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round" }) });
|
|
2969
3927
|
}
|
|
2970
|
-
function
|
|
3928
|
+
function IntervalSwitch({
|
|
3929
|
+
options,
|
|
3930
|
+
selected,
|
|
3931
|
+
onChange,
|
|
3932
|
+
annualDiscountLabel,
|
|
3933
|
+
el
|
|
3934
|
+
}) {
|
|
2971
3935
|
const pill = {
|
|
2972
3936
|
display: "inline-flex",
|
|
3937
|
+
flexWrap: "wrap",
|
|
3938
|
+
justifyContent: "center",
|
|
2973
3939
|
borderRadius: "9999px",
|
|
2974
3940
|
padding: "4px",
|
|
2975
|
-
background:
|
|
3941
|
+
background: T.muted,
|
|
2976
3942
|
gap: "2px"
|
|
2977
3943
|
};
|
|
2978
3944
|
const tab = (active) => ({
|
|
2979
3945
|
padding: "8px 20px",
|
|
2980
3946
|
borderRadius: "9999px",
|
|
2981
3947
|
border: "none",
|
|
2982
|
-
background: active ?
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
fontSize: "0.875rem",
|
|
3948
|
+
background: active ? T.card : "transparent",
|
|
3949
|
+
...text.action,
|
|
3950
|
+
color: active ? T.foreground : T.mutedForeground,
|
|
2986
3951
|
cursor: "pointer",
|
|
2987
|
-
boxShadow: active ?
|
|
3952
|
+
boxShadow: active ? T.shadow : "none",
|
|
2988
3953
|
transition: "all 0.15s ease"
|
|
2989
3954
|
});
|
|
2990
|
-
return /* @__PURE__ */ jsx("div", { style: { display: "flex", justifyContent: "center", marginBottom: "2.5rem" }, children: /* @__PURE__ */
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3955
|
+
return /* @__PURE__ */ jsx("div", { style: { display: "flex", justifyContent: "center", marginBottom: "2.5rem" }, children: /* @__PURE__ */ jsx("div", { role: "group", "aria-label": "Billing interval", ...el("pricingTable__toggle", pill), children: options.map((option) => {
|
|
3956
|
+
const active = option.interval === selected;
|
|
3957
|
+
const note = option.interval === "yearly" && annualDiscountLabel ? annualDiscountLabel : option.interval !== "monthly" && option.savingsPercent !== null ? `Save ${option.savingsPercent}%` : null;
|
|
3958
|
+
return /* @__PURE__ */ jsxs(
|
|
3959
|
+
"button",
|
|
3960
|
+
{
|
|
3961
|
+
type: "button",
|
|
3962
|
+
"data-interval": option.interval,
|
|
3963
|
+
onClick: () => onChange(option.interval),
|
|
3964
|
+
...el("pricingTable__toggleButton", tab(active)),
|
|
3965
|
+
"aria-pressed": active,
|
|
3966
|
+
"aria-label": note ? `${option.label}, ${note.replace(/^Save /, "save ")}` : void 0,
|
|
3967
|
+
children: [
|
|
3968
|
+
option.label,
|
|
3969
|
+
note && /* @__PURE__ */ jsx("span", { style: {
|
|
3970
|
+
marginLeft: "6px",
|
|
3971
|
+
...text.meta,
|
|
3972
|
+
fontWeight: 600,
|
|
3973
|
+
color: active ? T.primary : T.mutedForeground
|
|
3974
|
+
}, children: note })
|
|
3975
|
+
]
|
|
3976
|
+
},
|
|
3977
|
+
option.interval
|
|
3978
|
+
);
|
|
3979
|
+
}) }) });
|
|
3002
3980
|
}
|
|
3003
3981
|
function Card({
|
|
3004
|
-
|
|
3005
|
-
features,
|
|
3982
|
+
card,
|
|
3006
3983
|
isCurrent,
|
|
3007
|
-
|
|
3008
|
-
isEnt,
|
|
3984
|
+
filled: filledStyle,
|
|
3009
3985
|
enterpriseEmail,
|
|
3010
|
-
onSelect
|
|
3986
|
+
onSelect,
|
|
3987
|
+
el,
|
|
3988
|
+
panel,
|
|
3989
|
+
isUpgrade
|
|
3011
3990
|
}) {
|
|
3012
3991
|
const [hover, setHover] = useState(false);
|
|
3013
|
-
const
|
|
3014
|
-
const
|
|
3992
|
+
const { plan, features } = card;
|
|
3993
|
+
const isHighlighted = card.highlighted;
|
|
3994
|
+
const isEnt = card.enterprise;
|
|
3995
|
+
const filled = isHighlighted && filledStyle;
|
|
3996
|
+
const text2 = filled ? T.highlightForeground : T.foreground;
|
|
3997
|
+
const muted5 = filled ? `color-mix(in srgb, ${T.highlightForeground} 75%, ${T.highlight})` : T.mutedForeground;
|
|
3998
|
+
const accent = filled ? T.highlightForeground : T.primary;
|
|
3999
|
+
const badgeLabel = isCurrent ? "Current Plan" : card.badge;
|
|
3015
4000
|
const handleClick = () => {
|
|
3016
4001
|
if (isCurrent) return;
|
|
3017
4002
|
if (isEnt && enterpriseEmail) {
|
|
@@ -3020,75 +4005,110 @@ function Card({
|
|
|
3020
4005
|
}
|
|
3021
4006
|
onSelect?.();
|
|
3022
4007
|
};
|
|
4008
|
+
const ctaText = isCurrent ? "Current Plan" : card.family?.cta_text?.trim() ? card.cta : isEnt ? "Contact Sales" : card.cta;
|
|
4009
|
+
const emphasised = isUpgrade ?? isHighlighted;
|
|
4010
|
+
const cta = onSelect && /* @__PURE__ */ jsx(
|
|
4011
|
+
CTAButton,
|
|
4012
|
+
{
|
|
4013
|
+
onClick: handleClick,
|
|
4014
|
+
disabled: isCurrent,
|
|
4015
|
+
variant: isCurrent ? "current" : isEnt ? "outline" : !emphasised ? "secondary" : filled ? "inverse" : isHighlighted ? "highlight" : "primary",
|
|
4016
|
+
el,
|
|
4017
|
+
children: ctaText
|
|
4018
|
+
}
|
|
4019
|
+
);
|
|
4020
|
+
const dataAttributes = {
|
|
4021
|
+
"data-card-key": card.key,
|
|
4022
|
+
"data-family-code": card.family?.code,
|
|
4023
|
+
"data-plan-code": plan.code,
|
|
4024
|
+
"data-interval": plan.interval,
|
|
4025
|
+
"data-highlighted": isHighlighted ? "true" : void 0
|
|
4026
|
+
};
|
|
4027
|
+
if (panel) {
|
|
4028
|
+
return /* @__PURE__ */ jsx(
|
|
4029
|
+
PanelPlanCard,
|
|
4030
|
+
{
|
|
4031
|
+
frame: (style) => ({ ...el("planCard", style), ...dataAttributes }),
|
|
4032
|
+
el,
|
|
4033
|
+
name: card.name,
|
|
4034
|
+
description: card.description,
|
|
4035
|
+
price: isEnt ? "Custom" : card.price,
|
|
4036
|
+
interval: isEnt || !card.pricePeriod ? void 0 : card.pricePeriod.replace(/^\//, ""),
|
|
4037
|
+
priceNote: isEnt ? null : card.billing,
|
|
4038
|
+
features,
|
|
4039
|
+
isCurrent,
|
|
4040
|
+
isHighlighted,
|
|
4041
|
+
filled: filledStyle,
|
|
4042
|
+
highlightLabel: card.badge,
|
|
4043
|
+
children: cta
|
|
4044
|
+
}
|
|
4045
|
+
);
|
|
4046
|
+
}
|
|
3023
4047
|
return /* @__PURE__ */ jsxs(
|
|
3024
4048
|
"div",
|
|
3025
4049
|
{
|
|
4050
|
+
...dataAttributes,
|
|
3026
4051
|
onMouseEnter: () => setHover(true),
|
|
3027
4052
|
onMouseLeave: () => setHover(false),
|
|
3028
|
-
|
|
4053
|
+
...el("planCard", {
|
|
3029
4054
|
position: "relative",
|
|
3030
4055
|
display: "flex",
|
|
3031
4056
|
flexDirection: "column",
|
|
3032
|
-
background:
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
4057
|
+
background: filled ? T.highlight : T.card,
|
|
4058
|
+
color: text2,
|
|
4059
|
+
border: border(isHighlighted ? T.highlight : T.border),
|
|
4060
|
+
borderTopWidth: isHighlighted && !filled ? `calc(${T.borderWidth} + 2px)` : T.borderWidth,
|
|
4061
|
+
borderRadius: T.radius,
|
|
3036
4062
|
padding: "2rem",
|
|
3037
4063
|
transition: "box-shadow 0.2s ease, transform 0.2s ease",
|
|
3038
|
-
boxShadow: shadow,
|
|
4064
|
+
boxShadow: T.shadow,
|
|
3039
4065
|
transform: hover && !isCurrent ? "translateY(-2px)" : "none"
|
|
3040
|
-
},
|
|
4066
|
+
}),
|
|
3041
4067
|
children: [
|
|
3042
|
-
isHighlighted && /* @__PURE__ */ jsx("div", {
|
|
4068
|
+
isHighlighted && badgeLabel && /* @__PURE__ */ jsx("div", { ...el("planCard__badge", {
|
|
3043
4069
|
position: "absolute",
|
|
3044
4070
|
top: "-1px",
|
|
3045
4071
|
left: "50%",
|
|
3046
4072
|
transform: "translate(-50%, -50%)",
|
|
3047
|
-
background:
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
4073
|
+
background: filled ? T.highlightForeground : T.highlight,
|
|
4074
|
+
border: filled ? border(T.highlight) : void 0,
|
|
4075
|
+
...text.label,
|
|
4076
|
+
color: filled ? T.highlight : T.highlightForeground,
|
|
3051
4077
|
padding: "4px 12px",
|
|
3052
4078
|
borderRadius: "9999px",
|
|
3053
|
-
letterSpacing: "0.025em",
|
|
3054
|
-
textTransform: "uppercase",
|
|
3055
4079
|
whiteSpace: "nowrap"
|
|
3056
|
-
}, children:
|
|
3057
|
-
isCurrent && !isHighlighted && /* @__PURE__ */ jsx("div", {
|
|
4080
|
+
}), children: badgeLabel }),
|
|
4081
|
+
isCurrent && !isHighlighted && /* @__PURE__ */ jsx("div", { ...el("planCard__badge", {
|
|
3058
4082
|
position: "absolute",
|
|
3059
4083
|
top: "0",
|
|
3060
4084
|
left: "50%",
|
|
3061
4085
|
transform: "translate(-50%, -50%)",
|
|
3062
|
-
background:
|
|
3063
|
-
|
|
3064
|
-
fontSize: "0.7rem",
|
|
3065
|
-
fontWeight: 700,
|
|
4086
|
+
background: T.muted,
|
|
4087
|
+
...text.label,
|
|
3066
4088
|
padding: "4px 12px",
|
|
3067
4089
|
borderRadius: "9999px",
|
|
3068
|
-
letterSpacing: "0.025em",
|
|
3069
|
-
textTransform: "uppercase",
|
|
3070
4090
|
whiteSpace: "nowrap"
|
|
3071
|
-
}, children: "Current Plan" }),
|
|
3072
|
-
/* @__PURE__ */ jsxs("div", { style: { marginBottom: "1.25rem", marginTop: isHighlighted || isCurrent ? "0.5rem" : 0 }, children: [
|
|
3073
|
-
/* @__PURE__ */ jsx("h3", {
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
margin: "6px 0 0",
|
|
3084
|
-
lineHeight: 1.5
|
|
3085
|
-
}, children: plan.description })
|
|
4091
|
+
}), children: "Current Plan" }),
|
|
4092
|
+
/* @__PURE__ */ jsxs("div", { style: { marginBottom: "1.25rem", marginTop: isHighlighted && badgeLabel || isCurrent ? "0.5rem" : 0 }, children: [
|
|
4093
|
+
/* @__PURE__ */ jsx("h3", { ...el("planCard__name", {
|
|
4094
|
+
...text.heading,
|
|
4095
|
+
color: text2,
|
|
4096
|
+
margin: 0
|
|
4097
|
+
}), children: card.name }),
|
|
4098
|
+
card.description && /* @__PURE__ */ jsx("p", { ...el("planCard__description", {
|
|
4099
|
+
...text.secondary,
|
|
4100
|
+
color: muted5,
|
|
4101
|
+
margin: "6px 0 0"
|
|
4102
|
+
}), children: card.description })
|
|
3086
4103
|
] }),
|
|
3087
|
-
/* @__PURE__ */ jsx("div", {
|
|
3088
|
-
/* @__PURE__ */
|
|
3089
|
-
|
|
4104
|
+
/* @__PURE__ */ jsx("div", { ...el("planCard__price", { marginBottom: "1.5rem" }), children: isEnt ? /* @__PURE__ */ jsx("span", { style: { ...text.metric, color: text2 }, children: "Custom" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4105
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "baseline", gap: "2px" }, children: [
|
|
4106
|
+
/* @__PURE__ */ jsx("span", { "data-testid": "pricing-price", style: { ...text.metric, color: text2 }, children: card.price }),
|
|
4107
|
+
card.pricePeriod && /* @__PURE__ */ jsx("span", { style: { ...text.meta, color: muted5, marginLeft: "4px" }, children: card.pricePeriod })
|
|
4108
|
+
] }),
|
|
4109
|
+
card.billing && /* @__PURE__ */ jsx("p", { "data-testid": "pricing-billing", style: { ...text.meta, color: muted5, margin: "8px 0 0" }, children: card.billing })
|
|
3090
4110
|
] }) }),
|
|
3091
|
-
features
|
|
4111
|
+
features.length > 0 && /* @__PURE__ */ jsx("ul", { ...el("planCard__features", {
|
|
3092
4112
|
listStyle: "none",
|
|
3093
4113
|
padding: 0,
|
|
3094
4114
|
margin: "0 0 2rem",
|
|
@@ -3096,27 +4116,21 @@ function Card({
|
|
|
3096
4116
|
flexDirection: "column",
|
|
3097
4117
|
gap: "0.75rem",
|
|
3098
4118
|
flex: 1
|
|
3099
|
-
}, children: features.map((feat) => /* @__PURE__ */ jsxs("li", {
|
|
4119
|
+
}), children: features.map((feat, index) => /* @__PURE__ */ jsxs("li", { ...el("planCard__feature", {
|
|
3100
4120
|
display: "flex",
|
|
3101
4121
|
alignItems: "flex-start",
|
|
3102
4122
|
gap: "10px",
|
|
3103
|
-
|
|
3104
|
-
color:
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
/* @__PURE__ */
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
onClick: handleClick,
|
|
3115
|
-
disabled: isCurrent,
|
|
3116
|
-
variant: isCurrent ? "current" : isEnt ? "outline" : isHighlighted ? "primary" : "secondary",
|
|
3117
|
-
children: isCurrent ? "Current Plan" : isEnt ? "Contact Sales" : `Get ${plan.name}`
|
|
3118
|
-
}
|
|
3119
|
-
)
|
|
4123
|
+
...text.body,
|
|
4124
|
+
color: text2
|
|
4125
|
+
}), children: [
|
|
4126
|
+
/* @__PURE__ */ jsx("span", { style: { color: accent, marginTop: "2px" }, children: /* @__PURE__ */ jsx(Check, {}) }),
|
|
4127
|
+
/* @__PURE__ */ jsxs("span", { style: { display: "flex", flexDirection: "column" }, children: [
|
|
4128
|
+
/* @__PURE__ */ jsx("span", { children: feat.label }),
|
|
4129
|
+
feat.description && /* @__PURE__ */ jsx("span", { style: { ...text.meta, color: muted5, marginTop: "2px" }, children: feat.description })
|
|
4130
|
+
] })
|
|
4131
|
+
] }, `${index}:${feat.label}`)) }),
|
|
4132
|
+
features.length === 0 && /* @__PURE__ */ jsx("div", { style: { flex: 1 } }),
|
|
4133
|
+
cta
|
|
3120
4134
|
]
|
|
3121
4135
|
}
|
|
3122
4136
|
);
|
|
@@ -3125,124 +4139,94 @@ function CTAButton({
|
|
|
3125
4139
|
children,
|
|
3126
4140
|
onClick,
|
|
3127
4141
|
disabled,
|
|
3128
|
-
variant
|
|
4142
|
+
variant,
|
|
4143
|
+
el
|
|
3129
4144
|
}) {
|
|
3130
4145
|
const [hover, setHover] = useState(false);
|
|
3131
|
-
const
|
|
3132
|
-
width: "100%",
|
|
3133
|
-
padding: "12px 24px",
|
|
3134
|
-
borderRadius: "10px",
|
|
3135
|
-
fontSize: "0.9375rem",
|
|
3136
|
-
fontWeight: 600,
|
|
3137
|
-
cursor: disabled ? "default" : "pointer",
|
|
3138
|
-
border: "none",
|
|
3139
|
-
transition: "all 0.15s ease",
|
|
3140
|
-
lineHeight: 1.4
|
|
3141
|
-
};
|
|
3142
|
-
const variants = {
|
|
3143
|
-
primary: {
|
|
3144
|
-
background: hover && !disabled ? "#4f46e5" : C.hl,
|
|
3145
|
-
color: C.hlFg
|
|
3146
|
-
},
|
|
3147
|
-
secondary: {
|
|
3148
|
-
background: hover && !disabled ? "#f0f0f3" : C.mutedBg,
|
|
3149
|
-
color: C.text,
|
|
3150
|
-
border: `1px solid ${C.border}`
|
|
3151
|
-
},
|
|
3152
|
-
outline: {
|
|
3153
|
-
background: hover && !disabled ? "rgba(99,102,241,0.04)" : "transparent",
|
|
3154
|
-
color: C.hl,
|
|
3155
|
-
border: `1.5px solid ${C.hl}`
|
|
3156
|
-
},
|
|
3157
|
-
current: {
|
|
3158
|
-
background: C.mutedBg,
|
|
3159
|
-
color: C.muted
|
|
3160
|
-
}
|
|
3161
|
-
};
|
|
4146
|
+
const look = variant === "current" ? { ...buttonStyle("secondary"), background: T.muted, color: T.mutedForeground, cursor: "default" } : variant === "highlight" ? { ...buttonStyle("primary"), background: T.highlight, color: T.highlightForeground, border: primaryButtonBorder(T.highlight) } : variant === "inverse" ? { ...buttonStyle("primary"), background: T.highlightForeground, color: T.highlight, border: primaryButtonBorder(T.highlightForeground) } : buttonStyle(variant);
|
|
3162
4147
|
return /* @__PURE__ */ jsx(
|
|
3163
4148
|
"button",
|
|
3164
4149
|
{
|
|
4150
|
+
type: "button",
|
|
3165
4151
|
onClick: disabled ? void 0 : onClick,
|
|
3166
4152
|
disabled,
|
|
3167
4153
|
onMouseEnter: () => setHover(true),
|
|
3168
4154
|
onMouseLeave: () => setHover(false),
|
|
3169
|
-
|
|
4155
|
+
...el("planCard__cta", {
|
|
4156
|
+
...look,
|
|
4157
|
+
width: "100%",
|
|
4158
|
+
filter: hover && !disabled ? "brightness(0.95)" : void 0
|
|
4159
|
+
}),
|
|
3170
4160
|
children
|
|
3171
4161
|
}
|
|
3172
4162
|
);
|
|
3173
4163
|
}
|
|
3174
|
-
function Skeleton() {
|
|
3175
|
-
return /* @__PURE__ */ jsx("div", { style: {
|
|
4164
|
+
function Skeleton({ rootProps }) {
|
|
4165
|
+
return /* @__PURE__ */ jsx("div", { ...rootProps, "aria-busy": "true", style: {
|
|
4166
|
+
...rootProps.style,
|
|
3176
4167
|
display: "grid",
|
|
3177
4168
|
gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
|
|
3178
4169
|
gap: "1.5rem"
|
|
3179
4170
|
}, children: [0, 1, 2].map((i) => /* @__PURE__ */ jsxs("div", { style: {
|
|
3180
|
-
background:
|
|
3181
|
-
border:
|
|
3182
|
-
borderRadius:
|
|
4171
|
+
background: T.card,
|
|
4172
|
+
border: border(),
|
|
4173
|
+
borderRadius: T.radius,
|
|
3183
4174
|
padding: "2rem",
|
|
3184
4175
|
height: "380px"
|
|
3185
4176
|
}, children: [
|
|
3186
|
-
/* @__PURE__ */ jsx("div", { style: { height: 20, width: "40%", borderRadius: 6, background:
|
|
3187
|
-
/* @__PURE__ */ jsx("div", { style: { height: 40, width: "50%", borderRadius: 6, background:
|
|
3188
|
-
/* @__PURE__ */ jsx("div", { style: { height: 14, width: "80%", borderRadius: 6, background:
|
|
3189
|
-
/* @__PURE__ */ jsx("div", { style: { height: 14, width: "70%", borderRadius: 6, background:
|
|
3190
|
-
/* @__PURE__ */ jsx("div", { style: { height: 14, width: "60%", borderRadius: 6, background:
|
|
4177
|
+
/* @__PURE__ */ jsx("div", { style: { height: 20, width: "40%", borderRadius: 6, background: T.skeleton, marginBottom: 12 } }),
|
|
4178
|
+
/* @__PURE__ */ jsx("div", { style: { height: 40, width: "50%", borderRadius: 6, background: T.skeleton, marginBottom: 24 } }),
|
|
4179
|
+
/* @__PURE__ */ jsx("div", { style: { height: 14, width: "80%", borderRadius: 6, background: T.skeleton, marginBottom: 10 } }),
|
|
4180
|
+
/* @__PURE__ */ jsx("div", { style: { height: 14, width: "70%", borderRadius: 6, background: T.skeleton, marginBottom: 10 } }),
|
|
4181
|
+
/* @__PURE__ */ jsx("div", { style: { height: 14, width: "60%", borderRadius: 6, background: T.skeleton } })
|
|
3191
4182
|
] }, i)) });
|
|
3192
4183
|
}
|
|
3193
4184
|
function PricingTable({
|
|
3194
4185
|
currentPlanCode,
|
|
3195
4186
|
plans: propPlans,
|
|
4187
|
+
families: propFamilies,
|
|
3196
4188
|
features,
|
|
4189
|
+
sort,
|
|
3197
4190
|
onSelect,
|
|
3198
4191
|
returnUrl,
|
|
3199
4192
|
onStripeClientSecret,
|
|
3200
4193
|
onCheckoutResult,
|
|
3201
4194
|
onCheckoutError,
|
|
3202
4195
|
showToggle = true,
|
|
4196
|
+
defaultInterval,
|
|
3203
4197
|
highlightPlan,
|
|
4198
|
+
highlightStyle = "outline",
|
|
4199
|
+
highlightLabel,
|
|
3204
4200
|
className,
|
|
3205
|
-
|
|
4201
|
+
contactSalesPlans,
|
|
4202
|
+
enterpriseEmail,
|
|
4203
|
+
annualDiscountLabel,
|
|
4204
|
+
locale: localeProp,
|
|
4205
|
+
appearance
|
|
3206
4206
|
}) {
|
|
4207
|
+
const { root, el, planCardLayout } = useAppearance(appearance);
|
|
4208
|
+
const locale = resolvePricingLocale(localeProp);
|
|
3207
4209
|
const billing = useOptionalBillingContext();
|
|
3208
4210
|
const client = billing?.client;
|
|
3209
|
-
const
|
|
4211
|
+
const fetched = useQuery(
|
|
4212
|
+
{ ...catalogQuery(client), enabled: !propPlans && Boolean(client) },
|
|
4213
|
+
billing?.queryClient ?? idleQueryClient
|
|
4214
|
+
);
|
|
4215
|
+
const catalog = propPlans ? { plans: propPlans, families: Array.isArray(propFamilies) ? propFamilies : null } : { plans: fetched.data?.plans ?? [], families: fetched.data?.families ?? null };
|
|
4216
|
+
const loading = !propPlans && Boolean(client) && fetched.isPending;
|
|
4217
|
+
const loadError = propPlans ? null : !client ? "BillingProvider with a publishableKey is required to load plans" : fetched.error ? `Plans could not be loaded: ${fetched.error.message}` : null;
|
|
3210
4218
|
const [currentPlan, setCurrentPlan] = useState(currentPlanCode ?? null);
|
|
3211
|
-
const [
|
|
3212
|
-
const [loading, setLoading] = useState(!propPlans);
|
|
4219
|
+
const [selectedInterval, setSelectedInterval] = useState(null);
|
|
3213
4220
|
const [processing, setProcessing] = useState(false);
|
|
3214
4221
|
const [checkoutError, setCheckoutError] = useState(null);
|
|
3215
|
-
useEffect(() => {
|
|
3216
|
-
if (propPlans) {
|
|
3217
|
-
setPlans(propPlans);
|
|
3218
|
-
return;
|
|
3219
|
-
}
|
|
3220
|
-
if (!client) {
|
|
3221
|
-
setLoading(false);
|
|
3222
|
-
setCheckoutError("BillingProvider with a publishableKey is required to load plans");
|
|
3223
|
-
return;
|
|
3224
|
-
}
|
|
3225
|
-
let cancelled = false;
|
|
3226
|
-
client.catalogFetch("/api/v1/plans").then(async (res) => {
|
|
3227
|
-
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
3228
|
-
const data = await res.json();
|
|
3229
|
-
if (!cancelled) setPlans(data.plans || []);
|
|
3230
|
-
}).catch((cause) => {
|
|
3231
|
-
if (!cancelled) setCheckoutError(`Plans could not be loaded: ${cause instanceof Error ? cause.message : "unknown error"}`);
|
|
3232
|
-
}).finally(() => {
|
|
3233
|
-
if (!cancelled) setLoading(false);
|
|
3234
|
-
});
|
|
3235
|
-
return () => {
|
|
3236
|
-
cancelled = true;
|
|
3237
|
-
};
|
|
3238
|
-
}, [client, propPlans]);
|
|
3239
4222
|
useEffect(() => {
|
|
3240
4223
|
if (currentPlanCode !== void 0) setCurrentPlan(currentPlanCode);
|
|
3241
4224
|
}, [currentPlanCode]);
|
|
3242
|
-
|
|
4225
|
+
const withFamilies = Array.isArray(catalog.families);
|
|
4226
|
+
async function selectPlan(plan, family) {
|
|
3243
4227
|
setCheckoutError(null);
|
|
3244
4228
|
if (onSelect) {
|
|
3245
|
-
onSelect(plan);
|
|
4229
|
+
onSelect(plan, family);
|
|
3246
4230
|
return;
|
|
3247
4231
|
}
|
|
3248
4232
|
try {
|
|
@@ -3268,124 +4252,143 @@ function PricingTable({
|
|
|
3268
4252
|
onCheckoutError?.(error);
|
|
3269
4253
|
}
|
|
3270
4254
|
}
|
|
3271
|
-
const
|
|
3272
|
-
const
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
4255
|
+
const available = getAvailableIntervals(catalog);
|
|
4256
|
+
const interval = selectedInterval !== null && available.includes(selectedInterval) ? selectedInterval : getDefaultInterval(catalog, defaultInterval);
|
|
4257
|
+
const switchVisible = showToggle && available.length > 1 && interval !== null;
|
|
4258
|
+
const cards = getPricingCards(catalog, {
|
|
4259
|
+
interval: withFamilies || switchVisible ? interval : null,
|
|
4260
|
+
locale,
|
|
4261
|
+
highlight: highlightPlan,
|
|
4262
|
+
badge: highlightLabel,
|
|
4263
|
+
features,
|
|
4264
|
+
contactSales: contactSalesPlans,
|
|
4265
|
+
sort
|
|
4266
|
+
});
|
|
4267
|
+
const current = catalog.plans.find((p) => p.code === currentPlan);
|
|
4268
|
+
const currentYearly = current ? yearlyMinor(current) : null;
|
|
4269
|
+
const isUpgrade = (plan) => {
|
|
4270
|
+
if (!current || current.amount_currency !== plan.amount_currency) return void 0;
|
|
4271
|
+
const yearly = yearlyMinor(plan);
|
|
4272
|
+
return currentYearly !== null && yearly !== null ? yearly > currentYearly : plan.amount_cents > current.amount_cents;
|
|
4273
|
+
};
|
|
4274
|
+
const rootProps = root("pricingTable", text.body, className);
|
|
4275
|
+
if (loading) return /* @__PURE__ */ jsx(Skeleton, { rootProps });
|
|
4276
|
+
return /* @__PURE__ */ jsxs("div", { ...rootProps, children: [
|
|
4277
|
+
switchVisible && interval !== null && /* @__PURE__ */ jsx(
|
|
4278
|
+
IntervalSwitch,
|
|
4279
|
+
{
|
|
4280
|
+
options: getPricingIntervalOptions(catalog),
|
|
4281
|
+
selected: interval,
|
|
4282
|
+
onChange: setSelectedInterval,
|
|
4283
|
+
annualDiscountLabel,
|
|
4284
|
+
el
|
|
4285
|
+
}
|
|
4286
|
+
),
|
|
3280
4287
|
processing && /* @__PURE__ */ jsx("p", { role: "status", children: "Payment is processing. Confirmation may take a little while." }),
|
|
3281
|
-
checkoutError && /* @__PURE__ */ jsx("p", { role: "alert", children: checkoutError }),
|
|
3282
|
-
/* @__PURE__ */ jsx("div", {
|
|
4288
|
+
(checkoutError ?? loadError) && /* @__PURE__ */ jsx("p", { role: "alert", style: { color: T.dangerText }, children: checkoutError ?? loadError }),
|
|
4289
|
+
/* @__PURE__ */ jsx("div", { ...el("pricingTable__grid", {
|
|
3283
4290
|
display: "grid",
|
|
3284
4291
|
gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
|
|
3285
4292
|
gap: "1.5rem"
|
|
3286
|
-
}, children:
|
|
4293
|
+
}), children: cards.map((card) => /* @__PURE__ */ jsx(
|
|
3287
4294
|
Card,
|
|
3288
4295
|
{
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
isEnt: isEnterprise(plan),
|
|
4296
|
+
card,
|
|
4297
|
+
isCurrent: currentPlan === card.plan.code,
|
|
4298
|
+
isUpgrade: isUpgrade(card.plan),
|
|
4299
|
+
filled: highlightStyle === "filled",
|
|
3294
4300
|
enterpriseEmail,
|
|
3295
|
-
onSelect: () => void selectPlan(plan)
|
|
4301
|
+
onSelect: () => void selectPlan(card.plan, card.family),
|
|
4302
|
+
el,
|
|
4303
|
+
panel: planCardLayout === "panel"
|
|
3296
4304
|
},
|
|
3297
|
-
|
|
3298
|
-
)) })
|
|
4305
|
+
card.key
|
|
4306
|
+
)) }),
|
|
4307
|
+
/* @__PURE__ */ jsx(NozleBranding, {})
|
|
3299
4308
|
] });
|
|
3300
4309
|
}
|
|
3301
4310
|
function PlanCard({
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
monthlyPrice,
|
|
3305
|
-
annualPrice,
|
|
3306
|
-
features,
|
|
3307
|
-
description,
|
|
3308
|
-
isAnnual,
|
|
4311
|
+
plan,
|
|
4312
|
+
features: featureOverride,
|
|
3309
4313
|
isCurrent,
|
|
3310
4314
|
returnUrl,
|
|
3311
|
-
onSelect
|
|
4315
|
+
onSelect,
|
|
4316
|
+
appearance
|
|
3312
4317
|
}) {
|
|
3313
|
-
const
|
|
3314
|
-
const
|
|
4318
|
+
const { root, el, planCardLayout } = useAppearance(appearance);
|
|
4319
|
+
const { code: id, name, description } = plan;
|
|
4320
|
+
const price = formatPlanPrice(plan.amount_cents, plan.amount_currency);
|
|
4321
|
+
const interval = intervalLabel(plan.interval);
|
|
4322
|
+
const features = featureOverride ?? planFeatures(plan);
|
|
4323
|
+
const cta = onSelect && /* @__PURE__ */ jsx(
|
|
4324
|
+
"button",
|
|
4325
|
+
{
|
|
4326
|
+
type: "button",
|
|
4327
|
+
onClick: () => onSelect?.({
|
|
4328
|
+
planCode: id,
|
|
4329
|
+
returnUrl: returnUrl ?? window.location.href
|
|
4330
|
+
}),
|
|
4331
|
+
...el("planCard__cta", {
|
|
4332
|
+
...buttonStyle(isCurrent ? "secondary" : "primary"),
|
|
4333
|
+
...isCurrent && { background: T.muted, color: T.mutedForeground, cursor: "default" },
|
|
4334
|
+
width: "100%"
|
|
4335
|
+
}),
|
|
4336
|
+
disabled: isCurrent,
|
|
4337
|
+
"aria-label": isCurrent ? `Current plan: ${name}` : `Select ${name}`,
|
|
4338
|
+
children: isCurrent ? "Current Plan" : "Get Started"
|
|
4339
|
+
}
|
|
4340
|
+
);
|
|
4341
|
+
if (planCardLayout === "panel") {
|
|
4342
|
+
return /* @__PURE__ */ jsx(
|
|
4343
|
+
PanelPlanCard,
|
|
4344
|
+
{
|
|
4345
|
+
frame: (style) => ({ "data-testid": `plan-card-${id}`, "aria-current": isCurrent ? "true" : void 0, ...root("planCard", style) }),
|
|
4346
|
+
el,
|
|
4347
|
+
name,
|
|
4348
|
+
description,
|
|
4349
|
+
price,
|
|
4350
|
+
interval,
|
|
4351
|
+
features,
|
|
4352
|
+
isCurrent: Boolean(isCurrent),
|
|
4353
|
+
isHighlighted: false,
|
|
4354
|
+
filled: false,
|
|
4355
|
+
children: cta
|
|
4356
|
+
}
|
|
4357
|
+
);
|
|
4358
|
+
}
|
|
3315
4359
|
return /* @__PURE__ */ jsxs(
|
|
3316
4360
|
"div",
|
|
3317
4361
|
{
|
|
3318
4362
|
"data-testid": `plan-card-${id}`,
|
|
3319
4363
|
"aria-current": isCurrent ? "true" : void 0,
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
4364
|
+
...root("planCard", {
|
|
4365
|
+
...text.body,
|
|
4366
|
+
background: T.card,
|
|
4367
|
+
border: border(isCurrent ? T.primary : T.border),
|
|
4368
|
+
outline: isCurrent ? `1px solid ${T.primary}` : void 0,
|
|
4369
|
+
borderRadius: T.radius,
|
|
4370
|
+
boxShadow: T.shadow,
|
|
3324
4371
|
padding: "1.5rem",
|
|
3325
4372
|
display: "flex",
|
|
3326
4373
|
flexDirection: "column",
|
|
3327
4374
|
gap: "1rem"
|
|
3328
|
-
},
|
|
3329
|
-
className: isCurrent ? "border-primary" : "",
|
|
4375
|
+
}),
|
|
3330
4376
|
children: [
|
|
3331
4377
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
3332
|
-
/* @__PURE__ */ jsx(
|
|
3333
|
-
|
|
3334
|
-
{
|
|
3335
|
-
style: {
|
|
3336
|
-
fontSize: "1.25rem",
|
|
3337
|
-
fontWeight: 600,
|
|
3338
|
-
color: "var(--nozle-foreground, var(--foreground))",
|
|
3339
|
-
margin: 0
|
|
3340
|
-
},
|
|
3341
|
-
children: name
|
|
3342
|
-
}
|
|
3343
|
-
),
|
|
3344
|
-
description && /* @__PURE__ */ jsx(
|
|
3345
|
-
"p",
|
|
3346
|
-
{
|
|
3347
|
-
style: {
|
|
3348
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))",
|
|
3349
|
-
fontSize: "0.875rem",
|
|
3350
|
-
margin: "0.25rem 0 0"
|
|
3351
|
-
},
|
|
3352
|
-
children: description
|
|
3353
|
-
}
|
|
3354
|
-
)
|
|
4378
|
+
/* @__PURE__ */ jsx("h3", { ...el("planCard__name", { ...text.heading, margin: 0 }), children: name }),
|
|
4379
|
+
description && /* @__PURE__ */ jsx("p", { ...el("planCard__description", { ...text.secondary, margin: "0.25rem 0 0" }), children: description })
|
|
3355
4380
|
] }),
|
|
3356
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
3357
|
-
/* @__PURE__ */
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
fontWeight: 700,
|
|
3363
|
-
color: "var(--nozle-foreground, var(--foreground))"
|
|
3364
|
-
},
|
|
3365
|
-
children: [
|
|
3366
|
-
"$",
|
|
3367
|
-
price
|
|
3368
|
-
]
|
|
3369
|
-
}
|
|
3370
|
-
),
|
|
3371
|
-
/* @__PURE__ */ jsxs(
|
|
3372
|
-
"span",
|
|
3373
|
-
{
|
|
3374
|
-
style: {
|
|
3375
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))",
|
|
3376
|
-
marginLeft: "0.25rem"
|
|
3377
|
-
},
|
|
3378
|
-
children: [
|
|
3379
|
-
"/",
|
|
3380
|
-
interval
|
|
3381
|
-
]
|
|
3382
|
-
}
|
|
3383
|
-
)
|
|
4381
|
+
/* @__PURE__ */ jsxs("div", { ...el("planCard__price"), children: [
|
|
4382
|
+
/* @__PURE__ */ jsx("span", { style: text.metric, children: price }),
|
|
4383
|
+
/* @__PURE__ */ jsxs("span", { style: { ...text.meta, marginLeft: "0.25rem" }, children: [
|
|
4384
|
+
"/",
|
|
4385
|
+
interval
|
|
4386
|
+
] })
|
|
3384
4387
|
] }),
|
|
3385
4388
|
/* @__PURE__ */ jsx(
|
|
3386
4389
|
"ul",
|
|
3387
4390
|
{
|
|
3388
|
-
|
|
4391
|
+
...el("planCard__features", {
|
|
3389
4392
|
listStyle: "none",
|
|
3390
4393
|
padding: 0,
|
|
3391
4394
|
margin: 0,
|
|
@@ -3393,160 +4396,51 @@ function PlanCard({
|
|
|
3393
4396
|
flexDirection: "column",
|
|
3394
4397
|
gap: "0.5rem",
|
|
3395
4398
|
flex: 1
|
|
3396
|
-
},
|
|
3397
|
-
children: features.map((feature) => /* @__PURE__ */ jsxs(
|
|
3398
|
-
"
|
|
3399
|
-
{
|
|
3400
|
-
style: {
|
|
3401
|
-
display: "flex",
|
|
3402
|
-
alignItems: "center",
|
|
3403
|
-
gap: "0.5rem",
|
|
3404
|
-
color: "var(--nozle-foreground, var(--foreground))",
|
|
3405
|
-
fontSize: "0.875rem"
|
|
3406
|
-
},
|
|
3407
|
-
children: [
|
|
3408
|
-
/* @__PURE__ */ jsx(
|
|
3409
|
-
"span",
|
|
3410
|
-
{
|
|
3411
|
-
style: { color: "var(--nozle-primary, var(--primary))" },
|
|
3412
|
-
"aria-hidden": "true",
|
|
3413
|
-
children: "\u2713"
|
|
3414
|
-
}
|
|
3415
|
-
),
|
|
3416
|
-
feature
|
|
3417
|
-
]
|
|
3418
|
-
},
|
|
4399
|
+
}),
|
|
4400
|
+
children: features.map((feature) => /* @__PURE__ */ jsxs("li", { ...el("planCard__feature", { display: "flex", alignItems: "center", gap: "0.5rem", ...text.body }), children: [
|
|
4401
|
+
/* @__PURE__ */ jsx("span", { style: { color: T.primary }, "aria-hidden": "true", children: "\u2713" }),
|
|
3419
4402
|
feature
|
|
3420
|
-
))
|
|
4403
|
+
] }, feature))
|
|
3421
4404
|
}
|
|
3422
4405
|
),
|
|
3423
|
-
|
|
3424
|
-
"button",
|
|
3425
|
-
{
|
|
3426
|
-
onClick: () => onSelect?.({
|
|
3427
|
-
planCode: id,
|
|
3428
|
-
returnUrl: returnUrl ?? window.location.href
|
|
3429
|
-
}),
|
|
3430
|
-
style: {
|
|
3431
|
-
background: isCurrent ? "var(--nozle-muted, var(--muted))" : "var(--nozle-primary, var(--primary))",
|
|
3432
|
-
color: isCurrent ? "var(--nozle-muted-foreground, var(--muted-foreground))" : "var(--nozle-primary-foreground, var(--primary-foreground))",
|
|
3433
|
-
border: "none",
|
|
3434
|
-
borderRadius: "var(--nozle-radius, 0.5rem)",
|
|
3435
|
-
padding: "0.75rem 1.5rem",
|
|
3436
|
-
cursor: isCurrent ? "default" : "pointer",
|
|
3437
|
-
fontWeight: 500,
|
|
3438
|
-
width: "100%"
|
|
3439
|
-
},
|
|
3440
|
-
disabled: isCurrent,
|
|
3441
|
-
"aria-label": isCurrent ? `Current plan: ${name}` : `Select ${name}`,
|
|
3442
|
-
children: isCurrent ? "Current Plan" : "Get Started"
|
|
3443
|
-
}
|
|
3444
|
-
)
|
|
4406
|
+
cta
|
|
3445
4407
|
]
|
|
3446
4408
|
}
|
|
3447
4409
|
);
|
|
3448
4410
|
}
|
|
3449
4411
|
function PlanComparison({
|
|
3450
|
-
plans,
|
|
3451
|
-
features
|
|
4412
|
+
plans: catalogPlans,
|
|
4413
|
+
features: featureOverride,
|
|
4414
|
+
sort,
|
|
4415
|
+
appearance
|
|
3452
4416
|
}) {
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
"table",
|
|
3462
|
-
|
|
3463
|
-
style: {
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
},
|
|
3481
|
-
children: "Feature"
|
|
3482
|
-
}
|
|
3483
|
-
),
|
|
3484
|
-
plans.map((plan) => /* @__PURE__ */ jsx(
|
|
3485
|
-
"th",
|
|
3486
|
-
{
|
|
3487
|
-
style: {
|
|
3488
|
-
padding: "0.75rem 1rem",
|
|
3489
|
-
textAlign: "center",
|
|
3490
|
-
borderBottom: "1px solid var(--nozle-border, var(--border))",
|
|
3491
|
-
fontWeight: 600,
|
|
3492
|
-
color: "var(--nozle-foreground, var(--foreground))"
|
|
3493
|
-
},
|
|
3494
|
-
children: plan.name
|
|
3495
|
-
},
|
|
3496
|
-
plan.id
|
|
3497
|
-
))
|
|
3498
|
-
] }) }),
|
|
3499
|
-
/* @__PURE__ */ jsx("tbody", { children: features.map((feature) => /* @__PURE__ */ jsxs("tr", { children: [
|
|
3500
|
-
/* @__PURE__ */ jsx(
|
|
3501
|
-
"td",
|
|
3502
|
-
{
|
|
3503
|
-
style: {
|
|
3504
|
-
padding: "0.75rem 1rem",
|
|
3505
|
-
borderBottom: "1px solid var(--nozle-border, var(--border))",
|
|
3506
|
-
color: "var(--nozle-foreground, var(--foreground))"
|
|
3507
|
-
},
|
|
3508
|
-
children: feature.label
|
|
3509
|
-
}
|
|
3510
|
-
),
|
|
3511
|
-
plans.map((plan) => {
|
|
3512
|
-
const value = feature.values[plan.id];
|
|
3513
|
-
return /* @__PURE__ */ jsx(
|
|
3514
|
-
"td",
|
|
3515
|
-
{
|
|
3516
|
-
style: {
|
|
3517
|
-
padding: "0.75rem 1rem",
|
|
3518
|
-
textAlign: "center",
|
|
3519
|
-
borderBottom: "1px solid var(--nozle-border, var(--border))"
|
|
3520
|
-
},
|
|
3521
|
-
children: typeof value === "boolean" ? value ? /* @__PURE__ */ jsx(
|
|
3522
|
-
"span",
|
|
3523
|
-
{
|
|
3524
|
-
style: {
|
|
3525
|
-
color: "var(--nozle-primary, var(--primary))"
|
|
3526
|
-
},
|
|
3527
|
-
"aria-label": "Included",
|
|
3528
|
-
children: "\u2713"
|
|
3529
|
-
}
|
|
3530
|
-
) : /* @__PURE__ */ jsx(
|
|
3531
|
-
"span",
|
|
3532
|
-
{
|
|
3533
|
-
style: {
|
|
3534
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))"
|
|
3535
|
-
},
|
|
3536
|
-
"aria-label": "Not included",
|
|
3537
|
-
children: "\u2715"
|
|
3538
|
-
}
|
|
3539
|
-
) : /* @__PURE__ */ jsx("span", { children: value ?? "\u2014" })
|
|
3540
|
-
},
|
|
3541
|
-
plan.id
|
|
3542
|
-
);
|
|
3543
|
-
})
|
|
3544
|
-
] }, feature.key)) })
|
|
3545
|
-
]
|
|
3546
|
-
}
|
|
3547
|
-
)
|
|
3548
|
-
}
|
|
3549
|
-
);
|
|
4417
|
+
const { root, el } = useAppearance(appearance);
|
|
4418
|
+
const plans = sort ? [...catalogPlans].sort(sort) : sortPlansByPrice(catalogPlans);
|
|
4419
|
+
const features = featureOverride ?? comparisonRows(plans);
|
|
4420
|
+
const cell = (style) => el("planComparison__cell", style);
|
|
4421
|
+
return /* @__PURE__ */ jsxs("div", { ...root("planComparison", { minWidth: 0 }), children: [
|
|
4422
|
+
/* @__PURE__ */ jsx("div", { style: table.frame, children: /* @__PURE__ */ jsxs("table", { ...el("planComparison__table", table.table), children: [
|
|
4423
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
4424
|
+
/* @__PURE__ */ jsx("th", { ...cell(table.head()), children: "Feature" }),
|
|
4425
|
+
plans.map((plan) => /* @__PURE__ */ jsxs("th", { ...cell({ ...table.head(), textAlign: "center", padding: "10px 20px" }), children: [
|
|
4426
|
+
plan.name,
|
|
4427
|
+
/* @__PURE__ */ jsxs("div", { style: { ...text.meta, letterSpacing: "normal", textTransform: "none" }, children: [
|
|
4428
|
+
formatPlanPrice(plan.amount_cents, plan.amount_currency),
|
|
4429
|
+
" / ",
|
|
4430
|
+
intervalLabel(plan.interval)
|
|
4431
|
+
] })
|
|
4432
|
+
] }, plan.code))
|
|
4433
|
+
] }) }),
|
|
4434
|
+
/* @__PURE__ */ jsx("tbody", { children: features.map((feature, index) => /* @__PURE__ */ jsxs("tr", { ...el("planComparison__row"), children: [
|
|
4435
|
+
/* @__PURE__ */ jsx("td", { ...cell(table.cell(false, index === features.length - 1)), children: feature.label }),
|
|
4436
|
+
plans.map((plan) => {
|
|
4437
|
+
const value = feature.values[plan.code];
|
|
4438
|
+
return /* @__PURE__ */ jsx("td", { ...cell({ ...table.cell(false, index === features.length - 1), textAlign: "center" }), children: typeof value === "boolean" ? value ? /* @__PURE__ */ jsx("span", { style: { color: T.primary }, "aria-label": "Included", children: "\u2713" }) : /* @__PURE__ */ jsx("span", { style: { color: T.mutedForeground }, "aria-label": "Not included", children: "\u2715" }) : /* @__PURE__ */ jsx("span", { children: value ?? "\u2014" }) }, plan.code);
|
|
4439
|
+
})
|
|
4440
|
+
] }, feature.key)) })
|
|
4441
|
+
] }) }),
|
|
4442
|
+
/* @__PURE__ */ jsx(NozleBranding, {})
|
|
4443
|
+
] });
|
|
3550
4444
|
}
|
|
3551
4445
|
function UpgradeButton({
|
|
3552
4446
|
planCode,
|
|
@@ -3562,26 +4456,18 @@ function UpgradeButton({
|
|
|
3562
4456
|
onCheckoutStarted,
|
|
3563
4457
|
onDowngradeScheduled,
|
|
3564
4458
|
onUpgraded,
|
|
3565
|
-
onError
|
|
4459
|
+
onError,
|
|
4460
|
+
appearance
|
|
3566
4461
|
}) {
|
|
3567
4462
|
const [isOpen, setIsOpen] = useState(false);
|
|
4463
|
+
const { root } = useAppearance(appearance);
|
|
3568
4464
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3569
4465
|
/* @__PURE__ */ jsx(
|
|
3570
4466
|
"button",
|
|
3571
4467
|
{
|
|
3572
4468
|
type: "button",
|
|
3573
4469
|
onClick: () => setIsOpen(true),
|
|
3574
|
-
className,
|
|
3575
|
-
style: {
|
|
3576
|
-
padding: "0.75rem 1.5rem",
|
|
3577
|
-
borderRadius: "var(--nozle-radius, 0.5rem)",
|
|
3578
|
-
border: "none",
|
|
3579
|
-
background: "var(--nozle-primary, var(--primary))",
|
|
3580
|
-
color: "var(--nozle-primary-foreground, var(--primary-foreground))",
|
|
3581
|
-
cursor: "pointer",
|
|
3582
|
-
fontWeight: 500,
|
|
3583
|
-
...style
|
|
3584
|
-
},
|
|
4470
|
+
...root("upgradeButton", { ...buttonStyle(), ...style }, className),
|
|
3585
4471
|
children: label
|
|
3586
4472
|
}
|
|
3587
4473
|
),
|
|
@@ -3589,6 +4475,7 @@ function UpgradeButton({
|
|
|
3589
4475
|
UpgradeModal,
|
|
3590
4476
|
{
|
|
3591
4477
|
isOpen,
|
|
4478
|
+
appearance,
|
|
3592
4479
|
planCode,
|
|
3593
4480
|
returnUrl,
|
|
3594
4481
|
subscriptionId,
|
|
@@ -3647,11 +4534,13 @@ function PlanGate({
|
|
|
3647
4534
|
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
3648
4535
|
}
|
|
3649
4536
|
function UpgradePrompt({
|
|
3650
|
-
text = "Upgrade to access this feature",
|
|
4537
|
+
text: text2 = "Upgrade to access this feature",
|
|
3651
4538
|
planName: planName2,
|
|
3652
|
-
onUpgrade
|
|
4539
|
+
onUpgrade,
|
|
4540
|
+
appearance
|
|
3653
4541
|
}) {
|
|
3654
|
-
const
|
|
4542
|
+
const { root } = useAppearance(appearance);
|
|
4543
|
+
const displayText = planName2 ? `Upgrade to ${planName2} to access this feature` : text2;
|
|
3655
4544
|
function handleUpgrade() {
|
|
3656
4545
|
if (onUpgrade) {
|
|
3657
4546
|
onUpgrade();
|
|
@@ -3662,40 +4551,23 @@ function UpgradePrompt({
|
|
|
3662
4551
|
return /* @__PURE__ */ jsxs(
|
|
3663
4552
|
"div",
|
|
3664
4553
|
{
|
|
3665
|
-
|
|
4554
|
+
...root("upgradePrompt", {
|
|
3666
4555
|
display: "flex",
|
|
3667
4556
|
flexDirection: "column",
|
|
3668
4557
|
alignItems: "center",
|
|
3669
4558
|
gap: "0.75rem",
|
|
3670
4559
|
padding: "1rem",
|
|
3671
|
-
textAlign: "center"
|
|
3672
|
-
|
|
4560
|
+
textAlign: "center",
|
|
4561
|
+
...text.body
|
|
4562
|
+
}),
|
|
3673
4563
|
children: [
|
|
3674
|
-
/* @__PURE__ */ jsx(
|
|
3675
|
-
"p",
|
|
3676
|
-
{
|
|
3677
|
-
style: {
|
|
3678
|
-
color: "var(--nozle-foreground, var(--foreground))",
|
|
3679
|
-
fontSize: "0.875rem",
|
|
3680
|
-
margin: 0
|
|
3681
|
-
},
|
|
3682
|
-
children: displayText
|
|
3683
|
-
}
|
|
3684
|
-
),
|
|
4564
|
+
/* @__PURE__ */ jsx("p", { style: { margin: 0 }, children: displayText }),
|
|
3685
4565
|
/* @__PURE__ */ jsx(
|
|
3686
4566
|
"button",
|
|
3687
4567
|
{
|
|
4568
|
+
type: "button",
|
|
3688
4569
|
onClick: handleUpgrade,
|
|
3689
|
-
style: {
|
|
3690
|
-
padding: "0.5rem 1.25rem",
|
|
3691
|
-
borderRadius: "var(--nozle-radius, 0.5rem)",
|
|
3692
|
-
border: "none",
|
|
3693
|
-
background: "var(--nozle-primary, var(--primary))",
|
|
3694
|
-
color: "var(--nozle-primary-foreground, var(--primary-foreground))",
|
|
3695
|
-
cursor: "pointer",
|
|
3696
|
-
fontWeight: 500,
|
|
3697
|
-
fontSize: "0.875rem"
|
|
3698
|
-
},
|
|
4570
|
+
style: { ...buttonStyle(), padding: "0.5rem 1.25rem" },
|
|
3699
4571
|
children: "Upgrade"
|
|
3700
4572
|
}
|
|
3701
4573
|
)
|
|
@@ -3708,12 +4580,14 @@ function LockedOverlay({
|
|
|
3708
4580
|
upgradeText,
|
|
3709
4581
|
upgradePlanName,
|
|
3710
4582
|
onUpgrade,
|
|
3711
|
-
children
|
|
4583
|
+
children,
|
|
4584
|
+
appearance
|
|
3712
4585
|
}) {
|
|
4586
|
+
const { root, el } = useAppearance(appearance);
|
|
3713
4587
|
if (!locked) {
|
|
3714
4588
|
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
3715
4589
|
}
|
|
3716
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
4590
|
+
return /* @__PURE__ */ jsxs("div", { ...root("lockedOverlay", { position: "relative", display: "inline-block" }), children: [
|
|
3717
4591
|
/* @__PURE__ */ jsx(
|
|
3718
4592
|
"div",
|
|
3719
4593
|
{
|
|
@@ -3729,16 +4603,15 @@ function LockedOverlay({
|
|
|
3729
4603
|
/* @__PURE__ */ jsx(
|
|
3730
4604
|
"div",
|
|
3731
4605
|
{
|
|
3732
|
-
|
|
4606
|
+
...el("lockedOverlay__panel", {
|
|
3733
4607
|
position: "absolute",
|
|
3734
4608
|
inset: 0,
|
|
3735
4609
|
display: "flex",
|
|
3736
4610
|
alignItems: "center",
|
|
3737
4611
|
justifyContent: "center",
|
|
3738
|
-
background:
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
},
|
|
4612
|
+
background: `color-mix(in srgb, ${T.background} 92%, transparent)`,
|
|
4613
|
+
borderRadius: T.radius
|
|
4614
|
+
}),
|
|
3742
4615
|
role: "dialog",
|
|
3743
4616
|
"aria-label": "Feature locked \u2014 upgrade required",
|
|
3744
4617
|
children: /* @__PURE__ */ jsx(
|
|
@@ -3746,7 +4619,8 @@ function LockedOverlay({
|
|
|
3746
4619
|
{
|
|
3747
4620
|
text: upgradeText,
|
|
3748
4621
|
planName: upgradePlanName,
|
|
3749
|
-
onUpgrade
|
|
4622
|
+
onUpgrade,
|
|
4623
|
+
appearance
|
|
3750
4624
|
}
|
|
3751
4625
|
)
|
|
3752
4626
|
}
|
|
@@ -3754,68 +4628,109 @@ function LockedOverlay({
|
|
|
3754
4628
|
] });
|
|
3755
4629
|
}
|
|
3756
4630
|
function getUsageMeterColor(percentage) {
|
|
3757
|
-
if (percentage >= 95) return
|
|
3758
|
-
if (percentage >= 80) return
|
|
3759
|
-
return
|
|
4631
|
+
if (percentage >= 95) return T.danger;
|
|
4632
|
+
if (percentage >= 80) return T.warning;
|
|
4633
|
+
return T.primary;
|
|
3760
4634
|
}
|
|
3761
4635
|
function UsageMeter({
|
|
3762
4636
|
used,
|
|
3763
4637
|
limit,
|
|
3764
4638
|
label,
|
|
3765
|
-
showText = true
|
|
4639
|
+
showText = true,
|
|
4640
|
+
appearance
|
|
3766
4641
|
}) {
|
|
3767
|
-
const
|
|
4642
|
+
const { root, el } = useAppearance(appearance);
|
|
4643
|
+
const unlimited = limit == null;
|
|
4644
|
+
const percentage = !unlimited && limit > 0 ? Math.min(100, used / limit * 100) : 0;
|
|
3768
4645
|
const color = getUsageMeterColor(percentage);
|
|
3769
|
-
return /* @__PURE__ */ jsxs(
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
4646
|
+
return /* @__PURE__ */ jsxs(
|
|
4647
|
+
"div",
|
|
4648
|
+
{
|
|
4649
|
+
...root("usageMeter", {
|
|
4650
|
+
width: "100%",
|
|
4651
|
+
display: "flex",
|
|
4652
|
+
flexDirection: "column",
|
|
4653
|
+
gap: "0.25rem",
|
|
4654
|
+
...text.body
|
|
4655
|
+
}),
|
|
4656
|
+
children: [
|
|
4657
|
+
label && /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
|
|
4658
|
+
/* @__PURE__ */ jsx("span", { ...el("usageMeter__label"), children: label }),
|
|
4659
|
+
showText && /* @__PURE__ */ jsxs("span", { ...el("usageMeter__value", { color: percentage >= 95 ? T.dangerText : percentage >= 80 ? T.warningText : T.foreground }), children: [
|
|
4660
|
+
used.toLocaleString(),
|
|
4661
|
+
" / ",
|
|
4662
|
+
unlimited ? "Unlimited" : limit.toLocaleString()
|
|
4663
|
+
] })
|
|
4664
|
+
] }),
|
|
4665
|
+
/* @__PURE__ */ jsx(
|
|
3784
4666
|
"div",
|
|
3785
4667
|
{
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
}
|
|
4668
|
+
...el("usageMeter__track", {
|
|
4669
|
+
height: "0.5rem",
|
|
4670
|
+
borderRadius: "9999px",
|
|
4671
|
+
overflow: "hidden",
|
|
4672
|
+
background: T.muted
|
|
4673
|
+
}),
|
|
4674
|
+
children: /* @__PURE__ */ jsx(
|
|
4675
|
+
"div",
|
|
4676
|
+
{
|
|
4677
|
+
...el("usageMeter__fill", {
|
|
4678
|
+
height: "100%",
|
|
4679
|
+
borderRadius: "9999px",
|
|
4680
|
+
width: `${percentage}%`,
|
|
4681
|
+
background: color,
|
|
4682
|
+
transition: "width 0.3s ease"
|
|
4683
|
+
})
|
|
4684
|
+
}
|
|
4685
|
+
)
|
|
3792
4686
|
}
|
|
3793
4687
|
)
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
4688
|
+
]
|
|
4689
|
+
}
|
|
4690
|
+
);
|
|
4691
|
+
}
|
|
4692
|
+
function usageItems(entitlements, results) {
|
|
4693
|
+
return entitlements.flatMap((entitlement) => {
|
|
4694
|
+
const result = results[entitlement.code];
|
|
4695
|
+
if (entitlement.feature_type !== "metered" || !result) return [];
|
|
4696
|
+
return [{ key: entitlement.code, label: entitlement.name, used: result.used, limit: result.limit ?? null }];
|
|
4697
|
+
});
|
|
3797
4698
|
}
|
|
3798
4699
|
function UsageDashboard({
|
|
3799
4700
|
features = [],
|
|
3800
|
-
loading = false
|
|
4701
|
+
loading = false,
|
|
4702
|
+
error = null,
|
|
4703
|
+
emptyMessage = "No usage to show yet.",
|
|
4704
|
+
appearance
|
|
3801
4705
|
}) {
|
|
4706
|
+
const { root } = useAppearance(appearance);
|
|
4707
|
+
const props = root("usageDashboard", {
|
|
4708
|
+
...surface,
|
|
4709
|
+
margin: 0,
|
|
4710
|
+
display: "flex",
|
|
4711
|
+
flexDirection: "column",
|
|
4712
|
+
gap: loading ? "0.75rem" : "1rem",
|
|
4713
|
+
// Errors and the empty message take the same colours as in the other components.
|
|
4714
|
+
...error ? { color: T.dangerText } : features.length === 0 && !loading && text.secondary
|
|
4715
|
+
});
|
|
3802
4716
|
if (loading) {
|
|
3803
|
-
return /* @__PURE__ */ jsx("div", {
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
style: { background: "var(--nozle-border, var(--border))" }
|
|
3808
|
-
},
|
|
3809
|
-
i
|
|
3810
|
-
)) });
|
|
4717
|
+
return /* @__PURE__ */ jsx("div", { ...props, "aria-busy": "true", children: [1, 2, 3].map((i) => /* @__PURE__ */ jsx("div", { style: { height: "2rem", borderRadius: `calc(${T.radius} / 3)`, background: T.skeleton } }, i)) });
|
|
4718
|
+
}
|
|
4719
|
+
if (error) {
|
|
4720
|
+
return /* @__PURE__ */ jsx("p", { ...props, role: "alert", children: typeof error === "string" ? error : error.message });
|
|
3811
4721
|
}
|
|
3812
|
-
|
|
4722
|
+
if (features.length === 0) {
|
|
4723
|
+
return /* @__PURE__ */ jsx("p", { ...props, role: "status", children: emptyMessage });
|
|
4724
|
+
}
|
|
4725
|
+
return /* @__PURE__ */ jsx("div", { ...props, children: features.map((f) => /* @__PURE__ */ jsx(UsageMeter, { used: f.used, limit: f.limit, label: f.label, appearance }, f.key)) });
|
|
3813
4726
|
}
|
|
3814
4727
|
function UsageAlert({
|
|
3815
4728
|
features,
|
|
3816
4729
|
threshold = 80,
|
|
3817
|
-
upgradeHref = "/plans"
|
|
4730
|
+
upgradeHref = "/plans",
|
|
4731
|
+
appearance
|
|
3818
4732
|
}) {
|
|
4733
|
+
const { root, el } = useAppearance(appearance);
|
|
3819
4734
|
const [dismissed, setDismissed] = useState(/* @__PURE__ */ new Set());
|
|
3820
4735
|
const alertFeatures = features.filter(
|
|
3821
4736
|
(f) => f.percentage >= threshold && !dismissed.has(f.key)
|
|
@@ -3826,15 +4741,19 @@ function UsageAlert({
|
|
|
3826
4741
|
const handleDismiss = (key) => {
|
|
3827
4742
|
setDismissed((prev) => new Set(prev).add(key));
|
|
3828
4743
|
};
|
|
3829
|
-
return /* @__PURE__ */ jsx("div", {
|
|
4744
|
+
return /* @__PURE__ */ jsx("div", { ...root("usageAlert", { display: "flex", flexDirection: "column", gap: "0.5rem", ...text.body }), children: alertFeatures.map((f) => /* @__PURE__ */ jsxs(
|
|
3830
4745
|
"div",
|
|
3831
4746
|
{
|
|
3832
4747
|
role: "alert",
|
|
3833
|
-
className: "flex items-center justify-between rounded-md px-4 py-3 text-sm",
|
|
3834
4748
|
style: {
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
4749
|
+
display: "flex",
|
|
4750
|
+
alignItems: "center",
|
|
4751
|
+
justifyContent: "space-between",
|
|
4752
|
+
borderRadius: `calc(${T.radius} / 2)`,
|
|
4753
|
+
padding: "0.75rem 1rem",
|
|
4754
|
+
background: T.warningSoft,
|
|
4755
|
+
color: T.warningText,
|
|
4756
|
+
border: border(T.warningBorder)
|
|
3838
4757
|
},
|
|
3839
4758
|
children: [
|
|
3840
4759
|
/* @__PURE__ */ jsxs("span", { children: [
|
|
@@ -3857,8 +4776,8 @@ function UsageAlert({
|
|
|
3857
4776
|
{
|
|
3858
4777
|
href: upgradeHref,
|
|
3859
4778
|
style: {
|
|
4779
|
+
...text.action,
|
|
3860
4780
|
color: "inherit",
|
|
3861
|
-
fontWeight: 600,
|
|
3862
4781
|
textDecoration: "underline"
|
|
3863
4782
|
},
|
|
3864
4783
|
children: "Upgrade"
|
|
@@ -3868,16 +4787,17 @@ function UsageAlert({
|
|
|
3868
4787
|
/* @__PURE__ */ jsx(
|
|
3869
4788
|
"button",
|
|
3870
4789
|
{
|
|
4790
|
+
type: "button",
|
|
3871
4791
|
onClick: () => handleDismiss(f.key),
|
|
3872
4792
|
"aria-label": `Dismiss ${f.label} alert`,
|
|
3873
|
-
|
|
4793
|
+
...el("usageAlert__dismiss", {
|
|
3874
4794
|
background: "none",
|
|
3875
4795
|
border: "none",
|
|
3876
4796
|
cursor: "pointer",
|
|
3877
4797
|
padding: "0 0 0 0.75rem",
|
|
3878
|
-
|
|
4798
|
+
...text.action,
|
|
3879
4799
|
color: "inherit"
|
|
3880
|
-
},
|
|
4800
|
+
}),
|
|
3881
4801
|
children: "\xD7"
|
|
3882
4802
|
}
|
|
3883
4803
|
)
|
|
@@ -3886,44 +4806,247 @@ function UsageAlert({
|
|
|
3886
4806
|
f.key
|
|
3887
4807
|
)) });
|
|
3888
4808
|
}
|
|
3889
|
-
var
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
4809
|
+
var STATUS_LABELS = {
|
|
4810
|
+
active: "Active",
|
|
4811
|
+
pending: "Pending",
|
|
4812
|
+
incomplete: "Awaiting payment",
|
|
4813
|
+
canceled: "Canceled",
|
|
4814
|
+
terminated: "Ended"
|
|
4815
|
+
};
|
|
4816
|
+
function statusColors(status, ending) {
|
|
4817
|
+
if (status === "active" && !ending) return { background: T.successSoft, color: T.successText };
|
|
4818
|
+
if (status === "active" || status === "pending" || status === "incomplete") {
|
|
4819
|
+
return { background: T.warningSoft, color: T.warningText };
|
|
4820
|
+
}
|
|
4821
|
+
return { background: T.muted, color: T.mutedForeground };
|
|
4822
|
+
}
|
|
4823
|
+
function day(value, locale) {
|
|
4824
|
+
const parsed = new Date(value);
|
|
4825
|
+
return Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleDateString(locale, { dateStyle: "medium" });
|
|
4826
|
+
}
|
|
4827
|
+
function currentPlanMeters(entitlements, usage) {
|
|
4828
|
+
const units = /* @__PURE__ */ new Map();
|
|
4829
|
+
for (const charge of usage?.charges_usage ?? []) {
|
|
4830
|
+
units.set(charge.feature.code, (units.get(charge.feature.code) ?? 0) + (Number(charge.units) || 0));
|
|
4831
|
+
}
|
|
4832
|
+
return (entitlements ?? []).filter((entitlement) => entitlement.feature_type === "metered").map((entitlement) => ({
|
|
4833
|
+
key: entitlement.code,
|
|
4834
|
+
label: entitlement.name,
|
|
4835
|
+
used: units.get(entitlement.code) ?? 0,
|
|
4836
|
+
limit: entitlement.usage_limit ?? null
|
|
4837
|
+
}));
|
|
4838
|
+
}
|
|
4839
|
+
function CurrentPlan(props) {
|
|
4840
|
+
return props.subscription === void 0 ? /* @__PURE__ */ jsx(CurrentPlanFromBackend, { ...props }) : /* @__PURE__ */ jsx(CurrentPlanView, { ...props });
|
|
4841
|
+
}
|
|
4842
|
+
function CurrentPlanFromBackend(props) {
|
|
4843
|
+
const subscription = useSubscription();
|
|
4844
|
+
const plans = usePlans();
|
|
4845
|
+
const usage = useUsage();
|
|
4846
|
+
const planCode = subscription.data?.subscription?.plan_code;
|
|
4847
|
+
const meters = props.usage ?? currentPlanMeters(plans.plans.find((plan) => plan.code === planCode)?.entitlements, usage.data);
|
|
4848
|
+
return /* @__PURE__ */ jsx(
|
|
4849
|
+
CurrentPlanView,
|
|
4850
|
+
{
|
|
4851
|
+
...props,
|
|
4852
|
+
subscription: subscription.data ?? null,
|
|
4853
|
+
usage: meters,
|
|
4854
|
+
isLoading: props.isLoading || subscription.isPending,
|
|
4855
|
+
error: props.error ?? subscription.error
|
|
4856
|
+
}
|
|
4857
|
+
);
|
|
4858
|
+
}
|
|
4859
|
+
function CurrentPlanView({
|
|
4860
|
+
subscription,
|
|
4861
|
+
usage = [],
|
|
4862
|
+
details = [],
|
|
4863
|
+
actions,
|
|
4864
|
+
warningPercent = 80,
|
|
4865
|
+
title = "Current plan",
|
|
4866
|
+
isLoading = false,
|
|
4867
|
+
error = null,
|
|
4868
|
+
emptyMessage = "No active plan.",
|
|
4869
|
+
pastDueMessage = "A payment is overdue. Pay the open invoice to keep your plan active.",
|
|
4870
|
+
locale = "en-US",
|
|
4871
|
+
appearance
|
|
4872
|
+
}) {
|
|
4873
|
+
const { root, el } = useAppearance(appearance);
|
|
4874
|
+
const frame = { ...surface, padding: 0, overflow: "hidden" };
|
|
4875
|
+
const header = /* @__PURE__ */ jsxs(
|
|
4876
|
+
"div",
|
|
4877
|
+
{
|
|
4878
|
+
style: {
|
|
4879
|
+
display: "flex",
|
|
4880
|
+
alignItems: "center",
|
|
4881
|
+
justifyContent: "space-between",
|
|
4882
|
+
gap: 12,
|
|
4883
|
+
padding: "0.875rem 1.25rem",
|
|
4884
|
+
borderBottom: border()
|
|
4885
|
+
},
|
|
4886
|
+
children: [
|
|
4887
|
+
/* @__PURE__ */ jsx("span", { style: text.heading, children: title }),
|
|
4888
|
+
actions
|
|
4889
|
+
]
|
|
4890
|
+
}
|
|
4891
|
+
);
|
|
4892
|
+
const body = (children) => /* @__PURE__ */ jsx("div", { style: { padding: "1.25rem", display: "grid", gap: "1rem" }, children });
|
|
4893
|
+
if (isLoading) {
|
|
4894
|
+
return /* @__PURE__ */ jsxs("section", { ...root("currentPlan", frame), "aria-busy": "true", children: [
|
|
4895
|
+
header,
|
|
4896
|
+
body(/* @__PURE__ */ jsx("div", { style: { height: 72, borderRadius: `calc(${T.radius} / 2)`, background: T.skeleton } }))
|
|
4897
|
+
] });
|
|
4898
|
+
}
|
|
4899
|
+
if (error) {
|
|
4900
|
+
return /* @__PURE__ */ jsxs("section", { ...root("currentPlan", frame), children: [
|
|
4901
|
+
header,
|
|
4902
|
+
body(/* @__PURE__ */ jsx("p", { role: "alert", style: { margin: 0, color: T.dangerText }, children: typeof error === "string" ? error : error.message }))
|
|
4903
|
+
] });
|
|
3909
4904
|
}
|
|
4905
|
+
const current = subscription?.subscription;
|
|
4906
|
+
if (!current) {
|
|
4907
|
+
return /* @__PURE__ */ jsxs("section", { ...root("currentPlan", frame), children: [
|
|
4908
|
+
header,
|
|
4909
|
+
body(/* @__PURE__ */ jsx("p", { role: "status", style: { ...text.secondary, margin: 0 }, children: emptyMessage }))
|
|
4910
|
+
] });
|
|
4911
|
+
}
|
|
4912
|
+
const pending = subscription?.pending_change;
|
|
4913
|
+
const ending = Boolean(current.ending_at);
|
|
4914
|
+
const near = warningPercent === null ? [] : usage.filter((meter) => meter.limit != null && meter.limit > 0 && meter.used / meter.limit * 100 >= warningPercent);
|
|
4915
|
+
const statusLabel = ending && current.ending_at ? `Ends ${day(current.ending_at, locale)}` : STATUS_LABELS[current.status] ?? current.status;
|
|
4916
|
+
return /* @__PURE__ */ jsxs("section", { ...root("currentPlan", frame), "aria-label": title, children: [
|
|
4917
|
+
header,
|
|
4918
|
+
body(
|
|
4919
|
+
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4920
|
+
subscription?.past_due && /* @__PURE__ */ jsx(
|
|
4921
|
+
"p",
|
|
4922
|
+
{
|
|
4923
|
+
role: "alert",
|
|
4924
|
+
...el("currentPlan__notice", {
|
|
4925
|
+
margin: 0,
|
|
4926
|
+
padding: "0.625rem 0.875rem",
|
|
4927
|
+
...text.body,
|
|
4928
|
+
borderRadius: `calc(${T.radius} / 2)`,
|
|
4929
|
+
background: T.dangerSoft,
|
|
4930
|
+
color: T.dangerText,
|
|
4931
|
+
border: border(T.dangerBorder)
|
|
4932
|
+
}),
|
|
4933
|
+
children: pastDueMessage
|
|
4934
|
+
}
|
|
4935
|
+
),
|
|
4936
|
+
near.map((meter) => /* @__PURE__ */ jsxs(
|
|
4937
|
+
"p",
|
|
4938
|
+
{
|
|
4939
|
+
role: "status",
|
|
4940
|
+
...el("currentPlan__notice", {
|
|
4941
|
+
margin: 0,
|
|
4942
|
+
padding: "0.625rem 0.875rem",
|
|
4943
|
+
...text.body,
|
|
4944
|
+
borderRadius: `calc(${T.radius} / 2)`,
|
|
4945
|
+
background: T.warningSoft,
|
|
4946
|
+
color: T.warningText,
|
|
4947
|
+
border: border(T.warningBorder)
|
|
4948
|
+
}),
|
|
4949
|
+
children: [
|
|
4950
|
+
"You have used ",
|
|
4951
|
+
Math.round(meter.used / meter.limit * 100),
|
|
4952
|
+
"% of the ",
|
|
4953
|
+
meter.label,
|
|
4954
|
+
" included in",
|
|
4955
|
+
" ",
|
|
4956
|
+
current.plan.name,
|
|
4957
|
+
"."
|
|
4958
|
+
]
|
|
4959
|
+
},
|
|
4960
|
+
meter.key
|
|
4961
|
+
)),
|
|
4962
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", flexWrap: "wrap", alignItems: "center", gap: "0.625rem" }, children: [
|
|
4963
|
+
/* @__PURE__ */ jsx("span", { ...el("currentPlan__name", text.heading), children: current.plan.name }),
|
|
4964
|
+
/* @__PURE__ */ jsx(
|
|
4965
|
+
"span",
|
|
4966
|
+
{
|
|
4967
|
+
...el("currentPlan__status", {
|
|
4968
|
+
...text.label,
|
|
4969
|
+
...statusColors(current.status, ending),
|
|
4970
|
+
padding: "0.125rem 0.625rem",
|
|
4971
|
+
borderRadius: 9999
|
|
4972
|
+
}),
|
|
4973
|
+
children: statusLabel
|
|
4974
|
+
}
|
|
4975
|
+
),
|
|
4976
|
+
pending && /* @__PURE__ */ jsxs(
|
|
4977
|
+
"span",
|
|
4978
|
+
{
|
|
4979
|
+
...el("currentPlan__scheduled", {
|
|
4980
|
+
...text.label,
|
|
4981
|
+
padding: "0.125rem 0.625rem",
|
|
4982
|
+
borderRadius: 9999,
|
|
4983
|
+
border: border()
|
|
4984
|
+
}),
|
|
4985
|
+
children: [
|
|
4986
|
+
"Changes to ",
|
|
4987
|
+
pending.name,
|
|
4988
|
+
" on ",
|
|
4989
|
+
day(pending.effective_at, locale)
|
|
4990
|
+
]
|
|
4991
|
+
}
|
|
4992
|
+
),
|
|
4993
|
+
/* @__PURE__ */ jsxs("span", { ...el("currentPlan__price", { ...text.secondary, marginLeft: "auto" }), children: [
|
|
4994
|
+
formatPlanPrice(Number(current.plan.amount_cents), current.plan.currency),
|
|
4995
|
+
" / ",
|
|
4996
|
+
intervalLabel(current.plan.interval)
|
|
4997
|
+
] })
|
|
4998
|
+
] }),
|
|
4999
|
+
usage.length > 0 && /* @__PURE__ */ jsx("div", { ...el("currentPlan__usage", { display: "grid", gap: "0.875rem" }), children: usage.map((meter) => /* @__PURE__ */ jsx(
|
|
5000
|
+
UsageMeter,
|
|
5001
|
+
{
|
|
5002
|
+
label: `${meter.label} used this period`,
|
|
5003
|
+
used: meter.used,
|
|
5004
|
+
limit: meter.limit,
|
|
5005
|
+
appearance
|
|
5006
|
+
},
|
|
5007
|
+
meter.key
|
|
5008
|
+
)) }),
|
|
5009
|
+
details.length > 0 && /* @__PURE__ */ jsx(
|
|
5010
|
+
"dl",
|
|
5011
|
+
{
|
|
5012
|
+
...el("currentPlan__details", {
|
|
5013
|
+
margin: 0,
|
|
5014
|
+
display: "grid",
|
|
5015
|
+
gridTemplateColumns: "repeat(auto-fit, minmax(160px, 1fr))",
|
|
5016
|
+
gap: "0.75rem"
|
|
5017
|
+
}),
|
|
5018
|
+
children: details.map((detail) => /* @__PURE__ */ jsxs("div", { children: [
|
|
5019
|
+
/* @__PURE__ */ jsx("dt", { style: text.meta, children: detail.label }),
|
|
5020
|
+
/* @__PURE__ */ jsx("dd", { style: { margin: "0.125rem 0 0", fontWeight: 600 }, children: detail.value })
|
|
5021
|
+
] }, detail.label))
|
|
5022
|
+
}
|
|
5023
|
+
)
|
|
5024
|
+
] })
|
|
5025
|
+
),
|
|
5026
|
+
/* @__PURE__ */ jsx(NozleBranding, {})
|
|
5027
|
+
] });
|
|
5028
|
+
}
|
|
5029
|
+
var TIER_STYLES = {
|
|
5030
|
+
free: { background: T.muted, color: T.mutedForeground, border: border() },
|
|
5031
|
+
starter: { background: T.primarySoft, color: T.primary, border: border(`color-mix(in srgb, ${T.primary} 30%, transparent)`) },
|
|
5032
|
+
pro: { background: T.primary, color: T.primaryForeground, border: border("transparent") },
|
|
5033
|
+
enterprise: { background: T.accent, color: T.accentForeground, border: border("transparent") }
|
|
3910
5034
|
};
|
|
3911
|
-
function PlanBadge({ plan, tier = "free" }) {
|
|
5035
|
+
function PlanBadge({ plan, tier = "free", appearance }) {
|
|
5036
|
+
const { root } = useAppearance(appearance);
|
|
3912
5037
|
const style = TIER_STYLES[tier] ?? TIER_STYLES.free;
|
|
3913
5038
|
return /* @__PURE__ */ jsx(
|
|
3914
5039
|
"span",
|
|
3915
5040
|
{
|
|
3916
|
-
|
|
5041
|
+
...root("planBadge", {
|
|
5042
|
+
...text.label,
|
|
3917
5043
|
...style,
|
|
3918
5044
|
display: "inline-flex",
|
|
3919
5045
|
alignItems: "center",
|
|
3920
5046
|
padding: "0.125rem 0.5rem",
|
|
3921
5047
|
borderRadius: "9999px",
|
|
3922
|
-
fontSize: "0.75rem",
|
|
3923
|
-
fontWeight: 600,
|
|
3924
|
-
lineHeight: "1.25rem",
|
|
3925
5048
|
whiteSpace: "nowrap"
|
|
3926
|
-
},
|
|
5049
|
+
}),
|
|
3927
5050
|
children: plan
|
|
3928
5051
|
}
|
|
3929
5052
|
);
|
|
@@ -3940,8 +5063,10 @@ var BRAND_LABELS = {
|
|
|
3940
5063
|
function PaymentMethodDisplay({
|
|
3941
5064
|
paymentMethod,
|
|
3942
5065
|
onUpdatePaymentMethod,
|
|
3943
|
-
updateHref = "/billing/update-payment"
|
|
5066
|
+
updateHref = "/billing/update-payment",
|
|
5067
|
+
appearance
|
|
3944
5068
|
}) {
|
|
5069
|
+
const { root } = useAppearance(appearance);
|
|
3945
5070
|
const handleUpdate = () => {
|
|
3946
5071
|
if (onUpdatePaymentMethod) {
|
|
3947
5072
|
onUpdatePaymentMethod();
|
|
@@ -3949,7 +5074,7 @@ function PaymentMethodDisplay({
|
|
|
3949
5074
|
window.location.href = updateHref;
|
|
3950
5075
|
}
|
|
3951
5076
|
};
|
|
3952
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
5077
|
+
return /* @__PURE__ */ jsxs("div", { ...root("paymentMethod", { ...surface, display: "flex", flexDirection: "column", gap: "0.75rem" }), children: [
|
|
3953
5078
|
paymentMethod ? /* @__PURE__ */ jsxs(
|
|
3954
5079
|
"div",
|
|
3955
5080
|
{
|
|
@@ -3958,80 +5083,245 @@ function PaymentMethodDisplay({
|
|
|
3958
5083
|
alignItems: "center",
|
|
3959
5084
|
gap: "0.75rem",
|
|
3960
5085
|
padding: "0.75rem 1rem",
|
|
3961
|
-
border:
|
|
3962
|
-
borderRadius:
|
|
3963
|
-
fontSize: "0.875rem"
|
|
5086
|
+
border: border(),
|
|
5087
|
+
borderRadius: `calc(${T.radius} * 0.6)`
|
|
3964
5088
|
},
|
|
3965
5089
|
children: [
|
|
3966
|
-
/* @__PURE__ */ jsx(
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
"span",
|
|
3978
|
-
{
|
|
3979
|
-
style: {
|
|
3980
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))"
|
|
3981
|
-
},
|
|
3982
|
-
children: [
|
|
3983
|
-
"\u2022\u2022\u2022\u2022 ",
|
|
3984
|
-
paymentMethod.last4
|
|
3985
|
-
]
|
|
3986
|
-
}
|
|
3987
|
-
),
|
|
3988
|
-
/* @__PURE__ */ jsxs(
|
|
3989
|
-
"span",
|
|
3990
|
-
{
|
|
3991
|
-
style: {
|
|
3992
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))",
|
|
3993
|
-
fontSize: "0.75rem"
|
|
3994
|
-
},
|
|
3995
|
-
children: [
|
|
3996
|
-
"Expires ",
|
|
3997
|
-
String(paymentMethod.expMonth).padStart(2, "0"),
|
|
3998
|
-
"/",
|
|
3999
|
-
String(paymentMethod.expYear).slice(-2)
|
|
4000
|
-
]
|
|
4001
|
-
}
|
|
4002
|
-
)
|
|
5090
|
+
/* @__PURE__ */ jsx("span", { style: { fontWeight: 600, color: T.foreground }, children: BRAND_LABELS[paymentMethod.brand.toLowerCase()] ?? paymentMethod.brand }),
|
|
5091
|
+
/* @__PURE__ */ jsxs("span", { style: text.secondary, children: [
|
|
5092
|
+
"\u2022\u2022\u2022\u2022 ",
|
|
5093
|
+
paymentMethod.last4
|
|
5094
|
+
] }),
|
|
5095
|
+
/* @__PURE__ */ jsxs("span", { style: text.meta, children: [
|
|
5096
|
+
"Expires ",
|
|
5097
|
+
String(paymentMethod.expMonth).padStart(2, "0"),
|
|
5098
|
+
"/",
|
|
5099
|
+
String(paymentMethod.expYear).slice(-2)
|
|
5100
|
+
] })
|
|
4003
5101
|
]
|
|
4004
5102
|
}
|
|
4005
|
-
) : /* @__PURE__ */ jsx(
|
|
4006
|
-
"p",
|
|
4007
|
-
{
|
|
4008
|
-
style: {
|
|
4009
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))",
|
|
4010
|
-
fontSize: "0.875rem"
|
|
4011
|
-
},
|
|
4012
|
-
children: "No payment method on file."
|
|
4013
|
-
}
|
|
4014
|
-
),
|
|
5103
|
+
) : /* @__PURE__ */ jsx("p", { style: { ...text.secondary, margin: 0 }, children: "No payment method on file." }),
|
|
4015
5104
|
/* @__PURE__ */ jsx(
|
|
4016
5105
|
"button",
|
|
4017
5106
|
{
|
|
5107
|
+
type: "button",
|
|
4018
5108
|
onClick: handleUpdate,
|
|
4019
5109
|
style: {
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
color: "var(--nozle-primary, var(--primary))",
|
|
5110
|
+
...buttonStyle("outline"),
|
|
5111
|
+
alignSelf: "flex-start",
|
|
4023
5112
|
padding: "0.375rem 0.875rem",
|
|
4024
|
-
borderRadius:
|
|
4025
|
-
fontSize: "0.875rem",
|
|
4026
|
-
fontWeight: 500,
|
|
4027
|
-
cursor: "pointer"
|
|
5113
|
+
borderRadius: buttonRadius(`calc(${T.radius} / 2)`)
|
|
4028
5114
|
},
|
|
4029
5115
|
children: "Update Payment Method"
|
|
4030
5116
|
}
|
|
4031
5117
|
)
|
|
4032
5118
|
] });
|
|
4033
5119
|
}
|
|
5120
|
+
var SOURCE_LABELS = {
|
|
5121
|
+
subscription_grant: "Plan grant",
|
|
5122
|
+
top_up: "Top-up",
|
|
5123
|
+
manual_grant: "Granted",
|
|
5124
|
+
adjustment: "Adjustment",
|
|
5125
|
+
allocated_top_up: "Allocated top-up"
|
|
5126
|
+
};
|
|
5127
|
+
var muted = T.mutedForeground;
|
|
5128
|
+
function date2(value) {
|
|
5129
|
+
const parsed = new Date(value);
|
|
5130
|
+
return Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleDateString("en-US", { dateStyle: "medium" });
|
|
5131
|
+
}
|
|
5132
|
+
function CreditBalance({
|
|
5133
|
+
balance,
|
|
5134
|
+
isLoading = false,
|
|
5135
|
+
error = null,
|
|
5136
|
+
emptyMessage = "No credit balance yet.",
|
|
5137
|
+
appearance
|
|
5138
|
+
}) {
|
|
5139
|
+
const { root, el } = useAppearance(appearance);
|
|
5140
|
+
if (isLoading) return /* @__PURE__ */ jsx("div", { ...root("creditBalance", { ...surface, height: 96, background: T.skeleton }), role: "status", "aria-label": "Loading credit balance" });
|
|
5141
|
+
if (error) return /* @__PURE__ */ jsx("p", { ...root("creditBalance", { ...surface, color: T.dangerText }), role: "alert", children: typeof error === "string" ? error : error.message });
|
|
5142
|
+
if (!balance) return /* @__PURE__ */ jsx("p", { ...root("creditBalance", { ...surface, color: muted }), role: "status", children: emptyMessage });
|
|
5143
|
+
const sources = (balance.sources ?? []).filter((source) => source.remaining !== "0");
|
|
5144
|
+
return /* @__PURE__ */ jsxs("section", { "aria-label": balance.credit_system_name, ...root("creditBalance", surface), children: [
|
|
5145
|
+
/* @__PURE__ */ jsx("h3", { style: { ...text.heading, margin: 0 }, children: balance.credit_system_name }),
|
|
5146
|
+
/* @__PURE__ */ jsxs("p", { style: { margin: "0.25rem 0 0" }, children: [
|
|
5147
|
+
/* @__PURE__ */ jsx("span", { ...el("creditBalance__amount", text.metric), children: formatDecimalString(balance.available) }),
|
|
5148
|
+
" ",
|
|
5149
|
+
/* @__PURE__ */ jsxs("span", { style: text.secondary, children: [
|
|
5150
|
+
creditUnitLabel(balance.available, balance.unit_name),
|
|
5151
|
+
" available"
|
|
5152
|
+
] })
|
|
5153
|
+
] }),
|
|
5154
|
+
/* @__PURE__ */ jsxs("p", { style: { ...text.meta, margin: "0.25rem 0 0" }, children: [
|
|
5155
|
+
"As of ",
|
|
5156
|
+
date2(balance.as_of)
|
|
5157
|
+
] }),
|
|
5158
|
+
sources.length > 0 && /* @__PURE__ */ jsx("ul", { style: { listStyle: "none", padding: 0, margin: "1rem 0 0" }, children: sources.map((source) => /* @__PURE__ */ jsxs("li", { style: { display: "flex", justifyContent: "space-between", padding: "0.25rem 0" }, children: [
|
|
5159
|
+
/* @__PURE__ */ jsx("span", { children: SOURCE_LABELS[source.type] ?? source.type }),
|
|
5160
|
+
/* @__PURE__ */ jsxs("span", { style: text.secondary, children: [
|
|
5161
|
+
formatDecimalString(source.remaining),
|
|
5162
|
+
source.expires_at ? ` \xB7 expires ${date2(source.expires_at)}` : ""
|
|
5163
|
+
] })
|
|
5164
|
+
] }, source.id)) })
|
|
5165
|
+
] });
|
|
5166
|
+
}
|
|
5167
|
+
var TYPE_LABELS = {
|
|
5168
|
+
consume: "Used",
|
|
5169
|
+
grant: "Granted",
|
|
5170
|
+
adjustment: "Adjustment",
|
|
5171
|
+
expire: "Expired",
|
|
5172
|
+
revoke: "Revoked",
|
|
5173
|
+
refund: "Refunded",
|
|
5174
|
+
transfer: "Transfer"
|
|
5175
|
+
};
|
|
5176
|
+
var muted2 = T.mutedForeground;
|
|
5177
|
+
function when(value) {
|
|
5178
|
+
const parsed = new Date(value);
|
|
5179
|
+
return Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short" });
|
|
5180
|
+
}
|
|
5181
|
+
function CreditHistory({
|
|
5182
|
+
operations = [],
|
|
5183
|
+
isLoading = false,
|
|
5184
|
+
error = null,
|
|
5185
|
+
emptyMessage = "No credit activity yet.",
|
|
5186
|
+
hasMore = false,
|
|
5187
|
+
onLoadMore,
|
|
5188
|
+
appearance
|
|
5189
|
+
}) {
|
|
5190
|
+
const { root, el } = useAppearance(appearance);
|
|
5191
|
+
if (error) return /* @__PURE__ */ jsx("p", { ...root("creditHistory", { ...surface, color: T.dangerText }), role: "alert", children: typeof error === "string" ? error : error.message });
|
|
5192
|
+
if (isLoading && operations.length === 0) return /* @__PURE__ */ jsx("div", { ...root("creditHistory", { ...surface, height: 120, background: T.skeleton }), role: "status", "aria-label": "Loading credit history" });
|
|
5193
|
+
if (operations.length === 0) return /* @__PURE__ */ jsx("p", { ...root("creditHistory", { ...surface, color: muted2 }), role: "status", children: emptyMessage });
|
|
5194
|
+
return /* @__PURE__ */ jsxs("div", { ...root("creditHistory", { ...text.body, minWidth: 0 }), children: [
|
|
5195
|
+
/* @__PURE__ */ jsx("div", { style: table.frame, children: /* @__PURE__ */ jsxs("table", { ...el("creditHistory__table", table.table), children: [
|
|
5196
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: ["When", "Type", "Feature", "Credits", "Status"].map((heading) => /* @__PURE__ */ jsx("th", { style: table.head(heading === "Credits"), children: heading }, heading)) }) }),
|
|
5197
|
+
/* @__PURE__ */ jsx("tbody", { children: operations.map((operation, index) => {
|
|
5198
|
+
const last = index === operations.length - 1;
|
|
5199
|
+
return /* @__PURE__ */ jsxs("tr", { ...el("creditHistory__row"), children: [
|
|
5200
|
+
/* @__PURE__ */ jsx("td", { style: table.cell(false, last), children: when(operation.occurred_at) }),
|
|
5201
|
+
/* @__PURE__ */ jsx("td", { style: table.cell(false, last), children: TYPE_LABELS[operation.type] ?? operation.type }),
|
|
5202
|
+
/* @__PURE__ */ jsx("td", { style: table.cell(false, last), children: operation.feature_code ?? "\u2014" }),
|
|
5203
|
+
/* @__PURE__ */ jsx("td", { style: table.cell(true, last), children: formatDecimalString(operation.credit_amount) }),
|
|
5204
|
+
/* @__PURE__ */ jsx("td", { style: { ...table.cell(false, last), color: operation.status === "succeeded" ? void 0 : muted2 }, children: operation.status })
|
|
5205
|
+
] }, operation.id);
|
|
5206
|
+
}) })
|
|
5207
|
+
] }) }),
|
|
5208
|
+
hasMore && onLoadMore && /* @__PURE__ */ jsx(
|
|
5209
|
+
"button",
|
|
5210
|
+
{
|
|
5211
|
+
type: "button",
|
|
5212
|
+
onClick: onLoadMore,
|
|
5213
|
+
disabled: isLoading,
|
|
5214
|
+
style: {
|
|
5215
|
+
...buttonStyle("secondary", isLoading),
|
|
5216
|
+
marginTop: "0.75rem",
|
|
5217
|
+
padding: "0.5rem 0.875rem",
|
|
5218
|
+
borderRadius: buttonRadius(`calc(${T.radius} / 2)`),
|
|
5219
|
+
opacity: 1
|
|
5220
|
+
},
|
|
5221
|
+
children: isLoading ? "Loading\u2026" : "Load more"
|
|
5222
|
+
}
|
|
5223
|
+
)
|
|
5224
|
+
] });
|
|
5225
|
+
}
|
|
5226
|
+
|
|
5227
|
+
// src/components/usage/usage-data.ts
|
|
5228
|
+
function formatAmount(amountCents, currency) {
|
|
5229
|
+
const amount = amountCents / 100;
|
|
5230
|
+
try {
|
|
5231
|
+
return new Intl.NumberFormat("en-US", { style: "currency", currency }).format(amount);
|
|
5232
|
+
} catch {
|
|
5233
|
+
return `${currency} ${amount.toFixed(2)}`;
|
|
5234
|
+
}
|
|
5235
|
+
}
|
|
5236
|
+
function formatDay(value) {
|
|
5237
|
+
const parsed = new Date(value);
|
|
5238
|
+
return Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleDateString("en-US", { dateStyle: "medium", timeZone: "UTC" });
|
|
5239
|
+
}
|
|
5240
|
+
var muted3 = T.mutedForeground;
|
|
5241
|
+
function UsageBreakdown({
|
|
5242
|
+
usage,
|
|
5243
|
+
isLoading = false,
|
|
5244
|
+
error = null,
|
|
5245
|
+
emptyMessage = "No usage in this period yet.",
|
|
5246
|
+
appearance
|
|
5247
|
+
}) {
|
|
5248
|
+
const { root, el } = useAppearance(appearance);
|
|
5249
|
+
if (isLoading) return /* @__PURE__ */ jsx("div", { ...root("usageBreakdown", { ...surface, height: 120, background: T.skeleton }), role: "status", "aria-label": "Loading usage" });
|
|
5250
|
+
if (error) return /* @__PURE__ */ jsx("p", { ...root("usageBreakdown", { ...surface, color: T.dangerText }), role: "alert", children: typeof error === "string" ? error : error.message });
|
|
5251
|
+
if (!usage || usage.charges_usage.length === 0) return /* @__PURE__ */ jsx("p", { ...root("usageBreakdown", { ...surface, color: muted3 }), role: "status", children: emptyMessage });
|
|
5252
|
+
const rows = [...usage.charges_usage].sort((a, b) => b.amount_cents - a.amount_cents);
|
|
5253
|
+
return /* @__PURE__ */ jsxs("section", { ...root("usageBreakdown", { ...text.body, minWidth: 0 }), children: [
|
|
5254
|
+
/* @__PURE__ */ jsxs("p", { style: { ...text.secondary, margin: "0 0 0.5rem" }, children: [
|
|
5255
|
+
formatDay(usage.from_datetime),
|
|
5256
|
+
" to ",
|
|
5257
|
+
formatDay(usage.to_datetime)
|
|
5258
|
+
] }),
|
|
5259
|
+
/* @__PURE__ */ jsx("div", { style: table.frame, children: /* @__PURE__ */ jsxs("table", { style: table.table, children: [
|
|
5260
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
5261
|
+
/* @__PURE__ */ jsx("th", { style: table.head(), children: "Feature" }),
|
|
5262
|
+
/* @__PURE__ */ jsx("th", { style: table.head(true), children: "Units" }),
|
|
5263
|
+
/* @__PURE__ */ jsx("th", { style: table.head(true), children: "Amount" })
|
|
5264
|
+
] }) }),
|
|
5265
|
+
/* @__PURE__ */ jsx("tbody", { children: rows.map((row, index) => {
|
|
5266
|
+
const last = index === rows.length - 1;
|
|
5267
|
+
return /* @__PURE__ */ jsxs("tr", { ...el("usageBreakdown__row"), children: [
|
|
5268
|
+
/* @__PURE__ */ jsx("td", { style: table.cell(false, last), children: row.charge.invoice_display_name || row.feature.name }),
|
|
5269
|
+
/* @__PURE__ */ jsx("td", { style: table.cell(true, last), children: formatDecimalString(row.units, 6) }),
|
|
5270
|
+
/* @__PURE__ */ jsx("td", { style: table.cell(true, last), children: formatAmount(row.amount_cents, row.amount_currency) })
|
|
5271
|
+
] }, row.charge.nozle_id);
|
|
5272
|
+
}) }),
|
|
5273
|
+
/* @__PURE__ */ jsx("tfoot", { children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
5274
|
+
/* @__PURE__ */ jsx("td", { style: table.foot(), colSpan: 2, children: "Total" }),
|
|
5275
|
+
/* @__PURE__ */ jsx("td", { style: table.foot(true), children: formatAmount(usage.amount_cents, usage.currency) })
|
|
5276
|
+
] }) })
|
|
5277
|
+
] }) })
|
|
5278
|
+
] });
|
|
5279
|
+
}
|
|
5280
|
+
var muted4 = T.mutedForeground;
|
|
5281
|
+
var BAR_AREA = 120;
|
|
5282
|
+
function unitsFor(period, featureCode) {
|
|
5283
|
+
return period.charges_usage.filter((charge) => charge.feature.code === featureCode).reduce((sum, charge) => sum + (Number(charge.units) || 0), 0);
|
|
5284
|
+
}
|
|
5285
|
+
function UsageTrend({
|
|
5286
|
+
periods,
|
|
5287
|
+
featureCode,
|
|
5288
|
+
isLoading = false,
|
|
5289
|
+
error = null,
|
|
5290
|
+
emptyMessage = "No past usage yet.",
|
|
5291
|
+
appearance
|
|
5292
|
+
}) {
|
|
5293
|
+
const { root, el } = useAppearance(appearance);
|
|
5294
|
+
if (isLoading) return /* @__PURE__ */ jsx("div", { ...root("usageTrend", { ...surface, height: 160, background: T.skeleton }), role: "status", "aria-label": "Loading usage trend" });
|
|
5295
|
+
if (error) return /* @__PURE__ */ jsx("p", { ...root("usageTrend", { ...surface, color: T.dangerText }), role: "alert", children: typeof error === "string" ? error : error.message });
|
|
5296
|
+
if (!periods || periods.length === 0) return /* @__PURE__ */ jsx("p", { ...root("usageTrend", { ...surface, color: muted4 }), role: "status", children: emptyMessage });
|
|
5297
|
+
const ordered = [...periods].sort((a, b) => Date.parse(a.from_datetime) - Date.parse(b.from_datetime));
|
|
5298
|
+
const points = ordered.map((period) => ({
|
|
5299
|
+
key: period.from_datetime,
|
|
5300
|
+
label: formatDay(period.from_datetime),
|
|
5301
|
+
value: featureCode ? unitsFor(period, featureCode) : period.amount_cents,
|
|
5302
|
+
text: featureCode ? `${unitsFor(period, featureCode).toLocaleString("en-US")} ${featureCode}` : formatAmount(period.amount_cents, period.currency)
|
|
5303
|
+
}));
|
|
5304
|
+
const max = Math.max(...points.map((point) => point.value), 0);
|
|
5305
|
+
return /* @__PURE__ */ jsx("figure", { ...root("usageTrend", { ...surface, margin: 0 }), children: /* @__PURE__ */ jsx("div", { style: { display: "flex", alignItems: "flex-end", gap: "0.5rem", height: BAR_AREA + 40 }, children: points.map((point) => /* @__PURE__ */ jsxs("div", { style: { ...text.meta, flex: 1, minWidth: 0, textAlign: "center" }, children: [
|
|
5306
|
+
/* @__PURE__ */ jsx("div", { children: point.text }),
|
|
5307
|
+
/* @__PURE__ */ jsx(
|
|
5308
|
+
"div",
|
|
5309
|
+
{
|
|
5310
|
+
role: "img",
|
|
5311
|
+
"aria-label": `${point.label}: ${point.text}`,
|
|
5312
|
+
title: `${point.label}: ${point.text}`,
|
|
5313
|
+
...el("usageTrend__bar", {
|
|
5314
|
+
height: max > 0 ? Math.max(2, point.value / max * BAR_AREA) : 2,
|
|
5315
|
+
marginTop: 4,
|
|
5316
|
+
borderRadius: `min(4px, ${T.radius}) min(4px, ${T.radius}) 0 0`,
|
|
5317
|
+
background: T.primary
|
|
5318
|
+
})
|
|
5319
|
+
}
|
|
5320
|
+
),
|
|
5321
|
+
/* @__PURE__ */ jsx("div", { style: { marginTop: 4 }, children: point.label })
|
|
5322
|
+
] }, point.key)) }) });
|
|
5323
|
+
}
|
|
4034
5324
|
|
|
4035
|
-
export { BillingContext, BillingPortal, BillingPortalError, BillingProvider, CancellationControl, Checkout, CheckoutButton, FeatureGate, LockedOverlay, PaymentMethodDisplay, PlanBadge, PlanCard, PlanChangeControl, PlanComparison, PlanGate, PricingTable, UpgradeButton, UpgradeModal, UpgradePrompt, UsageAlert, UsageDashboard, UsageGate, UsageMeter, getUsageMeterColor, useBillingContext, useCheckout, useCheckout2 as useCheckoutSession, useNozleClient, useOptionalBillingContext, usePlans };
|
|
5325
|
+
export { BillingContext, BillingPortal, BillingPortalError, BillingProvider, BillingRequestError, CancellationControl, Checkout, CheckoutButton, CreditBalance, CreditHistory, CurrentPlan, FeatureGate, LockedOverlay, PaymentMethodDisplay, PlanBadge, PlanCard, PlanChangeControl, PlanComparison, PlanGate, PoweredByNozle, PricingTable, UpgradeButton, UpgradeModal, UpgradePrompt, UsageAlert, UsageBreakdown, UsageDashboard, UsageGate, UsageMeter, UsageTrend, comparisonRows, currentPlanMeters, getAvailableIntervals, getDefaultInterval, getPricingCards, getPricingIntervalOptions, getUsageMeterColor, planFeatures, sortPlansByPrice, usageItems, useAppearance, useBillingContext, useBillingRefresh, useCheckout, useCheckout2 as useCheckoutSession, useCreditBalance, useCreditHistory, useEntitlement, useInvoiceDownload, useInvoices, useNozleBranding, useNozleClient, useOptionalBillingContext, usePlans, useSubscription, useUpgradePreview, useUsage };
|
|
4036
5326
|
//# sourceMappingURL=index.js.map
|
|
4037
5327
|
//# sourceMappingURL=index.js.map
|