@nozle-js/react 0.10.1 → 0.11.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/LICENSE +21 -0
- package/README.md +76 -1
- package/dist/chunk-NLPONQ5O.js +246 -0
- package/dist/chunk-NLPONQ5O.js.map +1 -0
- package/dist/index.cjs +2192 -1059
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +751 -71
- package/dist/index.d.ts +751 -71
- package/dist/index.js +1802 -930
- 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 -5
package/dist/index.js
CHANGED
|
@@ -1,9 +1,238 @@
|
|
|
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 { plans: data.plans ?? [], nozleBranding: data.meta?.nozle_branding === true };
|
|
169
|
+
},
|
|
170
|
+
staleTime: 5 * 6e4,
|
|
171
|
+
// The pricing page shows a load error at once; the app can call refetch().
|
|
172
|
+
retry: false
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function usePlans() {
|
|
176
|
+
const { client, queryClient } = useBillingContext();
|
|
177
|
+
const query = useQuery(catalogQuery(client), queryClient);
|
|
178
|
+
return {
|
|
179
|
+
plans: query.data?.plans ?? [],
|
|
180
|
+
isLoading: query.isPending,
|
|
181
|
+
error: query.error ?? null,
|
|
182
|
+
refetch: query.refetch
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// src/components/branding/logo.ts
|
|
187
|
+
var LOGO_VIEW_BOX = "0 0 3208 1122";
|
|
188
|
+
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";
|
|
189
|
+
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";
|
|
190
|
+
var BrandingScope = createContext(false);
|
|
191
|
+
function useNozleBranding() {
|
|
192
|
+
const billing = useOptionalBillingContext();
|
|
193
|
+
const { data } = useQuery(
|
|
194
|
+
// Reads the catalog request the components already make: mounting this must not refetch a
|
|
195
|
+
// failed catalog, or a table that shows the error would loop back into loading.
|
|
196
|
+
{ ...catalogQuery(billing?.client), enabled: Boolean(billing), refetchOnMount: false, retryOnMount: false },
|
|
197
|
+
billing?.queryClient ?? idleQueryClient
|
|
198
|
+
);
|
|
199
|
+
return data?.nozleBranding === true;
|
|
200
|
+
}
|
|
201
|
+
function PoweredByNozle() {
|
|
202
|
+
const { root } = useAppearance();
|
|
203
|
+
return /* @__PURE__ */ jsxs(
|
|
204
|
+
"a",
|
|
205
|
+
{
|
|
206
|
+
href: "https://nozle.dev",
|
|
207
|
+
target: "_blank",
|
|
208
|
+
rel: "noopener noreferrer",
|
|
209
|
+
...root("poweredBy", {
|
|
210
|
+
...text.meta,
|
|
211
|
+
display: "flex",
|
|
212
|
+
alignItems: "center",
|
|
213
|
+
justifyContent: "center",
|
|
214
|
+
gap: "6px",
|
|
215
|
+
marginTop: "1rem",
|
|
216
|
+
textDecoration: "none"
|
|
217
|
+
}),
|
|
218
|
+
children: [
|
|
219
|
+
"Powered by",
|
|
220
|
+
/* @__PURE__ */ jsxs("svg", { viewBox: LOGO_VIEW_BOX, height: "14", width: "40", role: "img", "aria-label": "Nozle", style: { color: T.foreground }, children: [
|
|
221
|
+
/* @__PURE__ */ jsx("path", { fill: "#F34213", d: LOGO_MARK_PATH }),
|
|
222
|
+
/* @__PURE__ */ jsx("path", { fill: "currentColor", d: LOGO_WORDMARK_PATH })
|
|
223
|
+
] })
|
|
224
|
+
]
|
|
225
|
+
}
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
function NozleBranding({ show, children }) {
|
|
229
|
+
const nested = useContext(BrandingScope);
|
|
230
|
+
const fromCatalog = useNozleBranding();
|
|
231
|
+
return /* @__PURE__ */ jsxs(BrandingScope.Provider, { value: true, children: [
|
|
232
|
+
children,
|
|
233
|
+
!nested && (show ?? fromCatalog) && /* @__PURE__ */ jsx(PoweredByNozle, {})
|
|
234
|
+
] });
|
|
235
|
+
}
|
|
7
236
|
|
|
8
237
|
// src/components/portal/client.ts
|
|
9
238
|
var BillingPortalError = class extends Error {
|
|
@@ -96,7 +325,7 @@ var identityQuery = `query SdkPortalIdentity {
|
|
|
96
325
|
addressLine1 addressLine2 city state zipcode country
|
|
97
326
|
shippingAddress { addressLine1 addressLine2 city state zipcode country }
|
|
98
327
|
}
|
|
99
|
-
customerPortalOrganization { id name }
|
|
328
|
+
customerPortalOrganization { id name nozleBranding }
|
|
100
329
|
}`;
|
|
101
330
|
var subscriptionsQuery = `query SdkPortalSubscriptions($page: Int) {
|
|
102
331
|
customerPortalSubscriptions(status: [active], limit: 20, page: $page) {
|
|
@@ -145,40 +374,55 @@ var topUpMutation = `mutation SdkPortalTopUp($input: CreateCustomerPortalWalletT
|
|
|
145
374
|
}`;
|
|
146
375
|
|
|
147
376
|
// src/components/portal/styles.ts
|
|
377
|
+
var line = `${T.borderWidth} solid ${T.border}`;
|
|
378
|
+
var r = (factor) => `calc(${T.radius} * ${factor})`;
|
|
148
379
|
var portalStyles = `
|
|
149
|
-
.nozle-billing-portal{color
|
|
380
|
+
.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
381
|
.nozle-billing-portal *{box-sizing:border-box}
|
|
151
382
|
.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
|
|
383
|
+
.nozle-billing-portal h2{font:${T.fontTitle}}
|
|
384
|
+
.nozle-billing-portal h3{font:${T.fontHeading}}
|
|
154
385
|
.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
|
|
386
|
+
.nozle-billing-portal section{margin-top:28px;padding-top:24px;border-top:${line}}
|
|
387
|
+
.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}
|
|
388
|
+
.nozle-billing-portal button:hover,.nozle-billing-portal .nzp-link:hover{background:${T.muted}}
|
|
158
389
|
.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
|
|
390
|
+
.nozle-billing-portal :focus-visible{outline:2px solid ${T.primary};outline-offset:3px}
|
|
391
|
+
.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}}
|
|
392
|
+
.nozle-billing-portal .nzp-primary:hover{filter:brightness(.92);background:${T.primary}}
|
|
393
|
+
.nozle-billing-portal .nzp-muted{color:${T.mutedForeground}}
|
|
394
|
+
.nozle-billing-portal .nzp-notice{color:${T.successText}}
|
|
163
395
|
.nozle-billing-portal .nzp-row{display:flex;align-items:center;justify-content:space-between;gap:14px;flex-wrap:wrap}
|
|
164
396
|
.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
|
|
397
|
+
.nozle-billing-portal .nzp-card{background:${T.card};border:${line};border-radius:${r(0.85)};box-shadow:${T.shadow};padding:20px;min-width:0}
|
|
398
|
+
.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}}
|
|
399
|
+
.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}
|
|
400
|
+
.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}
|
|
401
|
+
.nozle-billing-portal .nzp-card-actions>.nozle-cancellation>button{margin-left:auto}
|
|
402
|
+
.nozle-billing-portal .nzp-price{font:${T.fontMetric};font-variant-numeric:tabular-nums;margin:12px 0 4px}
|
|
403
|
+
.nozle-billing-portal .nzp-price .nzp-muted{font:${T.fontMeta}}
|
|
404
|
+
.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}
|
|
405
|
+
.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}
|
|
406
|
+
.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}
|
|
407
|
+
.nozle-billing-portal .nzp-table{overflow-x:auto;margin-top:16px;background:${T.card};border:${line};border-radius:${T.radius}}
|
|
408
|
+
.nozle-billing-portal table{width:100%;border-collapse:collapse;text-align:left;font:inherit}
|
|
409
|
+
.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}
|
|
410
|
+
.nozle-billing-portal td{height:48px;padding:14px 20px;border-bottom:${T.borderWidth} solid ${T.rowLine};white-space:nowrap}
|
|
411
|
+
.nozle-billing-portal tbody tr:last-child td{border-bottom:0}
|
|
412
|
+
.nozle-billing-portal .nzp-num{text-align:right}
|
|
413
|
+
.nozle-billing-portal td.nzp-num{font-family:${T.fontNumbers};font-variant-numeric:tabular-nums}
|
|
414
|
+
.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}}
|
|
415
|
+
.nozle-billing-portal .nzp-search:focus-within{border-color:${T.foreground}}
|
|
416
|
+
.nozle-billing-portal .nzp-search input{flex:1;height:100%;border:0;padding:0;background:transparent;color:${T.foreground};outline:none}
|
|
417
|
+
.nozle-billing-portal .nzp-search input::placeholder{color:${T.mutedForeground}}
|
|
418
|
+
.nozle-billing-portal label{display:grid;gap:6px;font-weight:500;min-width:0}
|
|
419
|
+
.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
420
|
.nozle-billing-portal .nzp-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,220px),1fr));gap:16px;margin:20px 0}
|
|
177
421
|
.nozle-billing-portal .nzp-actions{display:flex;align-items:flex-end;gap:10px;flex-wrap:wrap;margin-top:20px}
|
|
178
422
|
.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
|
|
423
|
+
.nozle-billing-portal dt{font:${T.fontMeta};color:${T.mutedForeground}}
|
|
180
424
|
.nozle-billing-portal dd{margin:3px 0 0;overflow-wrap:anywhere}
|
|
181
|
-
.nozle-billing-portal footer{margin-top:30px;text-align:center;color
|
|
425
|
+
.nozle-billing-portal footer{margin-top:30px;text-align:center;color:${T.mutedForeground};font:${T.fontMeta}}
|
|
182
426
|
`;
|
|
183
427
|
|
|
184
428
|
// src/components/portal/timezones.ts
|
|
@@ -362,6 +606,25 @@ function compareDecimalStrings(left, right) {
|
|
|
362
606
|
}
|
|
363
607
|
return magnitude * a.sign;
|
|
364
608
|
}
|
|
609
|
+
function formatDecimalString(value, maximumFractionDigits = 12) {
|
|
610
|
+
const parsed = parseDecimal(value);
|
|
611
|
+
if (!parsed) return value;
|
|
612
|
+
const fractionLimit = Math.max(
|
|
613
|
+
0,
|
|
614
|
+
Math.min(30, Math.trunc(maximumFractionDigits))
|
|
615
|
+
);
|
|
616
|
+
const firstGroupLength = parsed.integer.length % 3 || 3;
|
|
617
|
+
const groups = [parsed.integer.slice(0, firstGroupLength)];
|
|
618
|
+
for (let index = firstGroupLength; index < parsed.integer.length; index += 3) {
|
|
619
|
+
groups.push(parsed.integer.slice(index, index + 3));
|
|
620
|
+
}
|
|
621
|
+
const groupedInteger = groups.join(",");
|
|
622
|
+
const fraction = parsed.fraction.slice(0, fractionLimit);
|
|
623
|
+
return `${parsed.sign < 0 ? "-" : ""}${groupedInteger}${fraction ? `.${fraction}` : ""}`;
|
|
624
|
+
}
|
|
625
|
+
function creditUnitLabel(value, unitName) {
|
|
626
|
+
return compareDecimalStrings(value, "1") === 0 ? unitName : `${unitName}s`;
|
|
627
|
+
}
|
|
365
628
|
|
|
366
629
|
// src/components/portal/amounts.ts
|
|
367
630
|
function walletAmountCents(credits, rate, currencyDigits) {
|
|
@@ -382,16 +645,16 @@ function walletAmountCents(credits, rate, currencyDigits) {
|
|
|
382
645
|
return `${padded.slice(0, -scale)}.${padded.slice(-scale)}`;
|
|
383
646
|
}
|
|
384
647
|
var styles = `
|
|
385
|
-
.nozle-cancellation{font
|
|
386
|
-
.nozle-cancellation button{font
|
|
648
|
+
.nozle-cancellation{font:${T.fontBody};color:${T.foreground}}
|
|
649
|
+
.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
650
|
.nozle-cancellation button:disabled{opacity:.55;cursor:wait}
|
|
388
|
-
.nozle-cancellation :focus-visible{outline:2px solid
|
|
651
|
+
.nozle-cancellation :focus-visible{outline:2px solid ${T.primary};outline-offset:3px}
|
|
389
652
|
.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
|
|
653
|
+
.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}
|
|
654
|
+
.nozle-cancellation .nzc-dialog h3{margin:0 0 16px;font:${T.fontTitle};letter-spacing:normal;color:${T.foreground}}
|
|
655
|
+
.nozle-cancellation .nzc-dialog p{margin:12px 0}
|
|
393
656
|
.nozle-cancellation .nzc-buttons{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:10px;margin-top:20px}
|
|
394
|
-
.nozle-cancellation [role=alert]{color
|
|
657
|
+
.nozle-cancellation [role=alert],.nozle-cancellation .nzc-danger{color:${T.dangerText}}
|
|
395
658
|
`;
|
|
396
659
|
function formatDate(value, locale, timezone) {
|
|
397
660
|
const timeZone = timezone.startsWith("TZ_") ? portalTimezones[timezone] ?? "UTC" : timezone;
|
|
@@ -421,8 +684,10 @@ function CancellationFlow({
|
|
|
421
684
|
onError,
|
|
422
685
|
locale = "en-US",
|
|
423
686
|
timezone = "UTC",
|
|
424
|
-
nonce
|
|
687
|
+
nonce,
|
|
688
|
+
appearance
|
|
425
689
|
}) {
|
|
690
|
+
const { root } = useAppearance(appearance);
|
|
426
691
|
const [open, setOpen] = useState(false);
|
|
427
692
|
const [busy, setBusy] = useState(false);
|
|
428
693
|
const [preview, setPreview] = useState();
|
|
@@ -609,7 +874,7 @@ function CancellationFlow({
|
|
|
609
874
|
);
|
|
610
875
|
});
|
|
611
876
|
}
|
|
612
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
877
|
+
return /* @__PURE__ */ jsxs("div", { ...root("cancellationControl", void 0, "nozle-cancellation"), children: [
|
|
613
878
|
/* @__PURE__ */ jsx("style", { nonce, children: styles }),
|
|
614
879
|
notice && /* @__PURE__ */ jsx("p", { role: "status", children: notice }),
|
|
615
880
|
active && !expired && /* @__PURE__ */ jsx("button", { ref: trigger, type: "button", onClick: () => void loadPreview(), children: ending ? "Keep my subscription" : "Cancel subscription" }),
|
|
@@ -694,6 +959,7 @@ function CancellationFlow({
|
|
|
694
959
|
"button",
|
|
695
960
|
{
|
|
696
961
|
type: "button",
|
|
962
|
+
className: preview.operation === "cancel" ? "nzc-danger" : void 0,
|
|
697
963
|
disabled: busy,
|
|
698
964
|
onClick: () => void confirm(),
|
|
699
965
|
children: request.current ? "Check status / retry" : preview.operation === "cancel" ? "Confirm cancellation" : "Confirm keep subscription"
|
|
@@ -705,66 +971,6 @@ function CancellationFlow({
|
|
|
705
971
|
) })
|
|
706
972
|
] });
|
|
707
973
|
}
|
|
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
974
|
|
|
769
975
|
// src/components/billing/razorpay-checkout.ts
|
|
770
976
|
var loadingScript;
|
|
@@ -979,8 +1185,10 @@ function CheckoutButton({
|
|
|
979
1185
|
onStripeClientSecret,
|
|
980
1186
|
onSuccess,
|
|
981
1187
|
onComplete,
|
|
982
|
-
onScheduled
|
|
1188
|
+
onScheduled,
|
|
1189
|
+
appearance
|
|
983
1190
|
}) {
|
|
1191
|
+
const { root, vars } = useAppearance(appearance);
|
|
984
1192
|
const { createCheckout, verifyCheckout, getCheckoutStatus } = useBillingContext();
|
|
985
1193
|
const attempt = useRef(void 0);
|
|
986
1194
|
const inFlight = useRef(false);
|
|
@@ -1043,24 +1251,13 @@ function CheckoutButton({
|
|
|
1043
1251
|
type: "button",
|
|
1044
1252
|
onClick: () => void handleClick(),
|
|
1045
1253
|
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
|
-
},
|
|
1254
|
+
...root("checkoutButton", { ...buttonStyle("primary", loading), ...style }, className),
|
|
1058
1255
|
"aria-busy": loading,
|
|
1059
1256
|
children: loading ? "Loading..." : label
|
|
1060
1257
|
}
|
|
1061
1258
|
),
|
|
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 })
|
|
1259
|
+
processing && /* @__PURE__ */ jsx("span", { role: "status", style: { ...vars, ...text.body }, children: "Payment is processing. Confirmation may take a little while." }),
|
|
1260
|
+
error && /* @__PURE__ */ jsx("span", { role: "alert", style: { ...vars, ...text.body, color: T.dangerText }, children: error.message })
|
|
1064
1261
|
] });
|
|
1065
1262
|
}
|
|
1066
1263
|
var CheckoutContext = createContext(null);
|
|
@@ -1071,23 +1268,62 @@ function useCheckout() {
|
|
|
1071
1268
|
}
|
|
1072
1269
|
return ctx;
|
|
1073
1270
|
}
|
|
1074
|
-
function
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1271
|
+
function rgb(color) {
|
|
1272
|
+
const match = color?.match(/^rgba?\(\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)/);
|
|
1273
|
+
return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
|
|
1274
|
+
}
|
|
1275
|
+
function stripeColor(color) {
|
|
1276
|
+
return rgb(color) ? color : color?.startsWith("#") ? color : void 0;
|
|
1277
|
+
}
|
|
1278
|
+
function toStripeAppearance(colors) {
|
|
1279
|
+
const background = rgb(colors.background);
|
|
1280
|
+
const dark = background ? (0.2126 * background[0] + 0.7152 * background[1] + 0.0722 * background[2]) / 255 < 0.5 : false;
|
|
1281
|
+
const variables = Object.fromEntries(
|
|
1282
|
+
Object.entries({
|
|
1283
|
+
colorPrimary: stripeColor(colors.primary),
|
|
1284
|
+
colorBackground: stripeColor(colors.background),
|
|
1285
|
+
colorText: stripeColor(colors.foreground),
|
|
1286
|
+
colorDanger: stripeColor(colors.danger),
|
|
1287
|
+
borderRadius: colors.radius,
|
|
1288
|
+
fontFamily: colors.font
|
|
1289
|
+
}).filter(([, value]) => value)
|
|
1290
|
+
);
|
|
1291
|
+
return { theme: dark ? "night" : "stripe", variables };
|
|
1292
|
+
}
|
|
1293
|
+
function useStripeAppearance(appearanceKey) {
|
|
1294
|
+
const probe = useRef(null);
|
|
1295
|
+
const [stripeAppearance, setStripeAppearance] = useState(null);
|
|
1296
|
+
useEffect(() => {
|
|
1297
|
+
if (!probe.current) return;
|
|
1298
|
+
const style = getComputedStyle(probe.current);
|
|
1299
|
+
setStripeAppearance(
|
|
1300
|
+
toStripeAppearance({
|
|
1301
|
+
primary: style.color,
|
|
1302
|
+
background: style.backgroundColor,
|
|
1303
|
+
foreground: style.borderTopColor,
|
|
1304
|
+
danger: style.borderBottomColor,
|
|
1305
|
+
radius: style.borderTopLeftRadius,
|
|
1306
|
+
font: style.fontFamily
|
|
1307
|
+
})
|
|
1308
|
+
);
|
|
1309
|
+
}, [appearanceKey]);
|
|
1310
|
+
const probeElement = /* @__PURE__ */ jsx(
|
|
1311
|
+
"span",
|
|
1312
|
+
{
|
|
1313
|
+
ref: probe,
|
|
1314
|
+
"aria-hidden": "true",
|
|
1315
|
+
style: {
|
|
1316
|
+
display: "none",
|
|
1317
|
+
color: T.primary,
|
|
1318
|
+
backgroundColor: T.card,
|
|
1319
|
+
borderTop: `1px solid ${T.foreground}`,
|
|
1320
|
+
borderBottom: `1px solid ${T.danger}`,
|
|
1321
|
+
borderRadius: T.radius,
|
|
1322
|
+
fontFamily: T.font
|
|
1323
|
+
}
|
|
1089
1324
|
}
|
|
1090
|
-
|
|
1325
|
+
);
|
|
1326
|
+
return { probeElement, stripeAppearance };
|
|
1091
1327
|
}
|
|
1092
1328
|
function CheckoutInner({
|
|
1093
1329
|
submitLabel,
|
|
@@ -1097,10 +1333,12 @@ function CheckoutInner({
|
|
|
1097
1333
|
className,
|
|
1098
1334
|
style,
|
|
1099
1335
|
returnUrl,
|
|
1100
|
-
children
|
|
1336
|
+
children,
|
|
1337
|
+
appearance
|
|
1101
1338
|
}) {
|
|
1102
1339
|
const stripe = useStripe();
|
|
1103
1340
|
const elements = useElements();
|
|
1341
|
+
const { el } = useAppearance(appearance);
|
|
1104
1342
|
const [isProcessing, setIsProcessing] = useState(false);
|
|
1105
1343
|
const [error, setError] = useState(null);
|
|
1106
1344
|
const [isReady, setIsReady] = useState(false);
|
|
@@ -1134,7 +1372,7 @@ function CheckoutInner({
|
|
|
1134
1372
|
setIsProcessing(false);
|
|
1135
1373
|
}
|
|
1136
1374
|
}
|
|
1137
|
-
return /* @__PURE__ */ jsx(CheckoutContext.Provider, { value: { confirmPayment, isProcessing, error }, children: /* @__PURE__ */ jsxs("div", {
|
|
1375
|
+
return /* @__PURE__ */ jsx(CheckoutContext.Provider, { value: { confirmPayment, isProcessing, error }, children: /* @__PURE__ */ jsxs("div", { children: [
|
|
1138
1376
|
/* @__PURE__ */ jsx("style", { children: `@keyframes nozle-skeleton-pulse { 0%{background-position:200% 0} 100%{background-position:-200% 0} }` }),
|
|
1139
1377
|
!isReady && /* @__PURE__ */ jsx(
|
|
1140
1378
|
"div",
|
|
@@ -1142,8 +1380,8 @@ function CheckoutInner({
|
|
|
1142
1380
|
"data-testid": "checkout-skeleton",
|
|
1143
1381
|
style: {
|
|
1144
1382
|
height: "200px",
|
|
1145
|
-
borderRadius:
|
|
1146
|
-
background:
|
|
1383
|
+
borderRadius: T.radius,
|
|
1384
|
+
background: `linear-gradient(90deg, ${T.muted} 25%, ${T.skeleton} 50%, ${T.muted} 75%)`,
|
|
1147
1385
|
backgroundSize: "200% 100%",
|
|
1148
1386
|
animation: "nozle-skeleton-pulse 1.5s ease-in-out infinite"
|
|
1149
1387
|
}
|
|
@@ -1158,16 +1396,7 @@ function CheckoutInner({
|
|
|
1158
1396
|
}
|
|
1159
1397
|
}
|
|
1160
1398
|
),
|
|
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
|
-
),
|
|
1399
|
+
error !== null && /* @__PURE__ */ jsx("p", { ...el("checkout__error", { color: T.dangerText, margin: "0.5rem 0 0" }), children: error.message }),
|
|
1171
1400
|
children ?? /* @__PURE__ */ jsx(
|
|
1172
1401
|
"button",
|
|
1173
1402
|
{
|
|
@@ -1175,18 +1404,7 @@ function CheckoutInner({
|
|
|
1175
1404
|
onClick: () => void confirmPayment(),
|
|
1176
1405
|
disabled: isProcessing || !stripe,
|
|
1177
1406
|
"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
|
-
},
|
|
1407
|
+
...el("checkout__submit", { ...buttonStyle("primary", isProcessing), marginTop: "1rem", width: "100%" }),
|
|
1190
1408
|
children: isProcessing ? "Processing..." : submitLabel ?? "Pay now"
|
|
1191
1409
|
}
|
|
1192
1410
|
)
|
|
@@ -1204,19 +1422,21 @@ function StripeCheckout({
|
|
|
1204
1422
|
onReady,
|
|
1205
1423
|
className,
|
|
1206
1424
|
style,
|
|
1207
|
-
children
|
|
1425
|
+
children,
|
|
1426
|
+
appearance: themeAppearance
|
|
1208
1427
|
}) {
|
|
1428
|
+
const { root } = useAppearance(themeAppearance);
|
|
1429
|
+
const { probeElement, stripeAppearance } = useStripeAppearance(JSON.stringify(root("checkout").style));
|
|
1209
1430
|
const stripePromise = useMemo(
|
|
1210
1431
|
() => loadStripe(publishableKey, stripeAccount ? { stripeAccount } : void 0),
|
|
1211
1432
|
[publishableKey, stripeAccount]
|
|
1212
1433
|
);
|
|
1213
|
-
const appearance = useMemo(() => buildStripeAppearance(), []);
|
|
1214
1434
|
const embeddedOptions = useMemo(
|
|
1215
1435
|
() => ({ clientSecret, onComplete }),
|
|
1216
1436
|
[clientSecret, onComplete]
|
|
1217
1437
|
);
|
|
1218
1438
|
if (clientSecret.startsWith("cs_")) {
|
|
1219
|
-
return /* @__PURE__ */ jsx("div", {
|
|
1439
|
+
return /* @__PURE__ */ jsx("div", { ...root("checkout", { ...text.body, ...style }, className), children: /* @__PURE__ */ jsx(
|
|
1220
1440
|
EmbeddedCheckoutProvider,
|
|
1221
1441
|
{
|
|
1222
1442
|
stripe: stripePromise,
|
|
@@ -1225,23 +1445,26 @@ function StripeCheckout({
|
|
|
1225
1445
|
}
|
|
1226
1446
|
) });
|
|
1227
1447
|
}
|
|
1228
|
-
const options = { clientSecret, appearance };
|
|
1229
|
-
return /* @__PURE__ */
|
|
1230
|
-
|
|
1231
|
-
{
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1448
|
+
const options = stripeAppearance && { clientSecret, appearance: stripeAppearance };
|
|
1449
|
+
return /* @__PURE__ */ jsxs("div", { ...root("checkout", { ...text.body, ...style }, className), children: [
|
|
1450
|
+
probeElement,
|
|
1451
|
+
options && /* @__PURE__ */ jsx(Elements, { stripe: stripePromise, options, children: /* @__PURE__ */ jsx(
|
|
1452
|
+
CheckoutInner,
|
|
1453
|
+
{
|
|
1454
|
+
submitLabel,
|
|
1455
|
+
onSuccess,
|
|
1456
|
+
onError,
|
|
1457
|
+
onReady,
|
|
1458
|
+
returnUrl,
|
|
1459
|
+
children,
|
|
1460
|
+
appearance: themeAppearance
|
|
1461
|
+
}
|
|
1462
|
+
) }, clientSecret)
|
|
1463
|
+
] });
|
|
1242
1464
|
}
|
|
1243
1465
|
function ProviderCheckout(props) {
|
|
1244
1466
|
const billing = useOptionalBillingContext();
|
|
1467
|
+
const { root, el } = useAppearance(props.appearance);
|
|
1245
1468
|
const [busy, setBusy] = useState(false);
|
|
1246
1469
|
const [error, setError] = useState(null);
|
|
1247
1470
|
const [processing, setProcessing] = useState(false);
|
|
@@ -1291,17 +1514,19 @@ function ProviderCheckout(props) {
|
|
|
1291
1514
|
submitLabel: props.submitLabel,
|
|
1292
1515
|
onSuccess: props.onSuccess,
|
|
1293
1516
|
onComplete: props.onComplete,
|
|
1294
|
-
onError: props.onError
|
|
1517
|
+
onError: props.onError,
|
|
1518
|
+
className: props.className,
|
|
1519
|
+
appearance: props.appearance
|
|
1295
1520
|
}
|
|
1296
1521
|
);
|
|
1297
1522
|
}
|
|
1298
|
-
return /* @__PURE__ */ jsx("p", { role: "alert", children: "Payment setup is incomplete. Contact support to continue." });
|
|
1523
|
+
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
1524
|
}
|
|
1300
1525
|
return /* @__PURE__ */ jsx(
|
|
1301
1526
|
CheckoutContext.Provider,
|
|
1302
1527
|
{
|
|
1303
1528
|
value: { confirmPayment: pay, isProcessing: busy, error },
|
|
1304
|
-
children: /* @__PURE__ */ jsxs("div", {
|
|
1529
|
+
children: /* @__PURE__ */ jsxs("div", { ...root("checkout", text.body, props.className), children: [
|
|
1305
1530
|
/* @__PURE__ */ jsx(
|
|
1306
1531
|
"button",
|
|
1307
1532
|
{
|
|
@@ -1309,28 +1534,30 @@ function ProviderCheckout(props) {
|
|
|
1309
1534
|
disabled: busy,
|
|
1310
1535
|
"aria-busy": busy,
|
|
1311
1536
|
onClick: () => void pay(),
|
|
1537
|
+
...el("checkout__submit", buttonStyle("primary", busy)),
|
|
1312
1538
|
children: busy ? "Processing\u2026" : props.submitLabel || "Pay now"
|
|
1313
1539
|
}
|
|
1314
1540
|
),
|
|
1315
1541
|
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 })
|
|
1542
|
+
error && /* @__PURE__ */ jsx("p", { role: "alert", ...el("checkout__error", { color: T.dangerText }), children: error.message })
|
|
1317
1543
|
] })
|
|
1318
1544
|
}
|
|
1319
1545
|
);
|
|
1320
1546
|
}
|
|
1321
1547
|
function Checkout(props) {
|
|
1322
|
-
return "checkout" in props ? /* @__PURE__ */ jsx(ProviderCheckout, { ...props }) : /* @__PURE__ */ jsx(StripeCheckout, { ...props });
|
|
1548
|
+
return /* @__PURE__ */ jsx(NozleBranding, { children: "checkout" in props ? /* @__PURE__ */ jsx(ProviderCheckout, { ...props }) : /* @__PURE__ */ jsx(StripeCheckout, { ...props }) });
|
|
1323
1549
|
}
|
|
1324
1550
|
var styles2 = `
|
|
1325
|
-
.nozle-
|
|
1326
|
-
.nozle-
|
|
1327
|
-
.nozle-
|
|
1328
|
-
.nozle-
|
|
1329
|
-
.nozle-
|
|
1330
|
-
.nozle-
|
|
1331
|
-
.nozle-
|
|
1332
|
-
.nozle-
|
|
1333
|
-
.nozle-
|
|
1551
|
+
.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}}
|
|
1552
|
+
.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}
|
|
1553
|
+
.nozle-upgradeModal h2{font:${T.fontTitle};letter-spacing:normal;color:${T.foreground};margin:0 0 16px}
|
|
1554
|
+
.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}
|
|
1555
|
+
.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}}
|
|
1556
|
+
.nozle-upgradeModal button:disabled{opacity:.55;cursor:wait}
|
|
1557
|
+
.nozle-upgradeModal :focus-visible{outline:2px solid ${T.primary};outline-offset:3px}
|
|
1558
|
+
.nozle-upgradeModal .nozle-upgradeModal__actions{display:flex;flex-wrap:wrap;gap:10px;justify-content:flex-end;margin-top:20px}
|
|
1559
|
+
.nozle-upgradeModal .nzu-back{margin-right:auto}
|
|
1560
|
+
.nozle-upgradeModal [role=alert]{color:${T.dangerText}}
|
|
1334
1561
|
`;
|
|
1335
1562
|
function UpgradeModal(props) {
|
|
1336
1563
|
const billing = useOptionalBillingContext();
|
|
@@ -1341,6 +1568,7 @@ function UpgradeModal(props) {
|
|
|
1341
1568
|
const dialog = useRef(null);
|
|
1342
1569
|
const titleId = useId();
|
|
1343
1570
|
const busy = Boolean(props.busy || confirming);
|
|
1571
|
+
const { root, el } = useAppearance(props.appearance);
|
|
1344
1572
|
useEffect(() => {
|
|
1345
1573
|
if (!props.isOpen) return;
|
|
1346
1574
|
const previous = document.activeElement;
|
|
@@ -1400,17 +1628,17 @@ function UpgradeModal(props) {
|
|
|
1400
1628
|
}
|
|
1401
1629
|
}
|
|
1402
1630
|
if (!props.isOpen) return null;
|
|
1403
|
-
const
|
|
1631
|
+
const money3 = (value) => new Intl.NumberFormat(props.locale ?? "en-US", {
|
|
1404
1632
|
style: "currency",
|
|
1405
1633
|
currency: props.preview?.currency ?? "USD"
|
|
1406
1634
|
}).format(value);
|
|
1407
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
1635
|
+
return /* @__PURE__ */ jsxs("div", { ...root("upgradeModal", void 0, "nozle-upgrade-modal"), children: [
|
|
1408
1636
|
/* @__PURE__ */ jsx("style", { nonce: props.nonce, children: styles2 }),
|
|
1409
1637
|
/* @__PURE__ */ jsxs(
|
|
1410
1638
|
"div",
|
|
1411
1639
|
{
|
|
1412
1640
|
ref: dialog,
|
|
1413
|
-
|
|
1641
|
+
...el("upgradeModal__dialog"),
|
|
1414
1642
|
role: "dialog",
|
|
1415
1643
|
"aria-modal": "true",
|
|
1416
1644
|
"aria-labelledby": titleId,
|
|
@@ -1448,15 +1676,15 @@ function UpgradeModal(props) {
|
|
|
1448
1676
|
props.preview && /* @__PURE__ */ jsxs("div", { children: [
|
|
1449
1677
|
props.preview.credit > 0 && /* @__PURE__ */ jsxs("p", { children: [
|
|
1450
1678
|
"Credits applied: ",
|
|
1451
|
-
|
|
1679
|
+
money3(-props.preview.credit)
|
|
1452
1680
|
] }),
|
|
1453
1681
|
/* @__PURE__ */ jsxs("p", { children: [
|
|
1454
1682
|
"New plan charge: ",
|
|
1455
|
-
|
|
1683
|
+
money3(props.preview.debit)
|
|
1456
1684
|
] }),
|
|
1457
1685
|
/* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsxs("strong", { children: [
|
|
1458
1686
|
"Due today: ",
|
|
1459
|
-
|
|
1687
|
+
money3(props.preview.net)
|
|
1460
1688
|
] }) }),
|
|
1461
1689
|
/* @__PURE__ */ jsxs("p", { children: [
|
|
1462
1690
|
"Next billing date:",
|
|
@@ -1468,18 +1696,21 @@ function UpgradeModal(props) {
|
|
|
1468
1696
|
] })
|
|
1469
1697
|
] }),
|
|
1470
1698
|
props.children,
|
|
1471
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1699
|
+
/* @__PURE__ */ jsxs("div", { ...el("upgradeModal__actions"), children: [
|
|
1700
|
+
props.onBack && /* @__PURE__ */ jsx("button", { type: "button", className: "nzu-back", onClick: props.onBack, disabled: busy, children: "Back" }),
|
|
1472
1701
|
/* @__PURE__ */ jsx("button", { type: "button", onClick: props.onCancel, disabled: busy, children: "Close" }),
|
|
1473
1702
|
/* @__PURE__ */ jsx(
|
|
1474
1703
|
"button",
|
|
1475
1704
|
{
|
|
1476
1705
|
type: "button",
|
|
1706
|
+
className: "nzu-primary",
|
|
1477
1707
|
onClick: () => void handleConfirm(),
|
|
1478
1708
|
disabled: busy || props.confirmDisabled,
|
|
1479
1709
|
children: busy ? "Checking subscription\u2026" : props.confirmLabel ?? "Confirm"
|
|
1480
1710
|
}
|
|
1481
1711
|
)
|
|
1482
|
-
] })
|
|
1712
|
+
] }),
|
|
1713
|
+
/* @__PURE__ */ jsx(NozleBranding, {})
|
|
1483
1714
|
]
|
|
1484
1715
|
}
|
|
1485
1716
|
)
|
|
@@ -1527,17 +1758,181 @@ function formatBillingDate(value, locale = "en-US", timezone) {
|
|
|
1527
1758
|
}).format(new Date(value));
|
|
1528
1759
|
}
|
|
1529
1760
|
}
|
|
1761
|
+
|
|
1762
|
+
// src/components/pricing/plan-features.ts
|
|
1763
|
+
function number(value) {
|
|
1764
|
+
const parsed = Number(value);
|
|
1765
|
+
return Number.isFinite(parsed) ? parsed.toLocaleString("en-US", { maximumFractionDigits: 6 }) : String(value);
|
|
1766
|
+
}
|
|
1767
|
+
function money(amount, currency) {
|
|
1768
|
+
const parsed = Number(amount);
|
|
1769
|
+
if (!Number.isFinite(parsed)) return `${amount} ${currency}`;
|
|
1770
|
+
try {
|
|
1771
|
+
return new Intl.NumberFormat("en-US", { style: "currency", currency, maximumFractionDigits: 6 }).format(parsed);
|
|
1772
|
+
} catch {
|
|
1773
|
+
return `${currency} ${number(amount)}`;
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
function words(code) {
|
|
1777
|
+
return code.replace(/_/g, " ");
|
|
1778
|
+
}
|
|
1779
|
+
function entitlementRows(entitlement) {
|
|
1780
|
+
if (entitlement.feature_type === "config") {
|
|
1781
|
+
return (entitlement.privileges ?? []).flatMap((privilege) => {
|
|
1782
|
+
if (privilege.value === null || privilege.value === false) return [];
|
|
1783
|
+
if (privilege.value === true) return [`${entitlement.name}: ${privilege.name}`];
|
|
1784
|
+
return [`${entitlement.name}: ${privilege.value}`];
|
|
1785
|
+
});
|
|
1786
|
+
}
|
|
1787
|
+
if (entitlement.feature_type === "metered" && entitlement.usage_limit != null) {
|
|
1788
|
+
return [`${number(entitlement.usage_limit)} ${entitlement.name}`];
|
|
1789
|
+
}
|
|
1790
|
+
return [entitlement.name];
|
|
1791
|
+
}
|
|
1792
|
+
function creditGrantRow(rule) {
|
|
1793
|
+
const credits = `${number(rule.amount)} ${words(rule.credit_system_code)}`;
|
|
1794
|
+
if (rule.interval === "lifetime") return `${credits} (one time)`;
|
|
1795
|
+
const count = rule.interval_count ?? 1;
|
|
1796
|
+
return count === 1 ? `${credits} every ${rule.interval}` : `${credits} every ${count} ${rule.interval}s`;
|
|
1797
|
+
}
|
|
1798
|
+
function isZero(amount) {
|
|
1799
|
+
return amount === void 0 || Number(amount) === 0;
|
|
1800
|
+
}
|
|
1801
|
+
function rangeLabel(range, unit) {
|
|
1802
|
+
const from = number(range.from_value);
|
|
1803
|
+
return range.to_value === null ? `${from}+ ${unit}` : `${from} to ${number(range.to_value)} ${unit}`;
|
|
1804
|
+
}
|
|
1805
|
+
function rangeRows(ranges, unit, currency) {
|
|
1806
|
+
return ranges.map((range) => {
|
|
1807
|
+
const parts = [];
|
|
1808
|
+
if (!isZero(range.flat_amount)) parts.push(`${money(range.flat_amount, currency)} flat`);
|
|
1809
|
+
if (!isZero(range.per_unit_amount)) parts.push(`${money(range.per_unit_amount, currency)} each`);
|
|
1810
|
+
if (!isZero(range.rate)) parts.push(`${number(range.rate)}%`);
|
|
1811
|
+
return `${rangeLabel(range, unit)}: ${parts.length ? parts.join(" + ") : "free"}`;
|
|
1812
|
+
});
|
|
1813
|
+
}
|
|
1814
|
+
function chargeRows(charge, currency) {
|
|
1815
|
+
const properties = charge.properties ?? {};
|
|
1816
|
+
const unit = charge.feature_code ? words(charge.feature_code) : "unit";
|
|
1817
|
+
switch (charge.charge_model) {
|
|
1818
|
+
case "standard":
|
|
1819
|
+
return properties.amount === void 0 ? [] : [`${money(properties.amount, currency)} per ${unit}`];
|
|
1820
|
+
case "package": {
|
|
1821
|
+
if (properties.amount === void 0 || !properties.package_size) return [];
|
|
1822
|
+
const free = properties.free_units ? `, first ${number(properties.free_units)} free` : "";
|
|
1823
|
+
return [`${money(properties.amount, currency)} per ${number(properties.package_size)} ${unit}${free}`];
|
|
1824
|
+
}
|
|
1825
|
+
case "graduated":
|
|
1826
|
+
return rangeRows(properties.graduated_ranges ?? [], unit, currency);
|
|
1827
|
+
case "volume":
|
|
1828
|
+
return rangeRows(properties.volume_ranges ?? [], unit, currency);
|
|
1829
|
+
case "graduated_percentage":
|
|
1830
|
+
return rangeRows(properties.graduated_percentage_ranges ?? [], unit, currency);
|
|
1831
|
+
case "percentage": {
|
|
1832
|
+
if (properties.rate === void 0) return [];
|
|
1833
|
+
const fixed = isZero(properties.fixed_amount) ? "" : ` + ${money(properties.fixed_amount, currency)} per ${unit}`;
|
|
1834
|
+
return [`${number(properties.rate)}% of each ${unit}${fixed}`];
|
|
1835
|
+
}
|
|
1836
|
+
default:
|
|
1837
|
+
return [];
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
function planFeatures(plan) {
|
|
1841
|
+
const rows = (plan.entitlements ?? []).flatMap(entitlementRows);
|
|
1842
|
+
rows.push(...(plan.credit_grant_rules ?? []).map(creditGrantRow));
|
|
1843
|
+
for (const charge of plan.charges ?? []) rows.push(...chargeRows(charge, plan.amount_currency));
|
|
1844
|
+
return rows;
|
|
1845
|
+
}
|
|
1846
|
+
var INTERVAL_LABELS = {
|
|
1847
|
+
weekly: "week",
|
|
1848
|
+
monthly: "month",
|
|
1849
|
+
quarterly: "quarter",
|
|
1850
|
+
semiannual: "6 months",
|
|
1851
|
+
yearly: "year"
|
|
1852
|
+
};
|
|
1853
|
+
function intervalLabel(interval) {
|
|
1854
|
+
return INTERVAL_LABELS[interval] ?? interval;
|
|
1855
|
+
}
|
|
1856
|
+
function formatPlanPrice(amountCents, currency) {
|
|
1857
|
+
const amount = amountCents / 100;
|
|
1858
|
+
try {
|
|
1859
|
+
return new Intl.NumberFormat("en-US", {
|
|
1860
|
+
style: "currency",
|
|
1861
|
+
currency,
|
|
1862
|
+
minimumFractionDigits: Number.isInteger(amount) ? 0 : 2,
|
|
1863
|
+
maximumFractionDigits: 2
|
|
1864
|
+
}).format(amount);
|
|
1865
|
+
} catch {
|
|
1866
|
+
return `${currency} ${amount}`;
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
function entitlementValue(entitlement) {
|
|
1870
|
+
if (entitlement.feature_type === "config") {
|
|
1871
|
+
const values = (entitlement.privileges ?? []).filter((privilege) => privilege.value !== null && privilege.value !== false).map((privilege) => privilege.value === true ? privilege.name : String(privilege.value));
|
|
1872
|
+
return values.length ? values.join(", ") : false;
|
|
1873
|
+
}
|
|
1874
|
+
if (entitlement.feature_type === "metered" && entitlement.usage_limit != null) {
|
|
1875
|
+
return number(entitlement.usage_limit);
|
|
1876
|
+
}
|
|
1877
|
+
return true;
|
|
1878
|
+
}
|
|
1879
|
+
function comparisonRows(plans) {
|
|
1880
|
+
const rows = /* @__PURE__ */ new Map();
|
|
1881
|
+
const row = (key, label) => {
|
|
1882
|
+
let existing = rows.get(key);
|
|
1883
|
+
if (!existing) {
|
|
1884
|
+
existing = { key, label, values: {} };
|
|
1885
|
+
rows.set(key, existing);
|
|
1886
|
+
}
|
|
1887
|
+
return existing;
|
|
1888
|
+
};
|
|
1889
|
+
for (const plan of plans) {
|
|
1890
|
+
for (const entitlement of plan.entitlements ?? []) {
|
|
1891
|
+
row(`entitlement:${entitlement.code}`, entitlement.name).values[plan.code] = entitlementValue(entitlement);
|
|
1892
|
+
}
|
|
1893
|
+
for (const rule of plan.credit_grant_rules ?? []) {
|
|
1894
|
+
row(`credit:${rule.credit_system_code}`, words(rule.credit_system_code)).values[plan.code] = creditGrantRow(rule);
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
const all = [...rows.values()];
|
|
1898
|
+
for (const item of all) {
|
|
1899
|
+
for (const plan of plans) item.values[plan.code] ??= false;
|
|
1900
|
+
}
|
|
1901
|
+
return all;
|
|
1902
|
+
}
|
|
1903
|
+
function sortPlansByPrice(plans) {
|
|
1904
|
+
const currencyRank = /* @__PURE__ */ new Map();
|
|
1905
|
+
for (const plan of plans) {
|
|
1906
|
+
if (!currencyRank.has(plan.amount_currency)) currencyRank.set(plan.amount_currency, currencyRank.size);
|
|
1907
|
+
}
|
|
1908
|
+
return plans.map((plan, index) => ({ plan, index })).sort(
|
|
1909
|
+
(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
|
|
1910
|
+
).map(({ plan }) => plan);
|
|
1911
|
+
}
|
|
1530
1912
|
var styles3 = `
|
|
1531
|
-
.nozle-plan-change{font
|
|
1532
|
-
.nozle-plan-change button{font
|
|
1913
|
+
.nozle-plan-change{font:${T.fontBody};color:${T.foreground};margin-top:12px}
|
|
1914
|
+
.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
1915
|
.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
|
|
1916
|
+
.nozle-plan-change :focus-visible{outline:2px solid ${T.primary};outline-offset:3px}
|
|
1917
|
+
.nozle-plan-change [role=alert]{color:${T.dangerText}}
|
|
1536
1918
|
.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
|
|
1919
|
+
.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
1920
|
.nozle-upgrade-modal dl{display:block;margin:16px 0;padding:0}
|
|
1539
1921
|
.nozle-upgrade-modal dl div{display:flex;justify-content:space-between;gap:16px;margin:12px 0}
|
|
1922
|
+
.nozle-upgrade-modal dt{font:${T.fontMeta};color:${T.mutedForeground}}
|
|
1540
1923
|
.nozle-upgrade-modal dd{margin:0;text-align:right}
|
|
1924
|
+
.nozle-upgrade-modal label{font:${T.fontBody};font-weight:500}
|
|
1925
|
+
.nozle-plan-change .nzpc-plans{display:grid;gap:10px}
|
|
1926
|
+
.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}}
|
|
1927
|
+
.nozle-plan-change label.nzpc-plan[data-selected]{border-color:${T.primary};background:${T.primarySoft}}
|
|
1928
|
+
.nozle-plan-change label.nzpc-plan input{flex:none;width:auto;margin:4px 0 0;padding:0;accent-color:${T.primary}}
|
|
1929
|
+
.nozle-plan-change .nzpc-plan-body{display:grid;gap:4px;min-width:0;flex:1}
|
|
1930
|
+
.nozle-plan-change .nzpc-plan-head{display:flex;justify-content:space-between;align-items:baseline;gap:8px}
|
|
1931
|
+
.nozle-plan-change .nzpc-plan-name{font:${T.fontHeading}}
|
|
1932
|
+
.nozle-plan-change .nzpc-plan-op{font:${T.labelFont};letter-spacing:${T.labelSpacing};text-transform:${T.labelTransform};color:${T.mutedForeground}}
|
|
1933
|
+
.nozle-plan-change .nzpc-plan-price{font-variant-numeric:tabular-nums}
|
|
1934
|
+
.nozle-plan-change .nzpc-plan-muted{font:${T.fontMeta};color:${T.mutedForeground};margin:0}
|
|
1935
|
+
.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
1936
|
`;
|
|
1542
1937
|
var paymentPending = (state) => ["awaiting_payment", "processing", "needs_review"].includes(
|
|
1543
1938
|
state.checkoutStatus
|
|
@@ -1564,8 +1959,10 @@ function PlanChangeFlow({
|
|
|
1564
1959
|
returnUrl,
|
|
1565
1960
|
locale = "en-US",
|
|
1566
1961
|
timezone = "UTC",
|
|
1567
|
-
nonce
|
|
1962
|
+
nonce,
|
|
1963
|
+
appearance
|
|
1568
1964
|
}) {
|
|
1965
|
+
const { root } = useAppearance(appearance);
|
|
1569
1966
|
const owner = useMemo(
|
|
1570
1967
|
() => ({ controller: new AbortController() }),
|
|
1571
1968
|
[subscriptionId, actions]
|
|
@@ -1580,6 +1977,11 @@ function PlanChangeFlow({
|
|
|
1580
1977
|
null
|
|
1581
1978
|
);
|
|
1582
1979
|
const [target, setTarget] = useState("");
|
|
1980
|
+
const billing = useOptionalBillingContext();
|
|
1981
|
+
const catalog = useQuery(
|
|
1982
|
+
{ ...catalogQuery(billing?.client), enabled: Boolean(billing) },
|
|
1983
|
+
billing?.queryClient ?? idleQueryClient
|
|
1984
|
+
);
|
|
1583
1985
|
const [quote, setQuote] = useState();
|
|
1584
1986
|
const [checkout, setCheckout] = useState(null);
|
|
1585
1987
|
const [withdrawTarget, setWithdrawTarget] = useState();
|
|
@@ -1847,7 +2249,7 @@ function PlanChangeFlow({
|
|
|
1847
2249
|
await reconcile(signal);
|
|
1848
2250
|
});
|
|
1849
2251
|
const selected = state?.eligiblePlans.find((plan) => plan.code === target);
|
|
1850
|
-
const
|
|
2252
|
+
const money3 = (amount) => formatMinorAmount(
|
|
1851
2253
|
amount,
|
|
1852
2254
|
quote?.currency ?? state?.currentPlan.currency ?? "USD",
|
|
1853
2255
|
locale
|
|
@@ -1855,7 +2257,7 @@ function PlanChangeFlow({
|
|
|
1855
2257
|
const blocked = Boolean(
|
|
1856
2258
|
state?.endingAt || state?.pendingChange || state && paymentPending(state) || state?.blockedReason || pending.current?.accepted
|
|
1857
2259
|
);
|
|
1858
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
2260
|
+
return /* @__PURE__ */ jsxs("div", { ...root("planChangeControl", void 0, "nozle-plan-change"), children: [
|
|
1859
2261
|
/* @__PURE__ */ jsx("style", { nonce, children: styles3 }),
|
|
1860
2262
|
!state ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1861
2263
|
/* @__PURE__ */ jsx("p", { role: "status", children: "Loading plan changes\u2026" }),
|
|
@@ -1953,6 +2355,7 @@ function PlanChangeFlow({
|
|
|
1953
2355
|
/* @__PURE__ */ jsxs(
|
|
1954
2356
|
UpgradeModal,
|
|
1955
2357
|
{
|
|
2358
|
+
appearance,
|
|
1956
2359
|
isOpen: mode !== null,
|
|
1957
2360
|
planCode: target,
|
|
1958
2361
|
title: mode === "withdraw" ? "Keep your current plan?" : mode === "choose" ? "Change plan" : `Confirm ${quote?.operation ?? "plan change"}`,
|
|
@@ -1961,42 +2364,55 @@ function PlanChangeFlow({
|
|
|
1961
2364
|
confirmDisabled: mode === "choose" && (!target || blocked) || mode === "confirm" && !quote,
|
|
1962
2365
|
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
2366
|
onCancel: () => setMode(null),
|
|
2367
|
+
onBack: mode === "confirm" ? () => {
|
|
2368
|
+
setMode("choose");
|
|
2369
|
+
setQuote(void 0);
|
|
2370
|
+
setFailure(void 0);
|
|
2371
|
+
} : void 0,
|
|
1964
2372
|
onSubmit: confirm,
|
|
1965
2373
|
children: [
|
|
1966
2374
|
failure && /* @__PURE__ */ jsx("p", { role: "alert", children: failure }),
|
|
1967
|
-
mode === "choose" && /* @__PURE__ */
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
2375
|
+
mode === "choose" && /* @__PURE__ */ jsx("div", { className: "nzpc-plans", role: "radiogroup", "aria-label": "Choose a plan", children: state.eligiblePlans.map((plan) => {
|
|
2376
|
+
const details = catalog.data?.plans.find((entry) => entry.code === plan.code);
|
|
2377
|
+
const features = details ? planFeatures(details) : [];
|
|
2378
|
+
return /* @__PURE__ */ jsxs(
|
|
2379
|
+
"label",
|
|
1971
2380
|
{
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
setTarget(event.target.value);
|
|
1975
|
-
setQuote(void 0);
|
|
1976
|
-
},
|
|
1977
|
-
disabled: busy,
|
|
2381
|
+
className: "nzpc-plan",
|
|
2382
|
+
"data-selected": target === plan.code ? "" : void 0,
|
|
1978
2383
|
children: [
|
|
1979
|
-
/* @__PURE__ */ jsx(
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
plan.
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
"
|
|
1995
|
-
|
|
2384
|
+
/* @__PURE__ */ jsx(
|
|
2385
|
+
"input",
|
|
2386
|
+
{
|
|
2387
|
+
type: "radio",
|
|
2388
|
+
name: `nozle-plan-${subscriptionId}`,
|
|
2389
|
+
value: plan.code,
|
|
2390
|
+
checked: target === plan.code,
|
|
2391
|
+
disabled: busy,
|
|
2392
|
+
onChange: () => {
|
|
2393
|
+
setTarget(plan.code);
|
|
2394
|
+
setQuote(void 0);
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
),
|
|
2398
|
+
/* @__PURE__ */ jsxs("span", { className: "nzpc-plan-body", children: [
|
|
2399
|
+
/* @__PURE__ */ jsxs("span", { className: "nzpc-plan-head", children: [
|
|
2400
|
+
/* @__PURE__ */ jsx("span", { className: "nzpc-plan-name", children: plan.name }),
|
|
2401
|
+
/* @__PURE__ */ jsx("span", { className: "nzpc-plan-op", children: plan.operation === "upgrade" ? "Upgrade" : "Downgrade" })
|
|
2402
|
+
] }),
|
|
2403
|
+
/* @__PURE__ */ jsxs("span", { className: "nzpc-plan-price", children: [
|
|
2404
|
+
formatMinorAmount(plan.amountCents, plan.currency, locale),
|
|
2405
|
+
" / ",
|
|
2406
|
+
plan.interval
|
|
2407
|
+
] }),
|
|
2408
|
+
details?.description && /* @__PURE__ */ jsx("p", { className: "nzpc-plan-muted", children: details.description }),
|
|
2409
|
+
features.length > 0 && /* @__PURE__ */ jsx("ul", { children: features.map((feature) => /* @__PURE__ */ jsx("li", { children: feature }, feature)) })
|
|
2410
|
+
] })
|
|
1996
2411
|
]
|
|
1997
|
-
}
|
|
1998
|
-
|
|
1999
|
-
|
|
2412
|
+
},
|
|
2413
|
+
plan.code
|
|
2414
|
+
);
|
|
2415
|
+
}) }),
|
|
2000
2416
|
mode === "withdraw" && /* @__PURE__ */ jsxs("p", { children: [
|
|
2001
2417
|
"Remove the scheduled change to ",
|
|
2002
2418
|
withdrawTarget?.name,
|
|
@@ -2025,19 +2441,19 @@ function PlanChangeFlow({
|
|
|
2025
2441
|
/* @__PURE__ */ jsxs("dl", { children: [
|
|
2026
2442
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
2027
2443
|
/* @__PURE__ */ jsx("dt", { children: "New plan charge" }),
|
|
2028
|
-
/* @__PURE__ */ jsx("dd", { children:
|
|
2444
|
+
/* @__PURE__ */ jsx("dd", { children: money3(quote.debitAmountCents) })
|
|
2029
2445
|
] }),
|
|
2030
2446
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
2031
2447
|
/* @__PURE__ */ jsx("dt", { children: "Credits applied" }),
|
|
2032
|
-
/* @__PURE__ */ jsx("dd", { children:
|
|
2448
|
+
/* @__PURE__ */ jsx("dd", { children: money3(quote.creditAmountCents) })
|
|
2033
2449
|
] }),
|
|
2034
2450
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
2035
2451
|
/* @__PURE__ */ jsx("dt", { children: "Due now" }),
|
|
2036
|
-
/* @__PURE__ */ jsx("dd", { children:
|
|
2452
|
+
/* @__PURE__ */ jsx("dd", { children: money3(quote.amountDueNowCents) })
|
|
2037
2453
|
] }),
|
|
2038
2454
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
2039
2455
|
/* @__PURE__ */ jsx("dt", { children: "Due at effective date" }),
|
|
2040
|
-
/* @__PURE__ */ jsx("dd", { children:
|
|
2456
|
+
/* @__PURE__ */ jsx("dd", { children: money3(quote.amountDueAtEffectiveCents) })
|
|
2041
2457
|
] })
|
|
2042
2458
|
] }),
|
|
2043
2459
|
/* @__PURE__ */ jsxs("p", { children: [
|
|
@@ -2056,6 +2472,19 @@ function PlanChangeFlow({
|
|
|
2056
2472
|
] })
|
|
2057
2473
|
] });
|
|
2058
2474
|
}
|
|
2475
|
+
var PortalAppearance = createContext({
|
|
2476
|
+
el: (key) => ({ className: "nozle-" + key })
|
|
2477
|
+
});
|
|
2478
|
+
function Part({
|
|
2479
|
+
as: Tag = "div",
|
|
2480
|
+
part,
|
|
2481
|
+
className,
|
|
2482
|
+
style,
|
|
2483
|
+
...rest
|
|
2484
|
+
}) {
|
|
2485
|
+
const themed = useContext(PortalAppearance).el(part, style, className);
|
|
2486
|
+
return /* @__PURE__ */ jsx(Tag, { ...rest, ...themed });
|
|
2487
|
+
}
|
|
2059
2488
|
function asError(error) {
|
|
2060
2489
|
return error instanceof BillingPortalError ? error : new BillingPortalError("request");
|
|
2061
2490
|
}
|
|
@@ -2067,8 +2496,10 @@ function BillingPortal({
|
|
|
2067
2496
|
className = "",
|
|
2068
2497
|
style,
|
|
2069
2498
|
nonce,
|
|
2070
|
-
onError
|
|
2499
|
+
onError,
|
|
2500
|
+
appearance
|
|
2071
2501
|
}) {
|
|
2502
|
+
const { root, el } = useAppearance(appearance);
|
|
2072
2503
|
const [attempt, setAttempt] = useState(0);
|
|
2073
2504
|
const [state, setState] = useState();
|
|
2074
2505
|
const errorCallback = useRef(onError);
|
|
@@ -2105,11 +2536,10 @@ function BillingPortal({
|
|
|
2105
2536
|
}, [createSession, attempt]);
|
|
2106
2537
|
const current = state?.source === createSession && state.attempt === attempt ? state : void 0;
|
|
2107
2538
|
const reconnect = () => setAttempt((value) => value + 1);
|
|
2108
|
-
return /* @__PURE__ */ jsxs(
|
|
2539
|
+
return /* @__PURE__ */ jsx(PortalAppearance.Provider, { value: { el, appearance }, children: /* @__PURE__ */ jsxs(
|
|
2109
2540
|
"div",
|
|
2110
2541
|
{
|
|
2111
|
-
|
|
2112
|
-
style,
|
|
2542
|
+
...root("billingPortal", style, `nozle-billing-portal ${className}`),
|
|
2113
2543
|
"aria-label": "Nozle billing portal",
|
|
2114
2544
|
children: [
|
|
2115
2545
|
/* @__PURE__ */ jsx("style", { nonce, children: portalStyles }),
|
|
@@ -2128,9 +2558,9 @@ function BillingPortal({
|
|
|
2128
2558
|
) : current?.error ? /* @__PURE__ */ jsx(Failure, { error: current.error, retry: reconnect }) : /* @__PURE__ */ jsx(Loading, { label: "Starting billing session\u2026" })
|
|
2129
2559
|
]
|
|
2130
2560
|
}
|
|
2131
|
-
);
|
|
2561
|
+
) });
|
|
2132
2562
|
}
|
|
2133
|
-
function
|
|
2563
|
+
function useQuery4(client, query, variables, onError, retainDuringRefresh = false) {
|
|
2134
2564
|
const [attempt, setAttempt] = useState(0);
|
|
2135
2565
|
const key = JSON.stringify(variables);
|
|
2136
2566
|
const [state, setState] = useState();
|
|
@@ -2195,12 +2625,12 @@ function Failure({
|
|
|
2195
2625
|
reconnect
|
|
2196
2626
|
}) {
|
|
2197
2627
|
const action = error.code === "unauthorized" ? reconnect ?? retry : retry;
|
|
2198
|
-
return /* @__PURE__ */ jsxs(
|
|
2628
|
+
return /* @__PURE__ */ jsxs(Part, { role: "alert", className: "nzp-error", part: "billingPortal__error", children: [
|
|
2199
2629
|
/* @__PURE__ */ jsx("span", { children: error.message }),
|
|
2200
2630
|
action && /* @__PURE__ */ jsx("button", { type: "button", onClick: action, children: error.code === "unauthorized" ? "Reconnect" : "Try again" })
|
|
2201
2631
|
] });
|
|
2202
2632
|
}
|
|
2203
|
-
function
|
|
2633
|
+
function money2(cents, currency, locale) {
|
|
2204
2634
|
const formatter = new Intl.NumberFormat(locale, {
|
|
2205
2635
|
style: "currency",
|
|
2206
2636
|
currency
|
|
@@ -2227,7 +2657,7 @@ var intervals = {
|
|
|
2227
2657
|
};
|
|
2228
2658
|
function Portal(props) {
|
|
2229
2659
|
const { client, locale, reconnect, onError } = props;
|
|
2230
|
-
const { data, error, retry } =
|
|
2660
|
+
const { data, error, retry } = useQuery4(
|
|
2231
2661
|
client,
|
|
2232
2662
|
identityQuery,
|
|
2233
2663
|
{},
|
|
@@ -2240,7 +2670,7 @@ function Portal(props) {
|
|
|
2240
2670
|
return /* @__PURE__ */ jsx(Failure, { error, retry, reconnect });
|
|
2241
2671
|
if (!data) return /* @__PURE__ */ jsx(Loading, {});
|
|
2242
2672
|
const customer = data.customerPortalUser;
|
|
2243
|
-
return /* @__PURE__ */ jsxs(
|
|
2673
|
+
return /* @__PURE__ */ jsxs(NozleBranding, { show: data.customerPortalOrganization.nozleBranding === true, children: [
|
|
2244
2674
|
/* @__PURE__ */ jsxs("header", { children: [
|
|
2245
2675
|
/* @__PURE__ */ jsxs("div", { className: "nzp-row", children: [
|
|
2246
2676
|
/* @__PURE__ */ jsx("span", { className: "nzp-muted", children: data.customerPortalOrganization.name }),
|
|
@@ -2249,7 +2679,7 @@ function Portal(props) {
|
|
|
2249
2679
|
/* @__PURE__ */ jsx("h2", { children: "Billing portal" }),
|
|
2250
2680
|
/* @__PURE__ */ jsx("p", { className: "nzp-muted", children: customer.name || [customer.firstname, customer.lastname].filter(Boolean).join(" ") || customer.email || "Your account" })
|
|
2251
2681
|
] }),
|
|
2252
|
-
notice && /* @__PURE__ */ jsx("p", { role: "status", children: notice }),
|
|
2682
|
+
notice && /* @__PURE__ */ jsx("p", { className: "nzp-notice", role: "status", children: notice }),
|
|
2253
2683
|
view.kind !== "overview" && /* @__PURE__ */ jsx("button", { type: "button", onClick: back, children: "\u2190 Back to overview" }),
|
|
2254
2684
|
view.kind === "overview" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2255
2685
|
/* @__PURE__ */ jsx(
|
|
@@ -2267,7 +2697,7 @@ function Portal(props) {
|
|
|
2267
2697
|
onWallet: (wallet) => setView({ kind: "wallet", wallet })
|
|
2268
2698
|
}
|
|
2269
2699
|
),
|
|
2270
|
-
/* @__PURE__ */ jsxs("section",
|
|
2700
|
+
/* @__PURE__ */ jsxs(Part, { as: "section", part: "billingPortal__section", "aria-label": "Billing information", children: [
|
|
2271
2701
|
/* @__PURE__ */ jsxs("div", { className: "nzp-row", children: [
|
|
2272
2702
|
/* @__PURE__ */ jsx("h3", { children: "Billing information" }),
|
|
2273
2703
|
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => setView({ kind: "edit" }), children: "Edit billing information" })
|
|
@@ -2337,8 +2767,7 @@ function Portal(props) {
|
|
|
2337
2767
|
back();
|
|
2338
2768
|
}
|
|
2339
2769
|
}
|
|
2340
|
-
)
|
|
2341
|
-
/* @__PURE__ */ jsx("footer", { children: "Powered by Nozle" })
|
|
2770
|
+
)
|
|
2342
2771
|
] });
|
|
2343
2772
|
}
|
|
2344
2773
|
function Pager({
|
|
@@ -2385,17 +2814,20 @@ function Subscriptions({
|
|
|
2385
2814
|
planChangeActions,
|
|
2386
2815
|
nonce
|
|
2387
2816
|
}) {
|
|
2817
|
+
const portalAppearance = useContext(PortalAppearance).appearance;
|
|
2388
2818
|
const [page, setPage] = useState(1);
|
|
2389
2819
|
const [updated, setUpdated] = useState({});
|
|
2390
|
-
const { data, error, retry } =
|
|
2820
|
+
const { data, error, retry } = useQuery4(client, subscriptionsQuery, { page }, onError, true);
|
|
2391
2821
|
const result = data?.customerPortalSubscriptions;
|
|
2392
2822
|
useEffect(() => setUpdated({}), [result]);
|
|
2393
|
-
return /* @__PURE__ */ jsxs("section",
|
|
2823
|
+
return /* @__PURE__ */ jsxs(Part, { as: "section", part: "billingPortal__section", "aria-label": "Subscriptions", children: [
|
|
2394
2824
|
/* @__PURE__ */ jsx("h3", { children: "Subscriptions" }),
|
|
2395
2825
|
error ? /* @__PURE__ */ jsx(Failure, { error, retry, reconnect }) : !result ? /* @__PURE__ */ jsx(Loading, {}) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2396
2826
|
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
|
-
|
|
2827
|
+
Part,
|
|
2398
2828
|
{
|
|
2829
|
+
as: "article",
|
|
2830
|
+
part: "billingPortal__card",
|
|
2399
2831
|
className: "nzp-card",
|
|
2400
2832
|
"data-subscription-id": subscription.externalId,
|
|
2401
2833
|
children: [
|
|
@@ -2404,7 +2836,7 @@ function Subscriptions({
|
|
|
2404
2836
|
/* @__PURE__ */ jsx("span", { className: "nzp-badge", children: subscription.endingAt ? "Scheduled to end" : "Active" })
|
|
2405
2837
|
] }),
|
|
2406
2838
|
/* @__PURE__ */ jsxs("div", { className: "nzp-price", children: [
|
|
2407
|
-
|
|
2839
|
+
money2(
|
|
2408
2840
|
subscription.plan.amountCents,
|
|
2409
2841
|
subscription.plan.amountCurrency,
|
|
2410
2842
|
locale
|
|
@@ -2416,25 +2848,15 @@ function Subscriptions({
|
|
|
2416
2848
|
intervals[subscription.plan.interval] || subscription.plan.interval
|
|
2417
2849
|
] })
|
|
2418
2850
|
] }),
|
|
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
|
-
}
|
|
2851
|
+
/* @__PURE__ */ jsxs("p", { className: "nzp-muted", children: [
|
|
2852
|
+
"Base price, excluding tax and usage",
|
|
2853
|
+
/* @__PURE__ */ jsx("br", {}),
|
|
2854
|
+
"Current period ends",
|
|
2855
|
+
" ",
|
|
2856
|
+
date(
|
|
2857
|
+
subscription.currentBillingPeriodEndingAt,
|
|
2858
|
+
locale,
|
|
2859
|
+
timezone
|
|
2438
2860
|
)
|
|
2439
2861
|
] }),
|
|
2440
2862
|
subscription.endingAt && /* @__PURE__ */ jsxs("p", { children: [
|
|
@@ -2447,48 +2869,60 @@ function Subscriptions({
|
|
|
2447
2869
|
" ",
|
|
2448
2870
|
subscription.nextSubscription.plan.name
|
|
2449
2871
|
] }),
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2872
|
+
/* @__PURE__ */ jsxs("div", { className: "nzp-card-actions", children: [
|
|
2873
|
+
/* @__PURE__ */ jsx(
|
|
2874
|
+
"button",
|
|
2875
|
+
{
|
|
2876
|
+
type: "button",
|
|
2877
|
+
onClick: () => onUsage(subscription),
|
|
2878
|
+
children: "View usage"
|
|
2879
|
+
}
|
|
2880
|
+
),
|
|
2881
|
+
planChangeActions && subscription.externalId && /* @__PURE__ */ jsx(
|
|
2882
|
+
PlanChangeControl,
|
|
2883
|
+
{
|
|
2884
|
+
subscriptionId: subscription.externalId,
|
|
2885
|
+
refreshKey: `${subscription.id}:${subscription.endingAt ?? ""}:${subscription.nextSubscription?.id ?? ""}:${subscription.plan.code}`,
|
|
2886
|
+
actions: planChangeActions,
|
|
2887
|
+
appearance: portalAppearance,
|
|
2888
|
+
locale,
|
|
2889
|
+
timezone,
|
|
2890
|
+
nonce,
|
|
2891
|
+
onError,
|
|
2892
|
+
onChanged: retry
|
|
2893
|
+
}
|
|
2894
|
+
),
|
|
2895
|
+
cancellationActions && subscription.externalId && /* @__PURE__ */ jsx(
|
|
2896
|
+
CancellationControl,
|
|
2897
|
+
{
|
|
2898
|
+
subscription: cancellationState(subscription),
|
|
2899
|
+
actions: cancellationActions,
|
|
2900
|
+
appearance: portalAppearance,
|
|
2901
|
+
locale,
|
|
2902
|
+
timezone,
|
|
2903
|
+
nonce,
|
|
2904
|
+
onError,
|
|
2905
|
+
onChanged: retry,
|
|
2906
|
+
refresh: async (signal) => {
|
|
2907
|
+
const result2 = await client.request(
|
|
2908
|
+
subscriptionQuery,
|
|
2909
|
+
{ id: subscription.id },
|
|
2910
|
+
signal
|
|
2911
|
+
);
|
|
2912
|
+
if (!signal.aborted && result2.customerPortalSubscription) {
|
|
2913
|
+
const fresh = result2.customerPortalSubscription;
|
|
2914
|
+
setUpdated((previous) => ({
|
|
2915
|
+
...previous,
|
|
2916
|
+
[fresh.id]: fresh
|
|
2917
|
+
}));
|
|
2918
|
+
}
|
|
2919
|
+
return result2.customerPortalSubscription ? cancellationState(
|
|
2920
|
+
result2.customerPortalSubscription
|
|
2921
|
+
) : null;
|
|
2472
2922
|
}
|
|
2473
|
-
return result2.customerPortalSubscription ? cancellationState(
|
|
2474
|
-
result2.customerPortalSubscription
|
|
2475
|
-
) : null;
|
|
2476
2923
|
}
|
|
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
|
-
)
|
|
2924
|
+
)
|
|
2925
|
+
] })
|
|
2492
2926
|
]
|
|
2493
2927
|
},
|
|
2494
2928
|
subscription.externalId || subscription.id
|
|
@@ -2522,9 +2956,9 @@ function UsageDetails({
|
|
|
2522
2956
|
subscription,
|
|
2523
2957
|
timezone
|
|
2524
2958
|
}) {
|
|
2525
|
-
const { data, error, retry } =
|
|
2959
|
+
const { data, error, retry } = useQuery4(client, usageQuery, { subscriptionId: subscription.id }, onError);
|
|
2526
2960
|
const usage = data?.customerPortalCustomerUsage;
|
|
2527
|
-
return /* @__PURE__ */ jsxs("section",
|
|
2961
|
+
return /* @__PURE__ */ jsxs(Part, { as: "section", part: "billingPortal__section", "aria-label": "Subscription usage", children: [
|
|
2528
2962
|
/* @__PURE__ */ jsxs("h3", { children: [
|
|
2529
2963
|
planName(subscription),
|
|
2530
2964
|
" \xB7 Usage"
|
|
@@ -2536,18 +2970,18 @@ function UsageDetails({
|
|
|
2536
2970
|
" ",
|
|
2537
2971
|
date(usage.toDatetime, locale, timezone)
|
|
2538
2972
|
] }),
|
|
2539
|
-
/* @__PURE__ */ jsx("p", { className: "nzp-price", children:
|
|
2973
|
+
/* @__PURE__ */ jsx("p", { className: "nzp-price", children: money2(usage.amountCents, usage.currency, locale) }),
|
|
2540
2974
|
/* @__PURE__ */ jsx("p", { className: "nzp-muted", children: "Current usage, excluding tax" }),
|
|
2541
|
-
usage.chargesUsage.length ? /* @__PURE__ */ jsx(
|
|
2975
|
+
usage.chargesUsage.length ? /* @__PURE__ */ jsx(Part, { className: "nzp-table", part: "billingPortal__table", children: /* @__PURE__ */ jsxs("table", { children: [
|
|
2542
2976
|
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
2543
2977
|
/* @__PURE__ */ jsx("th", { children: "Feature" }),
|
|
2544
|
-
/* @__PURE__ */ jsx("th", { children: "Units" }),
|
|
2545
|
-
/* @__PURE__ */ jsx("th", { children: "Amount" })
|
|
2978
|
+
/* @__PURE__ */ jsx("th", { className: "nzp-num", children: "Units" }),
|
|
2979
|
+
/* @__PURE__ */ jsx("th", { className: "nzp-num", children: "Amount" })
|
|
2546
2980
|
] }) }),
|
|
2547
2981
|
/* @__PURE__ */ jsx("tbody", { children: usage.chargesUsage.map((charge, index) => /* @__PURE__ */ jsxs("tr", { children: [
|
|
2548
2982
|
/* @__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:
|
|
2983
|
+
/* @__PURE__ */ jsx("td", { className: "nzp-num", children: charge.units }),
|
|
2984
|
+
/* @__PURE__ */ jsx("td", { className: "nzp-num", children: money2(charge.amountCents, usage.currency, locale) })
|
|
2551
2985
|
] }, `${charge.id}-${index}`)) })
|
|
2552
2986
|
] }) }) : /* @__PURE__ */ jsx("p", { className: "nzp-empty", children: "No usage recorded for this billing period." })
|
|
2553
2987
|
] })
|
|
@@ -2557,25 +2991,31 @@ function Invoices(props) {
|
|
|
2557
2991
|
const { client, locale, reconnect, onError } = props;
|
|
2558
2992
|
const [search, setSearch] = useState("");
|
|
2559
2993
|
const [filter, setFilter] = useState({ page: 1, searchTerm: "" });
|
|
2560
|
-
const { data, error, retry } =
|
|
2994
|
+
const { data, error, retry } = useQuery4(client, invoicesQuery, filter, onError);
|
|
2561
2995
|
const result = data?.customerPortalInvoices;
|
|
2562
|
-
return /* @__PURE__ */ jsxs("section",
|
|
2996
|
+
return /* @__PURE__ */ jsxs(Part, { as: "section", part: "billingPortal__section", "aria-label": "Invoice history", children: [
|
|
2563
2997
|
/* @__PURE__ */ jsx("h3", { children: "Invoice history" }),
|
|
2564
2998
|
/* @__PURE__ */ jsxs(
|
|
2565
2999
|
"form",
|
|
2566
3000
|
{
|
|
2567
3001
|
className: "nzp-actions",
|
|
3002
|
+
role: "search",
|
|
2568
3003
|
onSubmit: (event) => {
|
|
2569
3004
|
event.preventDefault();
|
|
2570
3005
|
setFilter({ page: 1, searchTerm: search.trim() });
|
|
2571
3006
|
},
|
|
2572
3007
|
children: [
|
|
2573
|
-
/* @__PURE__ */ jsxs("label", { children: [
|
|
2574
|
-
"
|
|
3008
|
+
/* @__PURE__ */ jsxs("label", { className: "nzp-search", children: [
|
|
3009
|
+
/* @__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: [
|
|
3010
|
+
/* @__PURE__ */ jsx("circle", { cx: "11", cy: "11", r: "7" }),
|
|
3011
|
+
/* @__PURE__ */ jsx("path", { d: "m20 20-3.5-3.5" })
|
|
3012
|
+
] }),
|
|
2575
3013
|
/* @__PURE__ */ jsx(
|
|
2576
3014
|
"input",
|
|
2577
3015
|
{
|
|
2578
3016
|
type: "search",
|
|
3017
|
+
"aria-label": "Search invoices",
|
|
3018
|
+
placeholder: "Search invoices",
|
|
2579
3019
|
value: search,
|
|
2580
3020
|
onChange: (event) => setSearch(event.target.value)
|
|
2581
3021
|
}
|
|
@@ -2586,18 +3026,18 @@ function Invoices(props) {
|
|
|
2586
3026
|
}
|
|
2587
3027
|
),
|
|
2588
3028
|
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(
|
|
3029
|
+
!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
3030
|
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
2591
3031
|
/* @__PURE__ */ jsx("th", { children: "Invoice" }),
|
|
2592
3032
|
/* @__PURE__ */ jsx("th", { children: "Date" }),
|
|
2593
|
-
/* @__PURE__ */ jsx("th", { children: "Amount" }),
|
|
3033
|
+
/* @__PURE__ */ jsx("th", { className: "nzp-num", children: "Amount" }),
|
|
2594
3034
|
/* @__PURE__ */ jsx("th", { children: "Status" }),
|
|
2595
3035
|
/* @__PURE__ */ jsx("th", { children: "PDF" })
|
|
2596
3036
|
] }) }),
|
|
2597
3037
|
/* @__PURE__ */ jsx("tbody", { children: result.collection.map((invoice) => /* @__PURE__ */ jsxs("tr", { children: [
|
|
2598
3038
|
/* @__PURE__ */ jsx("td", { children: invoice.number }),
|
|
2599
3039
|
/* @__PURE__ */ jsx("td", { children: date(invoice.issuingDate, locale) }),
|
|
2600
|
-
/* @__PURE__ */ jsx("td", { children:
|
|
3040
|
+
/* @__PURE__ */ jsx("td", { className: "nzp-num", children: money2(
|
|
2601
3041
|
invoice.totalAmountCents,
|
|
2602
3042
|
invoice.currency,
|
|
2603
3043
|
locale
|
|
@@ -2701,7 +3141,7 @@ function CustomerForm({
|
|
|
2701
3141
|
if (!signal.aborted) onSaved();
|
|
2702
3142
|
});
|
|
2703
3143
|
};
|
|
2704
|
-
return /* @__PURE__ */ jsxs("section",
|
|
3144
|
+
return /* @__PURE__ */ jsxs(Part, { as: "section", part: "billingPortal__section", "aria-label": "Edit billing information", children: [
|
|
2705
3145
|
/* @__PURE__ */ jsx("h3", { children: "Edit billing information" }),
|
|
2706
3146
|
/* @__PURE__ */ jsxs("form", { onSubmit: submit, children: [
|
|
2707
3147
|
/* @__PURE__ */ jsx("div", { className: "nzp-fields", children: customerFields.map(([key, label]) => /* @__PURE__ */ jsxs("label", { children: [
|
|
@@ -2743,15 +3183,15 @@ function Wallets({
|
|
|
2743
3183
|
onWallet
|
|
2744
3184
|
}) {
|
|
2745
3185
|
const [page, setPage] = useState(1);
|
|
2746
|
-
const { data, error, retry } =
|
|
3186
|
+
const { data, error, retry } = useQuery4(client, walletsQuery, { page }, onError);
|
|
2747
3187
|
const result = data?.customerPortalWallets;
|
|
2748
3188
|
if (result && !result.collection.length) return null;
|
|
2749
|
-
return /* @__PURE__ */ jsxs("section",
|
|
3189
|
+
return /* @__PURE__ */ jsxs(Part, { as: "section", part: "billingPortal__section", "aria-label": "Wallets", children: [
|
|
2750
3190
|
/* @__PURE__ */ jsx("h3", { children: "Wallets" }),
|
|
2751
3191
|
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",
|
|
3192
|
+
/* @__PURE__ */ jsx("div", { className: "nzp-stack", children: result.collection.map((wallet) => /* @__PURE__ */ jsxs(Part, { as: "article", part: "billingPortal__card", className: "nzp-card", children: [
|
|
2753
3193
|
/* @__PURE__ */ jsx("h3", { children: wallet.name || "Prepaid credits" }),
|
|
2754
|
-
/* @__PURE__ */ jsx("div", { className: "nzp-price", children:
|
|
3194
|
+
/* @__PURE__ */ jsx("div", { className: "nzp-price", children: money2(wallet.balanceCents, wallet.currency, locale) }),
|
|
2755
3195
|
/* @__PURE__ */ jsxs("div", { className: "nzp-row", children: [
|
|
2756
3196
|
/* @__PURE__ */ jsxs("p", { className: "nzp-muted", children: [
|
|
2757
3197
|
wallet.creditsBalance,
|
|
@@ -2796,7 +3236,7 @@ function WalletTopUp({
|
|
|
2796
3236
|
amountCents,
|
|
2797
3237
|
String(wallet.paidTopUpMaxAmountCents)
|
|
2798
3238
|
) <= 0);
|
|
2799
|
-
return /* @__PURE__ */ jsxs("section",
|
|
3239
|
+
return /* @__PURE__ */ jsxs(Part, { as: "section", part: "billingPortal__section", "aria-label": "Add credits", children: [
|
|
2800
3240
|
/* @__PURE__ */ jsxs("h3", { children: [
|
|
2801
3241
|
"Add credits \xB7 ",
|
|
2802
3242
|
wallet.name || "Wallet"
|
|
@@ -2833,19 +3273,19 @@ function WalletTopUp({
|
|
|
2833
3273
|
)
|
|
2834
3274
|
] }) }),
|
|
2835
3275
|
/* @__PURE__ */ jsxs("p", { children: [
|
|
2836
|
-
amountCents !== null ?
|
|
3276
|
+
amountCents !== null ? money2(amountCents, wallet.currency, locale) : "\u2014",
|
|
2837
3277
|
" ",
|
|
2838
3278
|
"excluding tax"
|
|
2839
3279
|
] }),
|
|
2840
3280
|
wallet.paidTopUpMinAmountCents != null && /* @__PURE__ */ jsxs("p", { className: "nzp-muted", children: [
|
|
2841
3281
|
"Minimum",
|
|
2842
3282
|
" ",
|
|
2843
|
-
|
|
3283
|
+
money2(wallet.paidTopUpMinAmountCents, wallet.currency, locale)
|
|
2844
3284
|
] }),
|
|
2845
3285
|
wallet.paidTopUpMaxAmountCents != null && /* @__PURE__ */ jsxs("p", { className: "nzp-muted", children: [
|
|
2846
3286
|
"Maximum",
|
|
2847
3287
|
" ",
|
|
2848
|
-
|
|
3288
|
+
money2(wallet.paidTopUpMaxAmountCents, wallet.currency, locale)
|
|
2849
3289
|
] }),
|
|
2850
3290
|
action.error && /* @__PURE__ */ jsx(Failure, { error: action.error, reconnect }),
|
|
2851
3291
|
/* @__PURE__ */ jsx("div", { className: "nzp-actions", children: /* @__PURE__ */ jsx(
|
|
@@ -2862,38 +3302,112 @@ function WalletTopUp({
|
|
|
2862
3302
|
)
|
|
2863
3303
|
] });
|
|
2864
3304
|
}
|
|
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
|
-
|
|
3305
|
+
function useBilling() {
|
|
3306
|
+
const { billingFetch, billingEndpoint, queryClient } = useBillingContext();
|
|
3307
|
+
if (!billingFetch || !billingEndpoint) {
|
|
3308
|
+
throw new Error("Billing hooks need BillingProvider billingEndpoint, a backend route served by createBillingHandler");
|
|
3309
|
+
}
|
|
3310
|
+
return { fetchRoute: billingFetch, queryClient, endpoint: billingEndpoint };
|
|
3311
|
+
}
|
|
3312
|
+
function useSubscription() {
|
|
3313
|
+
const { fetchRoute, queryClient, endpoint } = useBilling();
|
|
3314
|
+
return useQuery(
|
|
3315
|
+
{ queryKey: ["nozle", endpoint, "subscription"], queryFn: () => fetchRoute("subscription") },
|
|
3316
|
+
queryClient
|
|
3317
|
+
);
|
|
3318
|
+
}
|
|
3319
|
+
function useEntitlement(featureCode, options = {}) {
|
|
3320
|
+
const { fetchRoute, queryClient, endpoint } = useBilling();
|
|
3321
|
+
return useQuery(
|
|
3322
|
+
{
|
|
3323
|
+
queryKey: ["nozle", endpoint, "entitlement", featureCode],
|
|
3324
|
+
queryFn: () => fetchRoute("entitlement", { feature: featureCode }),
|
|
3325
|
+
enabled: Boolean(featureCode) && options.enabled !== false,
|
|
3326
|
+
staleTime: 1e4
|
|
3327
|
+
},
|
|
3328
|
+
queryClient
|
|
3329
|
+
);
|
|
3330
|
+
}
|
|
3331
|
+
function useCreditBalance(creditSystemCode) {
|
|
3332
|
+
const { fetchRoute, queryClient, endpoint } = useBilling();
|
|
3333
|
+
return useQuery(
|
|
3334
|
+
{
|
|
3335
|
+
queryKey: ["nozle", endpoint, "credits"],
|
|
3336
|
+
queryFn: () => fetchRoute("credits"),
|
|
3337
|
+
select: (data) => {
|
|
3338
|
+
const balances = data.balances ?? [];
|
|
3339
|
+
if (creditSystemCode) return balances.find((b) => b.credit_system === creditSystemCode) ?? null;
|
|
3340
|
+
return balances.find((b) => Number(b.available) > 0) ?? balances[0] ?? null;
|
|
2888
3341
|
}
|
|
2889
|
-
}
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
3342
|
+
},
|
|
3343
|
+
queryClient
|
|
3344
|
+
);
|
|
3345
|
+
}
|
|
3346
|
+
function useCreditHistory(options = {}) {
|
|
3347
|
+
const { fetchRoute, queryClient, endpoint } = useBilling();
|
|
3348
|
+
const query = useInfiniteQuery(
|
|
3349
|
+
{
|
|
3350
|
+
queryKey: ["nozle", endpoint, "credit-history", options.creditSystemCode, options.limit],
|
|
3351
|
+
queryFn: ({ pageParam }) => fetchRoute("credit-history", {
|
|
3352
|
+
credit_system: options.creditSystemCode,
|
|
3353
|
+
limit: options.limit,
|
|
3354
|
+
cursor: pageParam
|
|
3355
|
+
}),
|
|
3356
|
+
initialPageParam: void 0,
|
|
3357
|
+
getNextPageParam: (page) => page.next_cursor ?? void 0
|
|
3358
|
+
},
|
|
3359
|
+
queryClient
|
|
3360
|
+
);
|
|
3361
|
+
return { ...query, operations: query.data?.pages.flatMap((page) => page.operations) ?? [] };
|
|
3362
|
+
}
|
|
3363
|
+
function useUsage(options = {}) {
|
|
3364
|
+
const { fetchRoute, queryClient, endpoint } = useBilling();
|
|
3365
|
+
const period = options.period ?? "current";
|
|
3366
|
+
return useQuery(
|
|
3367
|
+
{
|
|
3368
|
+
queryKey: ["nozle", endpoint, "usage", period, options.periods],
|
|
3369
|
+
queryFn: () => fetchRoute("usage", {
|
|
3370
|
+
period,
|
|
3371
|
+
periods: options.periods
|
|
3372
|
+
}),
|
|
3373
|
+
select: (data) => period === "past" ? data.periods ?? [] : data.usage ?? null
|
|
3374
|
+
},
|
|
3375
|
+
queryClient
|
|
3376
|
+
);
|
|
3377
|
+
}
|
|
3378
|
+
function useInvoices(options = {}) {
|
|
3379
|
+
const { fetchRoute, queryClient, endpoint } = useBilling();
|
|
3380
|
+
const page = options.page ?? 1;
|
|
3381
|
+
return useQuery(
|
|
3382
|
+
{
|
|
3383
|
+
queryKey: ["nozle", endpoint, "invoices", page],
|
|
3384
|
+
queryFn: () => fetchRoute("invoices", { page })
|
|
3385
|
+
},
|
|
3386
|
+
queryClient
|
|
3387
|
+
);
|
|
3388
|
+
}
|
|
3389
|
+
function useInvoiceDownload() {
|
|
3390
|
+
const { fetchRoute } = useBilling();
|
|
3391
|
+
return useCallback(
|
|
3392
|
+
async (invoiceId) => (await fetchRoute("invoice-download", { id: invoiceId })).url,
|
|
3393
|
+
[fetchRoute]
|
|
3394
|
+
);
|
|
3395
|
+
}
|
|
3396
|
+
function useUpgradePreview(planCode) {
|
|
3397
|
+
const { fetchRoute, queryClient, endpoint } = useBilling();
|
|
3398
|
+
return useQuery(
|
|
3399
|
+
{
|
|
3400
|
+
queryKey: ["nozle", endpoint, "preview", planCode],
|
|
3401
|
+
queryFn: () => fetchRoute("preview", { plan: planCode }),
|
|
3402
|
+
enabled: Boolean(planCode),
|
|
3403
|
+
staleTime: 0
|
|
3404
|
+
},
|
|
3405
|
+
queryClient
|
|
3406
|
+
);
|
|
3407
|
+
}
|
|
3408
|
+
function useBillingRefresh() {
|
|
3409
|
+
const { queryClient } = useBillingContext();
|
|
3410
|
+
return useCallback(() => queryClient.invalidateQueries({ queryKey: ["nozle"] }), [queryClient]);
|
|
2897
3411
|
}
|
|
2898
3412
|
function useCheckout2() {
|
|
2899
3413
|
const { createCheckout } = useBillingContext();
|
|
@@ -2941,33 +3455,126 @@ function useCheckout2() {
|
|
|
2941
3455
|
);
|
|
2942
3456
|
return { fetchClientSecret, checkout, isLoading, error };
|
|
2943
3457
|
}
|
|
3458
|
+
function PanelPlanCard({
|
|
3459
|
+
frame,
|
|
3460
|
+
el,
|
|
3461
|
+
name,
|
|
3462
|
+
description,
|
|
3463
|
+
price,
|
|
3464
|
+
interval,
|
|
3465
|
+
features,
|
|
3466
|
+
isCurrent,
|
|
3467
|
+
isHighlighted,
|
|
3468
|
+
filled: filledStyle,
|
|
3469
|
+
highlightLabel,
|
|
3470
|
+
children
|
|
3471
|
+
}) {
|
|
3472
|
+
const filled = isHighlighted && filledStyle;
|
|
3473
|
+
const text2 = filled ? T.highlightForeground : T.foreground;
|
|
3474
|
+
const muted5 = filled ? `color-mix(in srgb, ${T.highlightForeground} 75%, ${T.highlight})` : T.mutedForeground;
|
|
3475
|
+
const edge = isHighlighted ? T.highlight : isCurrent ? T.foreground : T.border;
|
|
3476
|
+
const label = isCurrent ? "Active" : isHighlighted ? highlightLabel : null;
|
|
3477
|
+
return /* @__PURE__ */ jsxs(
|
|
3478
|
+
"div",
|
|
3479
|
+
{
|
|
3480
|
+
...frame({
|
|
3481
|
+
display: "flex",
|
|
3482
|
+
flexDirection: "column",
|
|
3483
|
+
overflow: "hidden",
|
|
3484
|
+
...text.body,
|
|
3485
|
+
background: filled ? T.highlight : T.card,
|
|
3486
|
+
color: text2,
|
|
3487
|
+
border: border(edge),
|
|
3488
|
+
borderRadius: T.radius,
|
|
3489
|
+
boxShadow: T.shadow
|
|
3490
|
+
}),
|
|
3491
|
+
children: [
|
|
3492
|
+
/* @__PURE__ */ jsxs(
|
|
3493
|
+
"div",
|
|
3494
|
+
{
|
|
3495
|
+
...el("planCard__header", {
|
|
3496
|
+
display: "flex",
|
|
3497
|
+
alignItems: "center",
|
|
3498
|
+
justifyContent: "space-between",
|
|
3499
|
+
gap: "0.75rem",
|
|
3500
|
+
padding: "0.875rem 1.25rem",
|
|
3501
|
+
borderBottom: border(filled ? `color-mix(in srgb, ${T.highlightForeground} 30%, ${T.highlight})` : T.border)
|
|
3502
|
+
}),
|
|
3503
|
+
children: [
|
|
3504
|
+
/* @__PURE__ */ jsx("h3", { ...el("planCard__name", { ...text.heading, color: text2, margin: 0 }), children: name }),
|
|
3505
|
+
label && /* @__PURE__ */ jsx(
|
|
3506
|
+
"span",
|
|
3507
|
+
{
|
|
3508
|
+
...el("planCard__badge", {
|
|
3509
|
+
...text.label,
|
|
3510
|
+
whiteSpace: "nowrap",
|
|
3511
|
+
// The current plan's label takes the colour of its button.
|
|
3512
|
+
color: filled ? text2 : isCurrent ? T.mutedForeground : T.highlight
|
|
3513
|
+
}),
|
|
3514
|
+
children: label
|
|
3515
|
+
}
|
|
3516
|
+
)
|
|
3517
|
+
]
|
|
3518
|
+
}
|
|
3519
|
+
),
|
|
3520
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "1rem", padding: "1.25rem", flex: 1 }, children: [
|
|
3521
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
3522
|
+
/* @__PURE__ */ jsxs("p", { ...el("planCard__price", { ...text.metric, color: text2, margin: 0 }), children: [
|
|
3523
|
+
price,
|
|
3524
|
+
interval && /* @__PURE__ */ jsxs("span", { style: { ...text.meta, color: muted5 }, children: [
|
|
3525
|
+
" / ",
|
|
3526
|
+
interval
|
|
3527
|
+
] })
|
|
3528
|
+
] }),
|
|
3529
|
+
description && /* @__PURE__ */ jsx("p", { ...el("planCard__description", { ...text.secondary, color: muted5, margin: "0.25rem 0 0" }), children: description })
|
|
3530
|
+
] }),
|
|
3531
|
+
/* @__PURE__ */ jsx(
|
|
3532
|
+
"ul",
|
|
3533
|
+
{
|
|
3534
|
+
...el("planCard__features", {
|
|
3535
|
+
listStyle: "none",
|
|
3536
|
+
margin: 0,
|
|
3537
|
+
padding: 0,
|
|
3538
|
+
display: "flex",
|
|
3539
|
+
flexDirection: "column",
|
|
3540
|
+
gap: "0.375rem",
|
|
3541
|
+
flex: 1,
|
|
3542
|
+
...text.secondary,
|
|
3543
|
+
color: muted5
|
|
3544
|
+
}),
|
|
3545
|
+
children: features.map((feature) => /* @__PURE__ */ jsx("li", { ...el("planCard__feature"), children: feature }, feature))
|
|
3546
|
+
}
|
|
3547
|
+
),
|
|
3548
|
+
children
|
|
3549
|
+
] })
|
|
3550
|
+
]
|
|
3551
|
+
}
|
|
3552
|
+
);
|
|
3553
|
+
}
|
|
3554
|
+
function featureLines(override, plan, index) {
|
|
3555
|
+
const given = Array.isArray(override) ? override[index] : override?.[plan.code];
|
|
3556
|
+
return given ?? planFeatures(plan);
|
|
3557
|
+
}
|
|
2944
3558
|
var SYMBOLS = { USD: "$", EUR: "\u20AC", GBP: "\xA3", JPY: "\xA5" };
|
|
2945
3559
|
function fmtPrice(cents, cur) {
|
|
2946
3560
|
const s = SYMBOLS[cur] ?? cur + " ";
|
|
2947
3561
|
const val = cents % 100 === 0 ? String(cents / 100) : (cents / 100).toFixed(2);
|
|
2948
3562
|
return `${s}${val}`;
|
|
2949
3563
|
}
|
|
2950
|
-
function isEnterprise(p) {
|
|
2951
|
-
return /enterprise|custom/i.test(p.name + p.code);
|
|
2952
|
-
}
|
|
2953
|
-
function v(name, fb) {
|
|
2954
|
-
return `var(${name}, ${fb})`;
|
|
2955
|
-
}
|
|
2956
3564
|
var C = {
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
hlFg: v("--nozle-primary-foreground", "#ffffff")
|
|
3565
|
+
cardBg: T.card,
|
|
3566
|
+
hl: T.primary,
|
|
3567
|
+
border: T.border,
|
|
3568
|
+
radius: T.radius,
|
|
3569
|
+
text: T.foreground,
|
|
3570
|
+
muted: T.mutedForeground,
|
|
3571
|
+
mutedBg: T.muted,
|
|
3572
|
+
hlFg: T.primaryForeground
|
|
2966
3573
|
};
|
|
2967
3574
|
function Check() {
|
|
2968
3575
|
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
3576
|
}
|
|
2970
|
-
function Toggle({ isAnnual, onChange }) {
|
|
3577
|
+
function Toggle({ isAnnual, onChange, el }) {
|
|
2971
3578
|
const pill = {
|
|
2972
3579
|
display: "inline-flex",
|
|
2973
3580
|
borderRadius: "9999px",
|
|
@@ -2980,24 +3587,15 @@ function Toggle({ isAnnual, onChange }) {
|
|
|
2980
3587
|
borderRadius: "9999px",
|
|
2981
3588
|
border: "none",
|
|
2982
3589
|
background: active ? C.cardBg : "transparent",
|
|
3590
|
+
...text.action,
|
|
2983
3591
|
color: active ? C.text : C.muted,
|
|
2984
|
-
fontWeight: active ? 600 : 400,
|
|
2985
|
-
fontSize: "0.875rem",
|
|
2986
3592
|
cursor: "pointer",
|
|
2987
|
-
boxShadow: active ?
|
|
3593
|
+
boxShadow: active ? T.shadow : "none",
|
|
2988
3594
|
transition: "all 0.15s ease"
|
|
2989
3595
|
});
|
|
2990
|
-
return /* @__PURE__ */ jsx("div", { style: { display: "flex", justifyContent: "center", marginBottom: "2.5rem" }, children: /* @__PURE__ */ jsxs("div", {
|
|
2991
|
-
/* @__PURE__ */ jsx("button", { onClick: () => onChange(false),
|
|
2992
|
-
/* @__PURE__ */
|
|
2993
|
-
"Annual",
|
|
2994
|
-
/* @__PURE__ */ jsx("span", { style: {
|
|
2995
|
-
marginLeft: "6px",
|
|
2996
|
-
fontSize: "0.7rem",
|
|
2997
|
-
fontWeight: 600,
|
|
2998
|
-
color: isAnnual ? C.hl : C.muted
|
|
2999
|
-
}, children: "Save 20%" })
|
|
3000
|
-
] })
|
|
3596
|
+
return /* @__PURE__ */ jsx("div", { style: { display: "flex", justifyContent: "center", marginBottom: "2.5rem" }, children: /* @__PURE__ */ jsxs("div", { ...el("pricingTable__toggle", pill), children: [
|
|
3597
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => onChange(false), ...el("pricingTable__toggleButton", tab(!isAnnual)), "aria-pressed": !isAnnual, children: "Monthly" }),
|
|
3598
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => onChange(true), ...el("pricingTable__toggleButton", tab(isAnnual)), "aria-pressed": isAnnual, children: "Annual" })
|
|
3001
3599
|
] }) });
|
|
3002
3600
|
}
|
|
3003
3601
|
function Card({
|
|
@@ -3005,13 +3603,22 @@ function Card({
|
|
|
3005
3603
|
features,
|
|
3006
3604
|
isCurrent,
|
|
3007
3605
|
isHighlighted,
|
|
3606
|
+
filled: filledStyle,
|
|
3607
|
+
highlightLabel,
|
|
3008
3608
|
isEnt,
|
|
3009
3609
|
enterpriseEmail,
|
|
3010
|
-
onSelect
|
|
3610
|
+
onSelect,
|
|
3611
|
+
el,
|
|
3612
|
+
panel,
|
|
3613
|
+
isUpgrade
|
|
3011
3614
|
}) {
|
|
3012
3615
|
const [hover, setHover] = useState(false);
|
|
3013
|
-
const
|
|
3014
|
-
const
|
|
3616
|
+
const filled = isHighlighted && filledStyle;
|
|
3617
|
+
const text2 = filled ? T.highlightForeground : C.text;
|
|
3618
|
+
const muted5 = filled ? `color-mix(in srgb, ${T.highlightForeground} 75%, ${T.highlight})` : C.muted;
|
|
3619
|
+
const accent = filled ? T.highlightForeground : C.hl;
|
|
3620
|
+
const badgeLabel = isCurrent ? "Current Plan" : highlightLabel;
|
|
3621
|
+
const interval = `/${intervalLabel(plan.interval)}`;
|
|
3015
3622
|
const handleClick = () => {
|
|
3016
3623
|
if (isCurrent) return;
|
|
3017
3624
|
if (isEnt && enterpriseEmail) {
|
|
@@ -3020,75 +3627,97 @@ function Card({
|
|
|
3020
3627
|
}
|
|
3021
3628
|
onSelect?.();
|
|
3022
3629
|
};
|
|
3630
|
+
const emphasised = isUpgrade ?? isHighlighted;
|
|
3631
|
+
const cta = onSelect && /* @__PURE__ */ jsx(
|
|
3632
|
+
CTAButton,
|
|
3633
|
+
{
|
|
3634
|
+
onClick: handleClick,
|
|
3635
|
+
disabled: isCurrent,
|
|
3636
|
+
variant: isCurrent ? "current" : isEnt ? "outline" : !emphasised ? "secondary" : filled ? "inverse" : isHighlighted ? "highlight" : "primary",
|
|
3637
|
+
el,
|
|
3638
|
+
children: isCurrent ? "Current Plan" : isEnt ? "Contact Sales" : `Get ${plan.name}`
|
|
3639
|
+
}
|
|
3640
|
+
);
|
|
3641
|
+
if (panel) {
|
|
3642
|
+
return /* @__PURE__ */ jsx(
|
|
3643
|
+
PanelPlanCard,
|
|
3644
|
+
{
|
|
3645
|
+
frame: (style) => el("planCard", style),
|
|
3646
|
+
el,
|
|
3647
|
+
name: plan.name,
|
|
3648
|
+
description: plan.description,
|
|
3649
|
+
price: isEnt ? "Custom" : fmtPrice(plan.amount_cents, plan.amount_currency),
|
|
3650
|
+
interval: isEnt ? void 0 : intervalLabel(plan.interval),
|
|
3651
|
+
features: features ?? [],
|
|
3652
|
+
isCurrent,
|
|
3653
|
+
isHighlighted,
|
|
3654
|
+
filled: filledStyle,
|
|
3655
|
+
highlightLabel,
|
|
3656
|
+
children: cta
|
|
3657
|
+
}
|
|
3658
|
+
);
|
|
3659
|
+
}
|
|
3023
3660
|
return /* @__PURE__ */ jsxs(
|
|
3024
3661
|
"div",
|
|
3025
3662
|
{
|
|
3026
3663
|
onMouseEnter: () => setHover(true),
|
|
3027
3664
|
onMouseLeave: () => setHover(false),
|
|
3028
|
-
|
|
3665
|
+
...el("planCard", {
|
|
3029
3666
|
position: "relative",
|
|
3030
3667
|
display: "flex",
|
|
3031
3668
|
flexDirection: "column",
|
|
3032
|
-
background: C.cardBg,
|
|
3033
|
-
|
|
3034
|
-
|
|
3669
|
+
background: filled ? T.highlight : C.cardBg,
|
|
3670
|
+
color: text2,
|
|
3671
|
+
border: border(isHighlighted ? T.highlight : C.border),
|
|
3672
|
+
borderTopWidth: isHighlighted && !filled ? `calc(${T.borderWidth} + 2px)` : T.borderWidth,
|
|
3035
3673
|
borderRadius: C.radius,
|
|
3036
3674
|
padding: "2rem",
|
|
3037
3675
|
transition: "box-shadow 0.2s ease, transform 0.2s ease",
|
|
3038
|
-
boxShadow: shadow,
|
|
3676
|
+
boxShadow: T.shadow,
|
|
3039
3677
|
transform: hover && !isCurrent ? "translateY(-2px)" : "none"
|
|
3040
|
-
},
|
|
3678
|
+
}),
|
|
3041
3679
|
children: [
|
|
3042
|
-
isHighlighted && /* @__PURE__ */ jsx("div", {
|
|
3680
|
+
isHighlighted && badgeLabel && /* @__PURE__ */ jsx("div", { ...el("planCard__badge", {
|
|
3043
3681
|
position: "absolute",
|
|
3044
3682
|
top: "-1px",
|
|
3045
3683
|
left: "50%",
|
|
3046
3684
|
transform: "translate(-50%, -50%)",
|
|
3047
|
-
background:
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3685
|
+
background: filled ? T.highlightForeground : T.highlight,
|
|
3686
|
+
border: filled ? border(T.highlight) : void 0,
|
|
3687
|
+
...text.label,
|
|
3688
|
+
color: filled ? T.highlight : T.highlightForeground,
|
|
3051
3689
|
padding: "4px 12px",
|
|
3052
3690
|
borderRadius: "9999px",
|
|
3053
|
-
letterSpacing: "0.025em",
|
|
3054
|
-
textTransform: "uppercase",
|
|
3055
3691
|
whiteSpace: "nowrap"
|
|
3056
|
-
}, children:
|
|
3057
|
-
isCurrent && !isHighlighted && /* @__PURE__ */ jsx("div", {
|
|
3692
|
+
}), children: badgeLabel }),
|
|
3693
|
+
isCurrent && !isHighlighted && /* @__PURE__ */ jsx("div", { ...el("planCard__badge", {
|
|
3058
3694
|
position: "absolute",
|
|
3059
3695
|
top: "0",
|
|
3060
3696
|
left: "50%",
|
|
3061
3697
|
transform: "translate(-50%, -50%)",
|
|
3062
3698
|
background: C.mutedBg,
|
|
3063
|
-
|
|
3064
|
-
fontSize: "0.7rem",
|
|
3065
|
-
fontWeight: 700,
|
|
3699
|
+
...text.label,
|
|
3066
3700
|
padding: "4px 12px",
|
|
3067
3701
|
borderRadius: "9999px",
|
|
3068
|
-
letterSpacing: "0.025em",
|
|
3069
|
-
textTransform: "uppercase",
|
|
3070
3702
|
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 })
|
|
3703
|
+
}), children: "Current Plan" }),
|
|
3704
|
+
/* @__PURE__ */ jsxs("div", { style: { marginBottom: "1.25rem", marginTop: isHighlighted && badgeLabel || isCurrent ? "0.5rem" : 0 }, children: [
|
|
3705
|
+
/* @__PURE__ */ jsx("h3", { ...el("planCard__name", {
|
|
3706
|
+
...text.heading,
|
|
3707
|
+
color: text2,
|
|
3708
|
+
margin: 0
|
|
3709
|
+
}), children: plan.name }),
|
|
3710
|
+
plan.description && /* @__PURE__ */ jsx("p", { ...el("planCard__description", {
|
|
3711
|
+
...text.secondary,
|
|
3712
|
+
color: muted5,
|
|
3713
|
+
margin: "6px 0 0"
|
|
3714
|
+
}), children: plan.description })
|
|
3086
3715
|
] }),
|
|
3087
|
-
/* @__PURE__ */ jsx("div", {
|
|
3088
|
-
/* @__PURE__ */ jsx("span", { style: {
|
|
3089
|
-
/* @__PURE__ */ jsx("span", { style: {
|
|
3716
|
+
/* @__PURE__ */ jsx("div", { ...el("planCard__price", { marginBottom: "1.5rem" }), children: isEnt ? /* @__PURE__ */ jsx("span", { style: { ...text.metric, color: text2 }, children: "Custom" }) : /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "baseline", gap: "2px" }, children: [
|
|
3717
|
+
/* @__PURE__ */ jsx("span", { style: { ...text.metric, color: text2 }, children: fmtPrice(plan.amount_cents, plan.amount_currency) }),
|
|
3718
|
+
/* @__PURE__ */ jsx("span", { style: { ...text.meta, color: muted5, marginLeft: "4px" }, children: interval })
|
|
3090
3719
|
] }) }),
|
|
3091
|
-
features && features.length > 0 && /* @__PURE__ */ jsx("ul", {
|
|
3720
|
+
features && features.length > 0 && /* @__PURE__ */ jsx("ul", { ...el("planCard__features", {
|
|
3092
3721
|
listStyle: "none",
|
|
3093
3722
|
padding: 0,
|
|
3094
3723
|
margin: "0 0 2rem",
|
|
@@ -3096,27 +3725,18 @@ function Card({
|
|
|
3096
3725
|
flexDirection: "column",
|
|
3097
3726
|
gap: "0.75rem",
|
|
3098
3727
|
flex: 1
|
|
3099
|
-
}, children: features.map((feat) => /* @__PURE__ */ jsxs("li", {
|
|
3728
|
+
}), children: features.map((feat) => /* @__PURE__ */ jsxs("li", { ...el("planCard__feature", {
|
|
3100
3729
|
display: "flex",
|
|
3101
3730
|
alignItems: "flex-start",
|
|
3102
3731
|
gap: "10px",
|
|
3103
|
-
|
|
3104
|
-
color:
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
/* @__PURE__ */ jsx("span", { style: { color: C.hl, marginTop: "2px" }, children: /* @__PURE__ */ jsx(Check, {}) }),
|
|
3732
|
+
...text.body,
|
|
3733
|
+
color: text2
|
|
3734
|
+
}), children: [
|
|
3735
|
+
/* @__PURE__ */ jsx("span", { style: { color: accent, marginTop: "2px" }, children: /* @__PURE__ */ jsx(Check, {}) }),
|
|
3108
3736
|
/* @__PURE__ */ jsx("span", { children: feat })
|
|
3109
3737
|
] }, feat)) }),
|
|
3110
3738
|
(!features || features.length === 0) && /* @__PURE__ */ jsx("div", { style: { flex: 1 } }),
|
|
3111
|
-
|
|
3112
|
-
CTAButton,
|
|
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
|
-
)
|
|
3739
|
+
cta
|
|
3120
3740
|
]
|
|
3121
3741
|
}
|
|
3122
3742
|
);
|
|
@@ -3125,75 +3745,53 @@ function CTAButton({
|
|
|
3125
3745
|
children,
|
|
3126
3746
|
onClick,
|
|
3127
3747
|
disabled,
|
|
3128
|
-
variant
|
|
3748
|
+
variant,
|
|
3749
|
+
el
|
|
3129
3750
|
}) {
|
|
3130
3751
|
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
|
-
};
|
|
3752
|
+
const look = variant === "current" ? { ...buttonStyle("secondary"), background: C.mutedBg, color: C.muted, 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
3753
|
return /* @__PURE__ */ jsx(
|
|
3163
3754
|
"button",
|
|
3164
3755
|
{
|
|
3756
|
+
type: "button",
|
|
3165
3757
|
onClick: disabled ? void 0 : onClick,
|
|
3166
3758
|
disabled,
|
|
3167
3759
|
onMouseEnter: () => setHover(true),
|
|
3168
3760
|
onMouseLeave: () => setHover(false),
|
|
3169
|
-
|
|
3761
|
+
...el("planCard__cta", {
|
|
3762
|
+
...look,
|
|
3763
|
+
width: "100%",
|
|
3764
|
+
filter: hover && !disabled ? "brightness(0.95)" : void 0
|
|
3765
|
+
}),
|
|
3170
3766
|
children
|
|
3171
3767
|
}
|
|
3172
3768
|
);
|
|
3173
3769
|
}
|
|
3174
|
-
function Skeleton() {
|
|
3175
|
-
return /* @__PURE__ */ jsx("div", { style: {
|
|
3770
|
+
function Skeleton({ rootProps }) {
|
|
3771
|
+
return /* @__PURE__ */ jsx("div", { ...rootProps, "aria-busy": "true", style: {
|
|
3772
|
+
...rootProps.style,
|
|
3176
3773
|
display: "grid",
|
|
3177
3774
|
gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
|
|
3178
3775
|
gap: "1.5rem"
|
|
3179
3776
|
}, children: [0, 1, 2].map((i) => /* @__PURE__ */ jsxs("div", { style: {
|
|
3180
3777
|
background: C.cardBg,
|
|
3181
|
-
border:
|
|
3778
|
+
border: border(),
|
|
3182
3779
|
borderRadius: C.radius,
|
|
3183
3780
|
padding: "2rem",
|
|
3184
3781
|
height: "380px"
|
|
3185
3782
|
}, 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:
|
|
3783
|
+
/* @__PURE__ */ jsx("div", { style: { height: 20, width: "40%", borderRadius: 6, background: T.skeleton, marginBottom: 12 } }),
|
|
3784
|
+
/* @__PURE__ */ jsx("div", { style: { height: 40, width: "50%", borderRadius: 6, background: T.skeleton, marginBottom: 24 } }),
|
|
3785
|
+
/* @__PURE__ */ jsx("div", { style: { height: 14, width: "80%", borderRadius: 6, background: T.skeleton, marginBottom: 10 } }),
|
|
3786
|
+
/* @__PURE__ */ jsx("div", { style: { height: 14, width: "70%", borderRadius: 6, background: T.skeleton, marginBottom: 10 } }),
|
|
3787
|
+
/* @__PURE__ */ jsx("div", { style: { height: 14, width: "60%", borderRadius: 6, background: T.skeleton } })
|
|
3191
3788
|
] }, i)) });
|
|
3192
3789
|
}
|
|
3193
3790
|
function PricingTable({
|
|
3194
3791
|
currentPlanCode,
|
|
3195
3792
|
plans: propPlans,
|
|
3196
3793
|
features,
|
|
3794
|
+
sort,
|
|
3197
3795
|
onSelect,
|
|
3198
3796
|
returnUrl,
|
|
3199
3797
|
onStripeClientSecret,
|
|
@@ -3201,40 +3799,28 @@ function PricingTable({
|
|
|
3201
3799
|
onCheckoutError,
|
|
3202
3800
|
showToggle = true,
|
|
3203
3801
|
highlightPlan,
|
|
3802
|
+
highlightStyle = "outline",
|
|
3803
|
+
highlightLabel = "Most Popular",
|
|
3204
3804
|
className,
|
|
3205
|
-
|
|
3805
|
+
contactSalesPlans,
|
|
3806
|
+
enterpriseEmail,
|
|
3807
|
+
appearance
|
|
3206
3808
|
}) {
|
|
3809
|
+
const { root, el, planCardLayout } = useAppearance(appearance);
|
|
3810
|
+
const isEnterprise = (p) => contactSalesPlans?.includes(p.code) ?? false;
|
|
3207
3811
|
const billing = useOptionalBillingContext();
|
|
3208
3812
|
const client = billing?.client;
|
|
3209
|
-
const
|
|
3813
|
+
const catalog = useQuery(
|
|
3814
|
+
{ ...catalogQuery(client), enabled: !propPlans && Boolean(client) },
|
|
3815
|
+
billing?.queryClient ?? idleQueryClient
|
|
3816
|
+
);
|
|
3817
|
+
const plans = propPlans ?? catalog.data?.plans ?? [];
|
|
3818
|
+
const loading = !propPlans && Boolean(client) && catalog.isPending;
|
|
3819
|
+
const loadError = propPlans ? null : !client ? "BillingProvider with a publishableKey is required to load plans" : catalog.error ? `Plans could not be loaded: ${catalog.error.message}` : null;
|
|
3210
3820
|
const [currentPlan, setCurrentPlan] = useState(currentPlanCode ?? null);
|
|
3211
3821
|
const [isAnnual, setIsAnnual] = useState(false);
|
|
3212
|
-
const [loading, setLoading] = useState(!propPlans);
|
|
3213
3822
|
const [processing, setProcessing] = useState(false);
|
|
3214
3823
|
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 (!cancelled && res.ok) {
|
|
3228
|
-
const data = await res.json();
|
|
3229
|
-
setPlans(data.plans || []);
|
|
3230
|
-
}
|
|
3231
|
-
}).finally(() => {
|
|
3232
|
-
if (!cancelled) setLoading(false);
|
|
3233
|
-
});
|
|
3234
|
-
return () => {
|
|
3235
|
-
cancelled = true;
|
|
3236
|
-
};
|
|
3237
|
-
}, [client, propPlans]);
|
|
3238
3824
|
useEffect(() => {
|
|
3239
3825
|
if (currentPlanCode !== void 0) setCurrentPlan(currentPlanCode);
|
|
3240
3826
|
}, [currentPlanCode]);
|
|
@@ -3267,124 +3853,123 @@ function PricingTable({
|
|
|
3267
3853
|
onCheckoutError?.(error);
|
|
3268
3854
|
}
|
|
3269
3855
|
}
|
|
3270
|
-
const
|
|
3271
|
-
const
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
showToggle && hasAnnual && /* @__PURE__ */ jsx(Toggle, { isAnnual, onChange: setIsAnnual }),
|
|
3856
|
+
const ordered = sort ? [...plans].sort(sort) : [...sortPlansByPrice(plans.filter((p) => !isEnterprise(p))), ...plans.filter(isEnterprise)];
|
|
3857
|
+
const hasAnnual = ordered.some((p) => p.interval === "yearly");
|
|
3858
|
+
const current = plans.find((p) => p.code === currentPlan);
|
|
3859
|
+
const filtered = showToggle && hasAnnual ? ordered.filter((p) => p.interval === (isAnnual ? "yearly" : "monthly") || isEnterprise(p)) : ordered;
|
|
3860
|
+
const rootProps = root("pricingTable", text.body, className);
|
|
3861
|
+
if (loading) return /* @__PURE__ */ jsx(Skeleton, { rootProps });
|
|
3862
|
+
return /* @__PURE__ */ jsxs("div", { ...rootProps, children: [
|
|
3863
|
+
showToggle && hasAnnual && /* @__PURE__ */ jsx(Toggle, { isAnnual, onChange: setIsAnnual, el }),
|
|
3279
3864
|
processing && /* @__PURE__ */ jsx("p", { role: "status", children: "Payment is processing. Confirmation may take a little while." }),
|
|
3280
|
-
checkoutError && /* @__PURE__ */ jsx("p", { role: "alert", children: checkoutError }),
|
|
3281
|
-
/* @__PURE__ */ jsx("div", {
|
|
3865
|
+
(checkoutError ?? loadError) && /* @__PURE__ */ jsx("p", { role: "alert", style: { color: T.dangerText }, children: checkoutError ?? loadError }),
|
|
3866
|
+
/* @__PURE__ */ jsx("div", { ...el("pricingTable__grid", {
|
|
3282
3867
|
display: "grid",
|
|
3283
3868
|
gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
|
|
3284
3869
|
gap: "1.5rem"
|
|
3285
|
-
}, children: filtered.map((plan, i) => /* @__PURE__ */ jsx(
|
|
3870
|
+
}), children: filtered.map((plan, i) => /* @__PURE__ */ jsx(
|
|
3286
3871
|
Card,
|
|
3287
3872
|
{
|
|
3288
3873
|
plan,
|
|
3289
|
-
features: features
|
|
3874
|
+
features: featureLines(features, plan, i),
|
|
3290
3875
|
isCurrent: currentPlan === plan.code,
|
|
3876
|
+
isUpgrade: current && current.amount_currency === plan.amount_currency ? plan.amount_cents > current.amount_cents : void 0,
|
|
3291
3877
|
isHighlighted: highlightPlan === plan.code,
|
|
3878
|
+
filled: highlightStyle === "filled",
|
|
3879
|
+
highlightLabel,
|
|
3292
3880
|
isEnt: isEnterprise(plan),
|
|
3293
3881
|
enterpriseEmail,
|
|
3294
|
-
onSelect: () => void selectPlan(plan)
|
|
3882
|
+
onSelect: () => void selectPlan(plan),
|
|
3883
|
+
el,
|
|
3884
|
+
panel: planCardLayout === "panel"
|
|
3295
3885
|
},
|
|
3296
3886
|
plan.code
|
|
3297
|
-
)) })
|
|
3887
|
+
)) }),
|
|
3888
|
+
/* @__PURE__ */ jsx(NozleBranding, {})
|
|
3298
3889
|
] });
|
|
3299
3890
|
}
|
|
3300
3891
|
function PlanCard({
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
monthlyPrice,
|
|
3304
|
-
annualPrice,
|
|
3305
|
-
features,
|
|
3306
|
-
description,
|
|
3307
|
-
isAnnual,
|
|
3892
|
+
plan,
|
|
3893
|
+
features: featureOverride,
|
|
3308
3894
|
isCurrent,
|
|
3309
3895
|
returnUrl,
|
|
3310
|
-
onSelect
|
|
3896
|
+
onSelect,
|
|
3897
|
+
appearance
|
|
3311
3898
|
}) {
|
|
3312
|
-
const
|
|
3313
|
-
const
|
|
3899
|
+
const { root, el, planCardLayout } = useAppearance(appearance);
|
|
3900
|
+
const { code: id, name, description } = plan;
|
|
3901
|
+
const price = formatPlanPrice(plan.amount_cents, plan.amount_currency);
|
|
3902
|
+
const interval = intervalLabel(plan.interval);
|
|
3903
|
+
const features = featureOverride ?? planFeatures(plan);
|
|
3904
|
+
const cta = onSelect && /* @__PURE__ */ jsx(
|
|
3905
|
+
"button",
|
|
3906
|
+
{
|
|
3907
|
+
type: "button",
|
|
3908
|
+
onClick: () => onSelect?.({
|
|
3909
|
+
planCode: id,
|
|
3910
|
+
returnUrl: returnUrl ?? window.location.href
|
|
3911
|
+
}),
|
|
3912
|
+
...el("planCard__cta", {
|
|
3913
|
+
...buttonStyle(isCurrent ? "secondary" : "primary"),
|
|
3914
|
+
...isCurrent && { background: T.muted, color: T.mutedForeground, cursor: "default" },
|
|
3915
|
+
width: "100%"
|
|
3916
|
+
}),
|
|
3917
|
+
disabled: isCurrent,
|
|
3918
|
+
"aria-label": isCurrent ? `Current plan: ${name}` : `Select ${name}`,
|
|
3919
|
+
children: isCurrent ? "Current Plan" : "Get Started"
|
|
3920
|
+
}
|
|
3921
|
+
);
|
|
3922
|
+
if (planCardLayout === "panel") {
|
|
3923
|
+
return /* @__PURE__ */ jsx(
|
|
3924
|
+
PanelPlanCard,
|
|
3925
|
+
{
|
|
3926
|
+
frame: (style) => ({ "data-testid": `plan-card-${id}`, "aria-current": isCurrent ? "true" : void 0, ...root("planCard", style) }),
|
|
3927
|
+
el,
|
|
3928
|
+
name,
|
|
3929
|
+
description,
|
|
3930
|
+
price,
|
|
3931
|
+
interval,
|
|
3932
|
+
features,
|
|
3933
|
+
isCurrent: Boolean(isCurrent),
|
|
3934
|
+
isHighlighted: false,
|
|
3935
|
+
filled: false,
|
|
3936
|
+
children: cta
|
|
3937
|
+
}
|
|
3938
|
+
);
|
|
3939
|
+
}
|
|
3314
3940
|
return /* @__PURE__ */ jsxs(
|
|
3315
3941
|
"div",
|
|
3316
3942
|
{
|
|
3317
3943
|
"data-testid": `plan-card-${id}`,
|
|
3318
3944
|
"aria-current": isCurrent ? "true" : void 0,
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3945
|
+
...root("planCard", {
|
|
3946
|
+
...text.body,
|
|
3947
|
+
background: T.card,
|
|
3948
|
+
border: border(isCurrent ? T.primary : T.border),
|
|
3949
|
+
outline: isCurrent ? `1px solid ${T.primary}` : void 0,
|
|
3950
|
+
borderRadius: T.radius,
|
|
3951
|
+
boxShadow: T.shadow,
|
|
3323
3952
|
padding: "1.5rem",
|
|
3324
3953
|
display: "flex",
|
|
3325
3954
|
flexDirection: "column",
|
|
3326
3955
|
gap: "1rem"
|
|
3327
|
-
},
|
|
3328
|
-
className: isCurrent ? "border-primary" : "",
|
|
3956
|
+
}),
|
|
3329
3957
|
children: [
|
|
3330
3958
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
3331
|
-
/* @__PURE__ */ jsx(
|
|
3332
|
-
|
|
3333
|
-
{
|
|
3334
|
-
style: {
|
|
3335
|
-
fontSize: "1.25rem",
|
|
3336
|
-
fontWeight: 600,
|
|
3337
|
-
color: "var(--nozle-foreground, var(--foreground))",
|
|
3338
|
-
margin: 0
|
|
3339
|
-
},
|
|
3340
|
-
children: name
|
|
3341
|
-
}
|
|
3342
|
-
),
|
|
3343
|
-
description && /* @__PURE__ */ jsx(
|
|
3344
|
-
"p",
|
|
3345
|
-
{
|
|
3346
|
-
style: {
|
|
3347
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))",
|
|
3348
|
-
fontSize: "0.875rem",
|
|
3349
|
-
margin: "0.25rem 0 0"
|
|
3350
|
-
},
|
|
3351
|
-
children: description
|
|
3352
|
-
}
|
|
3353
|
-
)
|
|
3959
|
+
/* @__PURE__ */ jsx("h3", { ...el("planCard__name", { ...text.heading, margin: 0 }), children: name }),
|
|
3960
|
+
description && /* @__PURE__ */ jsx("p", { ...el("planCard__description", { ...text.secondary, margin: "0.25rem 0 0" }), children: description })
|
|
3354
3961
|
] }),
|
|
3355
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
3356
|
-
/* @__PURE__ */
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
fontWeight: 700,
|
|
3362
|
-
color: "var(--nozle-foreground, var(--foreground))"
|
|
3363
|
-
},
|
|
3364
|
-
children: [
|
|
3365
|
-
"$",
|
|
3366
|
-
price
|
|
3367
|
-
]
|
|
3368
|
-
}
|
|
3369
|
-
),
|
|
3370
|
-
/* @__PURE__ */ jsxs(
|
|
3371
|
-
"span",
|
|
3372
|
-
{
|
|
3373
|
-
style: {
|
|
3374
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))",
|
|
3375
|
-
marginLeft: "0.25rem"
|
|
3376
|
-
},
|
|
3377
|
-
children: [
|
|
3378
|
-
"/",
|
|
3379
|
-
interval
|
|
3380
|
-
]
|
|
3381
|
-
}
|
|
3382
|
-
)
|
|
3962
|
+
/* @__PURE__ */ jsxs("div", { ...el("planCard__price"), children: [
|
|
3963
|
+
/* @__PURE__ */ jsx("span", { style: text.metric, children: price }),
|
|
3964
|
+
/* @__PURE__ */ jsxs("span", { style: { ...text.meta, marginLeft: "0.25rem" }, children: [
|
|
3965
|
+
"/",
|
|
3966
|
+
interval
|
|
3967
|
+
] })
|
|
3383
3968
|
] }),
|
|
3384
3969
|
/* @__PURE__ */ jsx(
|
|
3385
3970
|
"ul",
|
|
3386
3971
|
{
|
|
3387
|
-
|
|
3972
|
+
...el("planCard__features", {
|
|
3388
3973
|
listStyle: "none",
|
|
3389
3974
|
padding: 0,
|
|
3390
3975
|
margin: 0,
|
|
@@ -3392,160 +3977,51 @@ function PlanCard({
|
|
|
3392
3977
|
flexDirection: "column",
|
|
3393
3978
|
gap: "0.5rem",
|
|
3394
3979
|
flex: 1
|
|
3395
|
-
},
|
|
3396
|
-
children: features.map((feature) => /* @__PURE__ */ jsxs(
|
|
3397
|
-
"
|
|
3398
|
-
{
|
|
3399
|
-
style: {
|
|
3400
|
-
display: "flex",
|
|
3401
|
-
alignItems: "center",
|
|
3402
|
-
gap: "0.5rem",
|
|
3403
|
-
color: "var(--nozle-foreground, var(--foreground))",
|
|
3404
|
-
fontSize: "0.875rem"
|
|
3405
|
-
},
|
|
3406
|
-
children: [
|
|
3407
|
-
/* @__PURE__ */ jsx(
|
|
3408
|
-
"span",
|
|
3409
|
-
{
|
|
3410
|
-
style: { color: "var(--nozle-primary, var(--primary))" },
|
|
3411
|
-
"aria-hidden": "true",
|
|
3412
|
-
children: "\u2713"
|
|
3413
|
-
}
|
|
3414
|
-
),
|
|
3415
|
-
feature
|
|
3416
|
-
]
|
|
3417
|
-
},
|
|
3980
|
+
}),
|
|
3981
|
+
children: features.map((feature) => /* @__PURE__ */ jsxs("li", { ...el("planCard__feature", { display: "flex", alignItems: "center", gap: "0.5rem", ...text.body }), children: [
|
|
3982
|
+
/* @__PURE__ */ jsx("span", { style: { color: T.primary }, "aria-hidden": "true", children: "\u2713" }),
|
|
3418
3983
|
feature
|
|
3419
|
-
))
|
|
3984
|
+
] }, feature))
|
|
3420
3985
|
}
|
|
3421
3986
|
),
|
|
3422
|
-
|
|
3423
|
-
"button",
|
|
3424
|
-
{
|
|
3425
|
-
onClick: () => onSelect?.({
|
|
3426
|
-
planCode: id,
|
|
3427
|
-
returnUrl: returnUrl ?? window.location.href
|
|
3428
|
-
}),
|
|
3429
|
-
style: {
|
|
3430
|
-
background: isCurrent ? "var(--nozle-muted, var(--muted))" : "var(--nozle-primary, var(--primary))",
|
|
3431
|
-
color: isCurrent ? "var(--nozle-muted-foreground, var(--muted-foreground))" : "var(--nozle-primary-foreground, var(--primary-foreground))",
|
|
3432
|
-
border: "none",
|
|
3433
|
-
borderRadius: "var(--nozle-radius, 0.5rem)",
|
|
3434
|
-
padding: "0.75rem 1.5rem",
|
|
3435
|
-
cursor: isCurrent ? "default" : "pointer",
|
|
3436
|
-
fontWeight: 500,
|
|
3437
|
-
width: "100%"
|
|
3438
|
-
},
|
|
3439
|
-
disabled: isCurrent,
|
|
3440
|
-
"aria-label": isCurrent ? `Current plan: ${name}` : `Select ${name}`,
|
|
3441
|
-
children: isCurrent ? "Current Plan" : "Get Started"
|
|
3442
|
-
}
|
|
3443
|
-
)
|
|
3987
|
+
cta
|
|
3444
3988
|
]
|
|
3445
3989
|
}
|
|
3446
3990
|
);
|
|
3447
3991
|
}
|
|
3448
3992
|
function PlanComparison({
|
|
3449
|
-
plans,
|
|
3450
|
-
features
|
|
3993
|
+
plans: catalogPlans,
|
|
3994
|
+
features: featureOverride,
|
|
3995
|
+
sort,
|
|
3996
|
+
appearance
|
|
3451
3997
|
}) {
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
"table",
|
|
3461
|
-
|
|
3462
|
-
style: {
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
},
|
|
3480
|
-
children: "Feature"
|
|
3481
|
-
}
|
|
3482
|
-
),
|
|
3483
|
-
plans.map((plan) => /* @__PURE__ */ jsx(
|
|
3484
|
-
"th",
|
|
3485
|
-
{
|
|
3486
|
-
style: {
|
|
3487
|
-
padding: "0.75rem 1rem",
|
|
3488
|
-
textAlign: "center",
|
|
3489
|
-
borderBottom: "1px solid var(--nozle-border, var(--border))",
|
|
3490
|
-
fontWeight: 600,
|
|
3491
|
-
color: "var(--nozle-foreground, var(--foreground))"
|
|
3492
|
-
},
|
|
3493
|
-
children: plan.name
|
|
3494
|
-
},
|
|
3495
|
-
plan.id
|
|
3496
|
-
))
|
|
3497
|
-
] }) }),
|
|
3498
|
-
/* @__PURE__ */ jsx("tbody", { children: features.map((feature) => /* @__PURE__ */ jsxs("tr", { children: [
|
|
3499
|
-
/* @__PURE__ */ jsx(
|
|
3500
|
-
"td",
|
|
3501
|
-
{
|
|
3502
|
-
style: {
|
|
3503
|
-
padding: "0.75rem 1rem",
|
|
3504
|
-
borderBottom: "1px solid var(--nozle-border, var(--border))",
|
|
3505
|
-
color: "var(--nozle-foreground, var(--foreground))"
|
|
3506
|
-
},
|
|
3507
|
-
children: feature.label
|
|
3508
|
-
}
|
|
3509
|
-
),
|
|
3510
|
-
plans.map((plan) => {
|
|
3511
|
-
const value = feature.values[plan.id];
|
|
3512
|
-
return /* @__PURE__ */ jsx(
|
|
3513
|
-
"td",
|
|
3514
|
-
{
|
|
3515
|
-
style: {
|
|
3516
|
-
padding: "0.75rem 1rem",
|
|
3517
|
-
textAlign: "center",
|
|
3518
|
-
borderBottom: "1px solid var(--nozle-border, var(--border))"
|
|
3519
|
-
},
|
|
3520
|
-
children: typeof value === "boolean" ? value ? /* @__PURE__ */ jsx(
|
|
3521
|
-
"span",
|
|
3522
|
-
{
|
|
3523
|
-
style: {
|
|
3524
|
-
color: "var(--nozle-primary, var(--primary))"
|
|
3525
|
-
},
|
|
3526
|
-
"aria-label": "Included",
|
|
3527
|
-
children: "\u2713"
|
|
3528
|
-
}
|
|
3529
|
-
) : /* @__PURE__ */ jsx(
|
|
3530
|
-
"span",
|
|
3531
|
-
{
|
|
3532
|
-
style: {
|
|
3533
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))"
|
|
3534
|
-
},
|
|
3535
|
-
"aria-label": "Not included",
|
|
3536
|
-
children: "\u2715"
|
|
3537
|
-
}
|
|
3538
|
-
) : /* @__PURE__ */ jsx("span", { children: value ?? "\u2014" })
|
|
3539
|
-
},
|
|
3540
|
-
plan.id
|
|
3541
|
-
);
|
|
3542
|
-
})
|
|
3543
|
-
] }, feature.key)) })
|
|
3544
|
-
]
|
|
3545
|
-
}
|
|
3546
|
-
)
|
|
3547
|
-
}
|
|
3548
|
-
);
|
|
3998
|
+
const { root, el } = useAppearance(appearance);
|
|
3999
|
+
const plans = sort ? [...catalogPlans].sort(sort) : sortPlansByPrice(catalogPlans);
|
|
4000
|
+
const features = featureOverride ?? comparisonRows(plans);
|
|
4001
|
+
const cell = (style) => el("planComparison__cell", style);
|
|
4002
|
+
return /* @__PURE__ */ jsxs("div", { ...root("planComparison", { minWidth: 0 }), children: [
|
|
4003
|
+
/* @__PURE__ */ jsx("div", { style: table.frame, children: /* @__PURE__ */ jsxs("table", { ...el("planComparison__table", table.table), children: [
|
|
4004
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
4005
|
+
/* @__PURE__ */ jsx("th", { ...cell(table.head()), children: "Feature" }),
|
|
4006
|
+
plans.map((plan) => /* @__PURE__ */ jsxs("th", { ...cell({ ...table.head(), textAlign: "center", padding: "10px 20px" }), children: [
|
|
4007
|
+
plan.name,
|
|
4008
|
+
/* @__PURE__ */ jsxs("div", { style: { ...text.meta, letterSpacing: "normal", textTransform: "none" }, children: [
|
|
4009
|
+
formatPlanPrice(plan.amount_cents, plan.amount_currency),
|
|
4010
|
+
" / ",
|
|
4011
|
+
intervalLabel(plan.interval)
|
|
4012
|
+
] })
|
|
4013
|
+
] }, plan.code))
|
|
4014
|
+
] }) }),
|
|
4015
|
+
/* @__PURE__ */ jsx("tbody", { children: features.map((feature, index) => /* @__PURE__ */ jsxs("tr", { ...el("planComparison__row"), children: [
|
|
4016
|
+
/* @__PURE__ */ jsx("td", { ...cell(table.cell(false, index === features.length - 1)), children: feature.label }),
|
|
4017
|
+
plans.map((plan) => {
|
|
4018
|
+
const value = feature.values[plan.code];
|
|
4019
|
+
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);
|
|
4020
|
+
})
|
|
4021
|
+
] }, feature.key)) })
|
|
4022
|
+
] }) }),
|
|
4023
|
+
/* @__PURE__ */ jsx(NozleBranding, {})
|
|
4024
|
+
] });
|
|
3549
4025
|
}
|
|
3550
4026
|
function UpgradeButton({
|
|
3551
4027
|
planCode,
|
|
@@ -3561,26 +4037,18 @@ function UpgradeButton({
|
|
|
3561
4037
|
onCheckoutStarted,
|
|
3562
4038
|
onDowngradeScheduled,
|
|
3563
4039
|
onUpgraded,
|
|
3564
|
-
onError
|
|
4040
|
+
onError,
|
|
4041
|
+
appearance
|
|
3565
4042
|
}) {
|
|
3566
4043
|
const [isOpen, setIsOpen] = useState(false);
|
|
4044
|
+
const { root } = useAppearance(appearance);
|
|
3567
4045
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3568
4046
|
/* @__PURE__ */ jsx(
|
|
3569
4047
|
"button",
|
|
3570
4048
|
{
|
|
3571
4049
|
type: "button",
|
|
3572
4050
|
onClick: () => setIsOpen(true),
|
|
3573
|
-
className,
|
|
3574
|
-
style: {
|
|
3575
|
-
padding: "0.75rem 1.5rem",
|
|
3576
|
-
borderRadius: "var(--nozle-radius, 0.5rem)",
|
|
3577
|
-
border: "none",
|
|
3578
|
-
background: "var(--nozle-primary, var(--primary))",
|
|
3579
|
-
color: "var(--nozle-primary-foreground, var(--primary-foreground))",
|
|
3580
|
-
cursor: "pointer",
|
|
3581
|
-
fontWeight: 500,
|
|
3582
|
-
...style
|
|
3583
|
-
},
|
|
4051
|
+
...root("upgradeButton", { ...buttonStyle(), ...style }, className),
|
|
3584
4052
|
children: label
|
|
3585
4053
|
}
|
|
3586
4054
|
),
|
|
@@ -3588,6 +4056,7 @@ function UpgradeButton({
|
|
|
3588
4056
|
UpgradeModal,
|
|
3589
4057
|
{
|
|
3590
4058
|
isOpen,
|
|
4059
|
+
appearance,
|
|
3591
4060
|
planCode,
|
|
3592
4061
|
returnUrl,
|
|
3593
4062
|
subscriptionId,
|
|
@@ -3646,11 +4115,13 @@ function PlanGate({
|
|
|
3646
4115
|
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
3647
4116
|
}
|
|
3648
4117
|
function UpgradePrompt({
|
|
3649
|
-
text = "Upgrade to access this feature",
|
|
4118
|
+
text: text2 = "Upgrade to access this feature",
|
|
3650
4119
|
planName: planName2,
|
|
3651
|
-
onUpgrade
|
|
4120
|
+
onUpgrade,
|
|
4121
|
+
appearance
|
|
3652
4122
|
}) {
|
|
3653
|
-
const
|
|
4123
|
+
const { root } = useAppearance(appearance);
|
|
4124
|
+
const displayText = planName2 ? `Upgrade to ${planName2} to access this feature` : text2;
|
|
3654
4125
|
function handleUpgrade() {
|
|
3655
4126
|
if (onUpgrade) {
|
|
3656
4127
|
onUpgrade();
|
|
@@ -3661,40 +4132,23 @@ function UpgradePrompt({
|
|
|
3661
4132
|
return /* @__PURE__ */ jsxs(
|
|
3662
4133
|
"div",
|
|
3663
4134
|
{
|
|
3664
|
-
|
|
4135
|
+
...root("upgradePrompt", {
|
|
3665
4136
|
display: "flex",
|
|
3666
4137
|
flexDirection: "column",
|
|
3667
4138
|
alignItems: "center",
|
|
3668
4139
|
gap: "0.75rem",
|
|
3669
4140
|
padding: "1rem",
|
|
3670
|
-
textAlign: "center"
|
|
3671
|
-
|
|
4141
|
+
textAlign: "center",
|
|
4142
|
+
...text.body
|
|
4143
|
+
}),
|
|
3672
4144
|
children: [
|
|
3673
|
-
/* @__PURE__ */ jsx(
|
|
3674
|
-
"p",
|
|
3675
|
-
{
|
|
3676
|
-
style: {
|
|
3677
|
-
color: "var(--nozle-foreground, var(--foreground))",
|
|
3678
|
-
fontSize: "0.875rem",
|
|
3679
|
-
margin: 0
|
|
3680
|
-
},
|
|
3681
|
-
children: displayText
|
|
3682
|
-
}
|
|
3683
|
-
),
|
|
4145
|
+
/* @__PURE__ */ jsx("p", { style: { margin: 0 }, children: displayText }),
|
|
3684
4146
|
/* @__PURE__ */ jsx(
|
|
3685
4147
|
"button",
|
|
3686
4148
|
{
|
|
4149
|
+
type: "button",
|
|
3687
4150
|
onClick: handleUpgrade,
|
|
3688
|
-
style: {
|
|
3689
|
-
padding: "0.5rem 1.25rem",
|
|
3690
|
-
borderRadius: "var(--nozle-radius, 0.5rem)",
|
|
3691
|
-
border: "none",
|
|
3692
|
-
background: "var(--nozle-primary, var(--primary))",
|
|
3693
|
-
color: "var(--nozle-primary-foreground, var(--primary-foreground))",
|
|
3694
|
-
cursor: "pointer",
|
|
3695
|
-
fontWeight: 500,
|
|
3696
|
-
fontSize: "0.875rem"
|
|
3697
|
-
},
|
|
4151
|
+
style: { ...buttonStyle(), padding: "0.5rem 1.25rem" },
|
|
3698
4152
|
children: "Upgrade"
|
|
3699
4153
|
}
|
|
3700
4154
|
)
|
|
@@ -3707,12 +4161,14 @@ function LockedOverlay({
|
|
|
3707
4161
|
upgradeText,
|
|
3708
4162
|
upgradePlanName,
|
|
3709
4163
|
onUpgrade,
|
|
3710
|
-
children
|
|
4164
|
+
children,
|
|
4165
|
+
appearance
|
|
3711
4166
|
}) {
|
|
4167
|
+
const { root, el } = useAppearance(appearance);
|
|
3712
4168
|
if (!locked) {
|
|
3713
4169
|
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
3714
4170
|
}
|
|
3715
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
4171
|
+
return /* @__PURE__ */ jsxs("div", { ...root("lockedOverlay", { position: "relative", display: "inline-block" }), children: [
|
|
3716
4172
|
/* @__PURE__ */ jsx(
|
|
3717
4173
|
"div",
|
|
3718
4174
|
{
|
|
@@ -3728,16 +4184,15 @@ function LockedOverlay({
|
|
|
3728
4184
|
/* @__PURE__ */ jsx(
|
|
3729
4185
|
"div",
|
|
3730
4186
|
{
|
|
3731
|
-
|
|
4187
|
+
...el("lockedOverlay__panel", {
|
|
3732
4188
|
position: "absolute",
|
|
3733
4189
|
inset: 0,
|
|
3734
4190
|
display: "flex",
|
|
3735
4191
|
alignItems: "center",
|
|
3736
4192
|
justifyContent: "center",
|
|
3737
|
-
background:
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
},
|
|
4193
|
+
background: `color-mix(in srgb, ${T.background} 92%, transparent)`,
|
|
4194
|
+
borderRadius: T.radius
|
|
4195
|
+
}),
|
|
3741
4196
|
role: "dialog",
|
|
3742
4197
|
"aria-label": "Feature locked \u2014 upgrade required",
|
|
3743
4198
|
children: /* @__PURE__ */ jsx(
|
|
@@ -3745,7 +4200,8 @@ function LockedOverlay({
|
|
|
3745
4200
|
{
|
|
3746
4201
|
text: upgradeText,
|
|
3747
4202
|
planName: upgradePlanName,
|
|
3748
|
-
onUpgrade
|
|
4203
|
+
onUpgrade,
|
|
4204
|
+
appearance
|
|
3749
4205
|
}
|
|
3750
4206
|
)
|
|
3751
4207
|
}
|
|
@@ -3753,68 +4209,109 @@ function LockedOverlay({
|
|
|
3753
4209
|
] });
|
|
3754
4210
|
}
|
|
3755
4211
|
function getUsageMeterColor(percentage) {
|
|
3756
|
-
if (percentage >= 95) return
|
|
3757
|
-
if (percentage >= 80) return
|
|
3758
|
-
return
|
|
4212
|
+
if (percentage >= 95) return T.danger;
|
|
4213
|
+
if (percentage >= 80) return T.warning;
|
|
4214
|
+
return T.primary;
|
|
3759
4215
|
}
|
|
3760
4216
|
function UsageMeter({
|
|
3761
4217
|
used,
|
|
3762
4218
|
limit,
|
|
3763
4219
|
label,
|
|
3764
|
-
showText = true
|
|
4220
|
+
showText = true,
|
|
4221
|
+
appearance
|
|
3765
4222
|
}) {
|
|
3766
|
-
const
|
|
4223
|
+
const { root, el } = useAppearance(appearance);
|
|
4224
|
+
const unlimited = limit == null;
|
|
4225
|
+
const percentage = !unlimited && limit > 0 ? Math.min(100, used / limit * 100) : 0;
|
|
3767
4226
|
const color = getUsageMeterColor(percentage);
|
|
3768
|
-
return /* @__PURE__ */ jsxs(
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
4227
|
+
return /* @__PURE__ */ jsxs(
|
|
4228
|
+
"div",
|
|
4229
|
+
{
|
|
4230
|
+
...root("usageMeter", {
|
|
4231
|
+
width: "100%",
|
|
4232
|
+
display: "flex",
|
|
4233
|
+
flexDirection: "column",
|
|
4234
|
+
gap: "0.25rem",
|
|
4235
|
+
...text.body
|
|
4236
|
+
}),
|
|
4237
|
+
children: [
|
|
4238
|
+
label && /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
|
|
4239
|
+
/* @__PURE__ */ jsx("span", { ...el("usageMeter__label"), children: label }),
|
|
4240
|
+
showText && /* @__PURE__ */ jsxs("span", { ...el("usageMeter__value", { color: percentage >= 95 ? T.dangerText : percentage >= 80 ? T.warningText : T.foreground }), children: [
|
|
4241
|
+
used.toLocaleString(),
|
|
4242
|
+
" / ",
|
|
4243
|
+
unlimited ? "Unlimited" : limit.toLocaleString()
|
|
4244
|
+
] })
|
|
4245
|
+
] }),
|
|
4246
|
+
/* @__PURE__ */ jsx(
|
|
3783
4247
|
"div",
|
|
3784
4248
|
{
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
}
|
|
4249
|
+
...el("usageMeter__track", {
|
|
4250
|
+
height: "0.5rem",
|
|
4251
|
+
borderRadius: "9999px",
|
|
4252
|
+
overflow: "hidden",
|
|
4253
|
+
background: T.muted
|
|
4254
|
+
}),
|
|
4255
|
+
children: /* @__PURE__ */ jsx(
|
|
4256
|
+
"div",
|
|
4257
|
+
{
|
|
4258
|
+
...el("usageMeter__fill", {
|
|
4259
|
+
height: "100%",
|
|
4260
|
+
borderRadius: "9999px",
|
|
4261
|
+
width: `${percentage}%`,
|
|
4262
|
+
background: color,
|
|
4263
|
+
transition: "width 0.3s ease"
|
|
4264
|
+
})
|
|
4265
|
+
}
|
|
4266
|
+
)
|
|
3791
4267
|
}
|
|
3792
4268
|
)
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
4269
|
+
]
|
|
4270
|
+
}
|
|
4271
|
+
);
|
|
4272
|
+
}
|
|
4273
|
+
function usageItems(entitlements, results) {
|
|
4274
|
+
return entitlements.flatMap((entitlement) => {
|
|
4275
|
+
const result = results[entitlement.code];
|
|
4276
|
+
if (entitlement.feature_type !== "metered" || !result) return [];
|
|
4277
|
+
return [{ key: entitlement.code, label: entitlement.name, used: result.used, limit: result.limit ?? null }];
|
|
4278
|
+
});
|
|
3796
4279
|
}
|
|
3797
4280
|
function UsageDashboard({
|
|
3798
4281
|
features = [],
|
|
3799
|
-
loading = false
|
|
4282
|
+
loading = false,
|
|
4283
|
+
error = null,
|
|
4284
|
+
emptyMessage = "No usage to show yet.",
|
|
4285
|
+
appearance
|
|
3800
4286
|
}) {
|
|
4287
|
+
const { root } = useAppearance(appearance);
|
|
4288
|
+
const props = root("usageDashboard", {
|
|
4289
|
+
...surface,
|
|
4290
|
+
margin: 0,
|
|
4291
|
+
display: "flex",
|
|
4292
|
+
flexDirection: "column",
|
|
4293
|
+
gap: loading ? "0.75rem" : "1rem",
|
|
4294
|
+
// Errors and the empty message take the same colours as in the other components.
|
|
4295
|
+
...error ? { color: T.dangerText } : features.length === 0 && !loading && text.secondary
|
|
4296
|
+
});
|
|
3801
4297
|
if (loading) {
|
|
3802
|
-
return /* @__PURE__ */ jsx("div", {
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
)) });
|
|
4298
|
+
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)) });
|
|
4299
|
+
}
|
|
4300
|
+
if (error) {
|
|
4301
|
+
return /* @__PURE__ */ jsx("p", { ...props, role: "alert", children: typeof error === "string" ? error : error.message });
|
|
4302
|
+
}
|
|
4303
|
+
if (features.length === 0) {
|
|
4304
|
+
return /* @__PURE__ */ jsx("p", { ...props, role: "status", children: emptyMessage });
|
|
3810
4305
|
}
|
|
3811
|
-
return /* @__PURE__ */ jsx("div", {
|
|
4306
|
+
return /* @__PURE__ */ jsx("div", { ...props, children: features.map((f) => /* @__PURE__ */ jsx(UsageMeter, { used: f.used, limit: f.limit, label: f.label, appearance }, f.key)) });
|
|
3812
4307
|
}
|
|
3813
4308
|
function UsageAlert({
|
|
3814
4309
|
features,
|
|
3815
4310
|
threshold = 80,
|
|
3816
|
-
upgradeHref = "/plans"
|
|
4311
|
+
upgradeHref = "/plans",
|
|
4312
|
+
appearance
|
|
3817
4313
|
}) {
|
|
4314
|
+
const { root, el } = useAppearance(appearance);
|
|
3818
4315
|
const [dismissed, setDismissed] = useState(/* @__PURE__ */ new Set());
|
|
3819
4316
|
const alertFeatures = features.filter(
|
|
3820
4317
|
(f) => f.percentage >= threshold && !dismissed.has(f.key)
|
|
@@ -3825,15 +4322,19 @@ function UsageAlert({
|
|
|
3825
4322
|
const handleDismiss = (key) => {
|
|
3826
4323
|
setDismissed((prev) => new Set(prev).add(key));
|
|
3827
4324
|
};
|
|
3828
|
-
return /* @__PURE__ */ jsx("div", {
|
|
4325
|
+
return /* @__PURE__ */ jsx("div", { ...root("usageAlert", { display: "flex", flexDirection: "column", gap: "0.5rem", ...text.body }), children: alertFeatures.map((f) => /* @__PURE__ */ jsxs(
|
|
3829
4326
|
"div",
|
|
3830
4327
|
{
|
|
3831
4328
|
role: "alert",
|
|
3832
|
-
className: "flex items-center justify-between rounded-md px-4 py-3 text-sm",
|
|
3833
4329
|
style: {
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
4330
|
+
display: "flex",
|
|
4331
|
+
alignItems: "center",
|
|
4332
|
+
justifyContent: "space-between",
|
|
4333
|
+
borderRadius: `calc(${T.radius} / 2)`,
|
|
4334
|
+
padding: "0.75rem 1rem",
|
|
4335
|
+
background: T.warningSoft,
|
|
4336
|
+
color: T.warningText,
|
|
4337
|
+
border: border(T.warningBorder)
|
|
3837
4338
|
},
|
|
3838
4339
|
children: [
|
|
3839
4340
|
/* @__PURE__ */ jsxs("span", { children: [
|
|
@@ -3856,8 +4357,8 @@ function UsageAlert({
|
|
|
3856
4357
|
{
|
|
3857
4358
|
href: upgradeHref,
|
|
3858
4359
|
style: {
|
|
4360
|
+
...text.action,
|
|
3859
4361
|
color: "inherit",
|
|
3860
|
-
fontWeight: 600,
|
|
3861
4362
|
textDecoration: "underline"
|
|
3862
4363
|
},
|
|
3863
4364
|
children: "Upgrade"
|
|
@@ -3867,16 +4368,17 @@ function UsageAlert({
|
|
|
3867
4368
|
/* @__PURE__ */ jsx(
|
|
3868
4369
|
"button",
|
|
3869
4370
|
{
|
|
4371
|
+
type: "button",
|
|
3870
4372
|
onClick: () => handleDismiss(f.key),
|
|
3871
4373
|
"aria-label": `Dismiss ${f.label} alert`,
|
|
3872
|
-
|
|
4374
|
+
...el("usageAlert__dismiss", {
|
|
3873
4375
|
background: "none",
|
|
3874
4376
|
border: "none",
|
|
3875
4377
|
cursor: "pointer",
|
|
3876
4378
|
padding: "0 0 0 0.75rem",
|
|
3877
|
-
|
|
4379
|
+
...text.action,
|
|
3878
4380
|
color: "inherit"
|
|
3879
|
-
},
|
|
4381
|
+
}),
|
|
3880
4382
|
children: "\xD7"
|
|
3881
4383
|
}
|
|
3882
4384
|
)
|
|
@@ -3885,44 +4387,247 @@ function UsageAlert({
|
|
|
3885
4387
|
f.key
|
|
3886
4388
|
)) });
|
|
3887
4389
|
}
|
|
3888
|
-
var
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
4390
|
+
var STATUS_LABELS = {
|
|
4391
|
+
active: "Active",
|
|
4392
|
+
pending: "Pending",
|
|
4393
|
+
incomplete: "Awaiting payment",
|
|
4394
|
+
canceled: "Canceled",
|
|
4395
|
+
terminated: "Ended"
|
|
4396
|
+
};
|
|
4397
|
+
function statusColors(status, ending) {
|
|
4398
|
+
if (status === "active" && !ending) return { background: T.successSoft, color: T.successText };
|
|
4399
|
+
if (status === "active" || status === "pending" || status === "incomplete") {
|
|
4400
|
+
return { background: T.warningSoft, color: T.warningText };
|
|
4401
|
+
}
|
|
4402
|
+
return { background: T.muted, color: T.mutedForeground };
|
|
4403
|
+
}
|
|
4404
|
+
function day(value, locale) {
|
|
4405
|
+
const parsed = new Date(value);
|
|
4406
|
+
return Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleDateString(locale, { dateStyle: "medium" });
|
|
4407
|
+
}
|
|
4408
|
+
function currentPlanMeters(entitlements, usage) {
|
|
4409
|
+
const units = /* @__PURE__ */ new Map();
|
|
4410
|
+
for (const charge of usage?.charges_usage ?? []) {
|
|
4411
|
+
units.set(charge.feature.code, (units.get(charge.feature.code) ?? 0) + (Number(charge.units) || 0));
|
|
4412
|
+
}
|
|
4413
|
+
return (entitlements ?? []).filter((entitlement) => entitlement.feature_type === "metered").map((entitlement) => ({
|
|
4414
|
+
key: entitlement.code,
|
|
4415
|
+
label: entitlement.name,
|
|
4416
|
+
used: units.get(entitlement.code) ?? 0,
|
|
4417
|
+
limit: entitlement.usage_limit ?? null
|
|
4418
|
+
}));
|
|
4419
|
+
}
|
|
4420
|
+
function CurrentPlan(props) {
|
|
4421
|
+
return props.subscription === void 0 ? /* @__PURE__ */ jsx(CurrentPlanFromBackend, { ...props }) : /* @__PURE__ */ jsx(CurrentPlanView, { ...props });
|
|
4422
|
+
}
|
|
4423
|
+
function CurrentPlanFromBackend(props) {
|
|
4424
|
+
const subscription = useSubscription();
|
|
4425
|
+
const plans = usePlans();
|
|
4426
|
+
const usage = useUsage();
|
|
4427
|
+
const planCode = subscription.data?.subscription?.plan_code;
|
|
4428
|
+
const meters = props.usage ?? currentPlanMeters(plans.plans.find((plan) => plan.code === planCode)?.entitlements, usage.data);
|
|
4429
|
+
return /* @__PURE__ */ jsx(
|
|
4430
|
+
CurrentPlanView,
|
|
4431
|
+
{
|
|
4432
|
+
...props,
|
|
4433
|
+
subscription: subscription.data ?? null,
|
|
4434
|
+
usage: meters,
|
|
4435
|
+
isLoading: props.isLoading || subscription.isPending,
|
|
4436
|
+
error: props.error ?? subscription.error
|
|
4437
|
+
}
|
|
4438
|
+
);
|
|
4439
|
+
}
|
|
4440
|
+
function CurrentPlanView({
|
|
4441
|
+
subscription,
|
|
4442
|
+
usage = [],
|
|
4443
|
+
details = [],
|
|
4444
|
+
actions,
|
|
4445
|
+
warningPercent = 80,
|
|
4446
|
+
title = "Current plan",
|
|
4447
|
+
isLoading = false,
|
|
4448
|
+
error = null,
|
|
4449
|
+
emptyMessage = "No active plan.",
|
|
4450
|
+
pastDueMessage = "A payment is overdue. Pay the open invoice to keep your plan active.",
|
|
4451
|
+
locale = "en-US",
|
|
4452
|
+
appearance
|
|
4453
|
+
}) {
|
|
4454
|
+
const { root, el } = useAppearance(appearance);
|
|
4455
|
+
const frame = { ...surface, padding: 0, overflow: "hidden" };
|
|
4456
|
+
const header = /* @__PURE__ */ jsxs(
|
|
4457
|
+
"div",
|
|
4458
|
+
{
|
|
4459
|
+
style: {
|
|
4460
|
+
display: "flex",
|
|
4461
|
+
alignItems: "center",
|
|
4462
|
+
justifyContent: "space-between",
|
|
4463
|
+
gap: 12,
|
|
4464
|
+
padding: "0.875rem 1.25rem",
|
|
4465
|
+
borderBottom: border()
|
|
4466
|
+
},
|
|
4467
|
+
children: [
|
|
4468
|
+
/* @__PURE__ */ jsx("span", { style: text.heading, children: title }),
|
|
4469
|
+
actions
|
|
4470
|
+
]
|
|
4471
|
+
}
|
|
4472
|
+
);
|
|
4473
|
+
const body = (children) => /* @__PURE__ */ jsx("div", { style: { padding: "1.25rem", display: "grid", gap: "1rem" }, children });
|
|
4474
|
+
if (isLoading) {
|
|
4475
|
+
return /* @__PURE__ */ jsxs("section", { ...root("currentPlan", frame), "aria-busy": "true", children: [
|
|
4476
|
+
header,
|
|
4477
|
+
body(/* @__PURE__ */ jsx("div", { style: { height: 72, borderRadius: `calc(${T.radius} / 2)`, background: T.skeleton } }))
|
|
4478
|
+
] });
|
|
3908
4479
|
}
|
|
4480
|
+
if (error) {
|
|
4481
|
+
return /* @__PURE__ */ jsxs("section", { ...root("currentPlan", frame), children: [
|
|
4482
|
+
header,
|
|
4483
|
+
body(/* @__PURE__ */ jsx("p", { role: "alert", style: { margin: 0, color: T.dangerText }, children: typeof error === "string" ? error : error.message }))
|
|
4484
|
+
] });
|
|
4485
|
+
}
|
|
4486
|
+
const current = subscription?.subscription;
|
|
4487
|
+
if (!current) {
|
|
4488
|
+
return /* @__PURE__ */ jsxs("section", { ...root("currentPlan", frame), children: [
|
|
4489
|
+
header,
|
|
4490
|
+
body(/* @__PURE__ */ jsx("p", { role: "status", style: { ...text.secondary, margin: 0 }, children: emptyMessage }))
|
|
4491
|
+
] });
|
|
4492
|
+
}
|
|
4493
|
+
const pending = subscription?.pending_change;
|
|
4494
|
+
const ending = Boolean(current.ending_at);
|
|
4495
|
+
const near = warningPercent === null ? [] : usage.filter((meter) => meter.limit != null && meter.limit > 0 && meter.used / meter.limit * 100 >= warningPercent);
|
|
4496
|
+
const statusLabel = ending && current.ending_at ? `Ends ${day(current.ending_at, locale)}` : STATUS_LABELS[current.status] ?? current.status;
|
|
4497
|
+
return /* @__PURE__ */ jsxs("section", { ...root("currentPlan", frame), "aria-label": title, children: [
|
|
4498
|
+
header,
|
|
4499
|
+
body(
|
|
4500
|
+
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4501
|
+
subscription?.past_due && /* @__PURE__ */ jsx(
|
|
4502
|
+
"p",
|
|
4503
|
+
{
|
|
4504
|
+
role: "alert",
|
|
4505
|
+
...el("currentPlan__notice", {
|
|
4506
|
+
margin: 0,
|
|
4507
|
+
padding: "0.625rem 0.875rem",
|
|
4508
|
+
...text.body,
|
|
4509
|
+
borderRadius: `calc(${T.radius} / 2)`,
|
|
4510
|
+
background: T.dangerSoft,
|
|
4511
|
+
color: T.dangerText,
|
|
4512
|
+
border: border(T.dangerBorder)
|
|
4513
|
+
}),
|
|
4514
|
+
children: pastDueMessage
|
|
4515
|
+
}
|
|
4516
|
+
),
|
|
4517
|
+
near.map((meter) => /* @__PURE__ */ jsxs(
|
|
4518
|
+
"p",
|
|
4519
|
+
{
|
|
4520
|
+
role: "status",
|
|
4521
|
+
...el("currentPlan__notice", {
|
|
4522
|
+
margin: 0,
|
|
4523
|
+
padding: "0.625rem 0.875rem",
|
|
4524
|
+
...text.body,
|
|
4525
|
+
borderRadius: `calc(${T.radius} / 2)`,
|
|
4526
|
+
background: T.warningSoft,
|
|
4527
|
+
color: T.warningText,
|
|
4528
|
+
border: border(T.warningBorder)
|
|
4529
|
+
}),
|
|
4530
|
+
children: [
|
|
4531
|
+
"You have used ",
|
|
4532
|
+
Math.round(meter.used / meter.limit * 100),
|
|
4533
|
+
"% of the ",
|
|
4534
|
+
meter.label,
|
|
4535
|
+
" included in",
|
|
4536
|
+
" ",
|
|
4537
|
+
current.plan.name,
|
|
4538
|
+
"."
|
|
4539
|
+
]
|
|
4540
|
+
},
|
|
4541
|
+
meter.key
|
|
4542
|
+
)),
|
|
4543
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", flexWrap: "wrap", alignItems: "center", gap: "0.625rem" }, children: [
|
|
4544
|
+
/* @__PURE__ */ jsx("span", { ...el("currentPlan__name", text.heading), children: current.plan.name }),
|
|
4545
|
+
/* @__PURE__ */ jsx(
|
|
4546
|
+
"span",
|
|
4547
|
+
{
|
|
4548
|
+
...el("currentPlan__status", {
|
|
4549
|
+
...text.label,
|
|
4550
|
+
...statusColors(current.status, ending),
|
|
4551
|
+
padding: "0.125rem 0.625rem",
|
|
4552
|
+
borderRadius: 9999
|
|
4553
|
+
}),
|
|
4554
|
+
children: statusLabel
|
|
4555
|
+
}
|
|
4556
|
+
),
|
|
4557
|
+
pending && /* @__PURE__ */ jsxs(
|
|
4558
|
+
"span",
|
|
4559
|
+
{
|
|
4560
|
+
...el("currentPlan__scheduled", {
|
|
4561
|
+
...text.label,
|
|
4562
|
+
padding: "0.125rem 0.625rem",
|
|
4563
|
+
borderRadius: 9999,
|
|
4564
|
+
border: border()
|
|
4565
|
+
}),
|
|
4566
|
+
children: [
|
|
4567
|
+
"Changes to ",
|
|
4568
|
+
pending.name,
|
|
4569
|
+
" on ",
|
|
4570
|
+
day(pending.effective_at, locale)
|
|
4571
|
+
]
|
|
4572
|
+
}
|
|
4573
|
+
),
|
|
4574
|
+
/* @__PURE__ */ jsxs("span", { ...el("currentPlan__price", { ...text.secondary, marginLeft: "auto" }), children: [
|
|
4575
|
+
formatPlanPrice(Number(current.plan.amount_cents), current.plan.currency),
|
|
4576
|
+
" / ",
|
|
4577
|
+
intervalLabel(current.plan.interval)
|
|
4578
|
+
] })
|
|
4579
|
+
] }),
|
|
4580
|
+
usage.length > 0 && /* @__PURE__ */ jsx("div", { ...el("currentPlan__usage", { display: "grid", gap: "0.875rem" }), children: usage.map((meter) => /* @__PURE__ */ jsx(
|
|
4581
|
+
UsageMeter,
|
|
4582
|
+
{
|
|
4583
|
+
label: `${meter.label} used this period`,
|
|
4584
|
+
used: meter.used,
|
|
4585
|
+
limit: meter.limit,
|
|
4586
|
+
appearance
|
|
4587
|
+
},
|
|
4588
|
+
meter.key
|
|
4589
|
+
)) }),
|
|
4590
|
+
details.length > 0 && /* @__PURE__ */ jsx(
|
|
4591
|
+
"dl",
|
|
4592
|
+
{
|
|
4593
|
+
...el("currentPlan__details", {
|
|
4594
|
+
margin: 0,
|
|
4595
|
+
display: "grid",
|
|
4596
|
+
gridTemplateColumns: "repeat(auto-fit, minmax(160px, 1fr))",
|
|
4597
|
+
gap: "0.75rem"
|
|
4598
|
+
}),
|
|
4599
|
+
children: details.map((detail) => /* @__PURE__ */ jsxs("div", { children: [
|
|
4600
|
+
/* @__PURE__ */ jsx("dt", { style: text.meta, children: detail.label }),
|
|
4601
|
+
/* @__PURE__ */ jsx("dd", { style: { margin: "0.125rem 0 0", fontWeight: 600 }, children: detail.value })
|
|
4602
|
+
] }, detail.label))
|
|
4603
|
+
}
|
|
4604
|
+
)
|
|
4605
|
+
] })
|
|
4606
|
+
),
|
|
4607
|
+
/* @__PURE__ */ jsx(NozleBranding, {})
|
|
4608
|
+
] });
|
|
4609
|
+
}
|
|
4610
|
+
var TIER_STYLES = {
|
|
4611
|
+
free: { background: T.muted, color: T.mutedForeground, border: border() },
|
|
4612
|
+
starter: { background: T.primarySoft, color: T.primary, border: border(`color-mix(in srgb, ${T.primary} 30%, transparent)`) },
|
|
4613
|
+
pro: { background: T.primary, color: T.primaryForeground, border: border("transparent") },
|
|
4614
|
+
enterprise: { background: T.accent, color: T.accentForeground, border: border("transparent") }
|
|
3909
4615
|
};
|
|
3910
|
-
function PlanBadge({ plan, tier = "free" }) {
|
|
4616
|
+
function PlanBadge({ plan, tier = "free", appearance }) {
|
|
4617
|
+
const { root } = useAppearance(appearance);
|
|
3911
4618
|
const style = TIER_STYLES[tier] ?? TIER_STYLES.free;
|
|
3912
4619
|
return /* @__PURE__ */ jsx(
|
|
3913
4620
|
"span",
|
|
3914
4621
|
{
|
|
3915
|
-
|
|
4622
|
+
...root("planBadge", {
|
|
4623
|
+
...text.label,
|
|
3916
4624
|
...style,
|
|
3917
4625
|
display: "inline-flex",
|
|
3918
4626
|
alignItems: "center",
|
|
3919
4627
|
padding: "0.125rem 0.5rem",
|
|
3920
4628
|
borderRadius: "9999px",
|
|
3921
|
-
fontSize: "0.75rem",
|
|
3922
|
-
fontWeight: 600,
|
|
3923
|
-
lineHeight: "1.25rem",
|
|
3924
4629
|
whiteSpace: "nowrap"
|
|
3925
|
-
},
|
|
4630
|
+
}),
|
|
3926
4631
|
children: plan
|
|
3927
4632
|
}
|
|
3928
4633
|
);
|
|
@@ -3939,8 +4644,10 @@ var BRAND_LABELS = {
|
|
|
3939
4644
|
function PaymentMethodDisplay({
|
|
3940
4645
|
paymentMethod,
|
|
3941
4646
|
onUpdatePaymentMethod,
|
|
3942
|
-
updateHref = "/billing/update-payment"
|
|
4647
|
+
updateHref = "/billing/update-payment",
|
|
4648
|
+
appearance
|
|
3943
4649
|
}) {
|
|
4650
|
+
const { root } = useAppearance(appearance);
|
|
3944
4651
|
const handleUpdate = () => {
|
|
3945
4652
|
if (onUpdatePaymentMethod) {
|
|
3946
4653
|
onUpdatePaymentMethod();
|
|
@@ -3948,7 +4655,7 @@ function PaymentMethodDisplay({
|
|
|
3948
4655
|
window.location.href = updateHref;
|
|
3949
4656
|
}
|
|
3950
4657
|
};
|
|
3951
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
4658
|
+
return /* @__PURE__ */ jsxs("div", { ...root("paymentMethod", { ...surface, display: "flex", flexDirection: "column", gap: "0.75rem" }), children: [
|
|
3952
4659
|
paymentMethod ? /* @__PURE__ */ jsxs(
|
|
3953
4660
|
"div",
|
|
3954
4661
|
{
|
|
@@ -3957,80 +4664,245 @@ function PaymentMethodDisplay({
|
|
|
3957
4664
|
alignItems: "center",
|
|
3958
4665
|
gap: "0.75rem",
|
|
3959
4666
|
padding: "0.75rem 1rem",
|
|
3960
|
-
border:
|
|
3961
|
-
borderRadius:
|
|
3962
|
-
fontSize: "0.875rem"
|
|
4667
|
+
border: border(),
|
|
4668
|
+
borderRadius: `calc(${T.radius} * 0.6)`
|
|
3963
4669
|
},
|
|
3964
4670
|
children: [
|
|
3965
|
-
/* @__PURE__ */ jsx(
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
"span",
|
|
3977
|
-
{
|
|
3978
|
-
style: {
|
|
3979
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))"
|
|
3980
|
-
},
|
|
3981
|
-
children: [
|
|
3982
|
-
"\u2022\u2022\u2022\u2022 ",
|
|
3983
|
-
paymentMethod.last4
|
|
3984
|
-
]
|
|
3985
|
-
}
|
|
3986
|
-
),
|
|
3987
|
-
/* @__PURE__ */ jsxs(
|
|
3988
|
-
"span",
|
|
3989
|
-
{
|
|
3990
|
-
style: {
|
|
3991
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))",
|
|
3992
|
-
fontSize: "0.75rem"
|
|
3993
|
-
},
|
|
3994
|
-
children: [
|
|
3995
|
-
"Expires ",
|
|
3996
|
-
String(paymentMethod.expMonth).padStart(2, "0"),
|
|
3997
|
-
"/",
|
|
3998
|
-
String(paymentMethod.expYear).slice(-2)
|
|
3999
|
-
]
|
|
4000
|
-
}
|
|
4001
|
-
)
|
|
4671
|
+
/* @__PURE__ */ jsx("span", { style: { fontWeight: 600, color: T.foreground }, children: BRAND_LABELS[paymentMethod.brand.toLowerCase()] ?? paymentMethod.brand }),
|
|
4672
|
+
/* @__PURE__ */ jsxs("span", { style: text.secondary, children: [
|
|
4673
|
+
"\u2022\u2022\u2022\u2022 ",
|
|
4674
|
+
paymentMethod.last4
|
|
4675
|
+
] }),
|
|
4676
|
+
/* @__PURE__ */ jsxs("span", { style: text.meta, children: [
|
|
4677
|
+
"Expires ",
|
|
4678
|
+
String(paymentMethod.expMonth).padStart(2, "0"),
|
|
4679
|
+
"/",
|
|
4680
|
+
String(paymentMethod.expYear).slice(-2)
|
|
4681
|
+
] })
|
|
4002
4682
|
]
|
|
4003
4683
|
}
|
|
4004
|
-
) : /* @__PURE__ */ jsx(
|
|
4005
|
-
"p",
|
|
4006
|
-
{
|
|
4007
|
-
style: {
|
|
4008
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))",
|
|
4009
|
-
fontSize: "0.875rem"
|
|
4010
|
-
},
|
|
4011
|
-
children: "No payment method on file."
|
|
4012
|
-
}
|
|
4013
|
-
),
|
|
4684
|
+
) : /* @__PURE__ */ jsx("p", { style: { ...text.secondary, margin: 0 }, children: "No payment method on file." }),
|
|
4014
4685
|
/* @__PURE__ */ jsx(
|
|
4015
4686
|
"button",
|
|
4016
4687
|
{
|
|
4688
|
+
type: "button",
|
|
4017
4689
|
onClick: handleUpdate,
|
|
4018
4690
|
style: {
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
color: "var(--nozle-primary, var(--primary))",
|
|
4691
|
+
...buttonStyle("outline"),
|
|
4692
|
+
alignSelf: "flex-start",
|
|
4022
4693
|
padding: "0.375rem 0.875rem",
|
|
4023
|
-
borderRadius:
|
|
4024
|
-
fontSize: "0.875rem",
|
|
4025
|
-
fontWeight: 500,
|
|
4026
|
-
cursor: "pointer"
|
|
4694
|
+
borderRadius: buttonRadius(`calc(${T.radius} / 2)`)
|
|
4027
4695
|
},
|
|
4028
4696
|
children: "Update Payment Method"
|
|
4029
4697
|
}
|
|
4030
4698
|
)
|
|
4031
4699
|
] });
|
|
4032
4700
|
}
|
|
4701
|
+
var SOURCE_LABELS = {
|
|
4702
|
+
subscription_grant: "Plan grant",
|
|
4703
|
+
top_up: "Top-up",
|
|
4704
|
+
manual_grant: "Granted",
|
|
4705
|
+
adjustment: "Adjustment",
|
|
4706
|
+
allocated_top_up: "Allocated top-up"
|
|
4707
|
+
};
|
|
4708
|
+
var muted = T.mutedForeground;
|
|
4709
|
+
function date2(value) {
|
|
4710
|
+
const parsed = new Date(value);
|
|
4711
|
+
return Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleDateString("en-US", { dateStyle: "medium" });
|
|
4712
|
+
}
|
|
4713
|
+
function CreditBalance({
|
|
4714
|
+
balance,
|
|
4715
|
+
isLoading = false,
|
|
4716
|
+
error = null,
|
|
4717
|
+
emptyMessage = "No credit balance yet.",
|
|
4718
|
+
appearance
|
|
4719
|
+
}) {
|
|
4720
|
+
const { root, el } = useAppearance(appearance);
|
|
4721
|
+
if (isLoading) return /* @__PURE__ */ jsx("div", { ...root("creditBalance", { ...surface, height: 96, background: T.skeleton }), role: "status", "aria-label": "Loading credit balance" });
|
|
4722
|
+
if (error) return /* @__PURE__ */ jsx("p", { ...root("creditBalance", { ...surface, color: T.dangerText }), role: "alert", children: typeof error === "string" ? error : error.message });
|
|
4723
|
+
if (!balance) return /* @__PURE__ */ jsx("p", { ...root("creditBalance", { ...surface, color: muted }), role: "status", children: emptyMessage });
|
|
4724
|
+
const sources = (balance.sources ?? []).filter((source) => source.remaining !== "0");
|
|
4725
|
+
return /* @__PURE__ */ jsxs("section", { "aria-label": balance.credit_system_name, ...root("creditBalance", surface), children: [
|
|
4726
|
+
/* @__PURE__ */ jsx("h3", { style: { ...text.heading, margin: 0 }, children: balance.credit_system_name }),
|
|
4727
|
+
/* @__PURE__ */ jsxs("p", { style: { margin: "0.25rem 0 0" }, children: [
|
|
4728
|
+
/* @__PURE__ */ jsx("span", { ...el("creditBalance__amount", text.metric), children: formatDecimalString(balance.available) }),
|
|
4729
|
+
" ",
|
|
4730
|
+
/* @__PURE__ */ jsxs("span", { style: text.secondary, children: [
|
|
4731
|
+
creditUnitLabel(balance.available, balance.unit_name),
|
|
4732
|
+
" available"
|
|
4733
|
+
] })
|
|
4734
|
+
] }),
|
|
4735
|
+
/* @__PURE__ */ jsxs("p", { style: { ...text.meta, margin: "0.25rem 0 0" }, children: [
|
|
4736
|
+
"As of ",
|
|
4737
|
+
date2(balance.as_of)
|
|
4738
|
+
] }),
|
|
4739
|
+
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: [
|
|
4740
|
+
/* @__PURE__ */ jsx("span", { children: SOURCE_LABELS[source.type] ?? source.type }),
|
|
4741
|
+
/* @__PURE__ */ jsxs("span", { style: text.secondary, children: [
|
|
4742
|
+
formatDecimalString(source.remaining),
|
|
4743
|
+
source.expires_at ? ` \xB7 expires ${date2(source.expires_at)}` : ""
|
|
4744
|
+
] })
|
|
4745
|
+
] }, source.id)) })
|
|
4746
|
+
] });
|
|
4747
|
+
}
|
|
4748
|
+
var TYPE_LABELS = {
|
|
4749
|
+
consume: "Used",
|
|
4750
|
+
grant: "Granted",
|
|
4751
|
+
adjustment: "Adjustment",
|
|
4752
|
+
expire: "Expired",
|
|
4753
|
+
revoke: "Revoked",
|
|
4754
|
+
refund: "Refunded",
|
|
4755
|
+
transfer: "Transfer"
|
|
4756
|
+
};
|
|
4757
|
+
var muted2 = T.mutedForeground;
|
|
4758
|
+
function when(value) {
|
|
4759
|
+
const parsed = new Date(value);
|
|
4760
|
+
return Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short" });
|
|
4761
|
+
}
|
|
4762
|
+
function CreditHistory({
|
|
4763
|
+
operations = [],
|
|
4764
|
+
isLoading = false,
|
|
4765
|
+
error = null,
|
|
4766
|
+
emptyMessage = "No credit activity yet.",
|
|
4767
|
+
hasMore = false,
|
|
4768
|
+
onLoadMore,
|
|
4769
|
+
appearance
|
|
4770
|
+
}) {
|
|
4771
|
+
const { root, el } = useAppearance(appearance);
|
|
4772
|
+
if (error) return /* @__PURE__ */ jsx("p", { ...root("creditHistory", { ...surface, color: T.dangerText }), role: "alert", children: typeof error === "string" ? error : error.message });
|
|
4773
|
+
if (isLoading && operations.length === 0) return /* @__PURE__ */ jsx("div", { ...root("creditHistory", { ...surface, height: 120, background: T.skeleton }), role: "status", "aria-label": "Loading credit history" });
|
|
4774
|
+
if (operations.length === 0) return /* @__PURE__ */ jsx("p", { ...root("creditHistory", { ...surface, color: muted2 }), role: "status", children: emptyMessage });
|
|
4775
|
+
return /* @__PURE__ */ jsxs("div", { ...root("creditHistory", { ...text.body, minWidth: 0 }), children: [
|
|
4776
|
+
/* @__PURE__ */ jsx("div", { style: table.frame, children: /* @__PURE__ */ jsxs("table", { ...el("creditHistory__table", table.table), children: [
|
|
4777
|
+
/* @__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)) }) }),
|
|
4778
|
+
/* @__PURE__ */ jsx("tbody", { children: operations.map((operation, index) => {
|
|
4779
|
+
const last = index === operations.length - 1;
|
|
4780
|
+
return /* @__PURE__ */ jsxs("tr", { ...el("creditHistory__row"), children: [
|
|
4781
|
+
/* @__PURE__ */ jsx("td", { style: table.cell(false, last), children: when(operation.occurred_at) }),
|
|
4782
|
+
/* @__PURE__ */ jsx("td", { style: table.cell(false, last), children: TYPE_LABELS[operation.type] ?? operation.type }),
|
|
4783
|
+
/* @__PURE__ */ jsx("td", { style: table.cell(false, last), children: operation.feature_code ?? "\u2014" }),
|
|
4784
|
+
/* @__PURE__ */ jsx("td", { style: table.cell(true, last), children: formatDecimalString(operation.credit_amount) }),
|
|
4785
|
+
/* @__PURE__ */ jsx("td", { style: { ...table.cell(false, last), color: operation.status === "succeeded" ? void 0 : muted2 }, children: operation.status })
|
|
4786
|
+
] }, operation.id);
|
|
4787
|
+
}) })
|
|
4788
|
+
] }) }),
|
|
4789
|
+
hasMore && onLoadMore && /* @__PURE__ */ jsx(
|
|
4790
|
+
"button",
|
|
4791
|
+
{
|
|
4792
|
+
type: "button",
|
|
4793
|
+
onClick: onLoadMore,
|
|
4794
|
+
disabled: isLoading,
|
|
4795
|
+
style: {
|
|
4796
|
+
...buttonStyle("secondary", isLoading),
|
|
4797
|
+
marginTop: "0.75rem",
|
|
4798
|
+
padding: "0.5rem 0.875rem",
|
|
4799
|
+
borderRadius: buttonRadius(`calc(${T.radius} / 2)`),
|
|
4800
|
+
opacity: 1
|
|
4801
|
+
},
|
|
4802
|
+
children: isLoading ? "Loading\u2026" : "Load more"
|
|
4803
|
+
}
|
|
4804
|
+
)
|
|
4805
|
+
] });
|
|
4806
|
+
}
|
|
4807
|
+
|
|
4808
|
+
// src/components/usage/usage-data.ts
|
|
4809
|
+
function formatAmount(amountCents, currency) {
|
|
4810
|
+
const amount = amountCents / 100;
|
|
4811
|
+
try {
|
|
4812
|
+
return new Intl.NumberFormat("en-US", { style: "currency", currency }).format(amount);
|
|
4813
|
+
} catch {
|
|
4814
|
+
return `${currency} ${amount.toFixed(2)}`;
|
|
4815
|
+
}
|
|
4816
|
+
}
|
|
4817
|
+
function formatDay(value) {
|
|
4818
|
+
const parsed = new Date(value);
|
|
4819
|
+
return Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleDateString("en-US", { dateStyle: "medium", timeZone: "UTC" });
|
|
4820
|
+
}
|
|
4821
|
+
var muted3 = T.mutedForeground;
|
|
4822
|
+
function UsageBreakdown({
|
|
4823
|
+
usage,
|
|
4824
|
+
isLoading = false,
|
|
4825
|
+
error = null,
|
|
4826
|
+
emptyMessage = "No usage in this period yet.",
|
|
4827
|
+
appearance
|
|
4828
|
+
}) {
|
|
4829
|
+
const { root, el } = useAppearance(appearance);
|
|
4830
|
+
if (isLoading) return /* @__PURE__ */ jsx("div", { ...root("usageBreakdown", { ...surface, height: 120, background: T.skeleton }), role: "status", "aria-label": "Loading usage" });
|
|
4831
|
+
if (error) return /* @__PURE__ */ jsx("p", { ...root("usageBreakdown", { ...surface, color: T.dangerText }), role: "alert", children: typeof error === "string" ? error : error.message });
|
|
4832
|
+
if (!usage || usage.charges_usage.length === 0) return /* @__PURE__ */ jsx("p", { ...root("usageBreakdown", { ...surface, color: muted3 }), role: "status", children: emptyMessage });
|
|
4833
|
+
const rows = [...usage.charges_usage].sort((a, b) => b.amount_cents - a.amount_cents);
|
|
4834
|
+
return /* @__PURE__ */ jsxs("section", { ...root("usageBreakdown", { ...text.body, minWidth: 0 }), children: [
|
|
4835
|
+
/* @__PURE__ */ jsxs("p", { style: { ...text.secondary, margin: "0 0 0.5rem" }, children: [
|
|
4836
|
+
formatDay(usage.from_datetime),
|
|
4837
|
+
" to ",
|
|
4838
|
+
formatDay(usage.to_datetime)
|
|
4839
|
+
] }),
|
|
4840
|
+
/* @__PURE__ */ jsx("div", { style: table.frame, children: /* @__PURE__ */ jsxs("table", { style: table.table, children: [
|
|
4841
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
4842
|
+
/* @__PURE__ */ jsx("th", { style: table.head(), children: "Feature" }),
|
|
4843
|
+
/* @__PURE__ */ jsx("th", { style: table.head(true), children: "Units" }),
|
|
4844
|
+
/* @__PURE__ */ jsx("th", { style: table.head(true), children: "Amount" })
|
|
4845
|
+
] }) }),
|
|
4846
|
+
/* @__PURE__ */ jsx("tbody", { children: rows.map((row, index) => {
|
|
4847
|
+
const last = index === rows.length - 1;
|
|
4848
|
+
return /* @__PURE__ */ jsxs("tr", { ...el("usageBreakdown__row"), children: [
|
|
4849
|
+
/* @__PURE__ */ jsx("td", { style: table.cell(false, last), children: row.charge.invoice_display_name || row.feature.name }),
|
|
4850
|
+
/* @__PURE__ */ jsx("td", { style: table.cell(true, last), children: formatDecimalString(row.units, 6) }),
|
|
4851
|
+
/* @__PURE__ */ jsx("td", { style: table.cell(true, last), children: formatAmount(row.amount_cents, row.amount_currency) })
|
|
4852
|
+
] }, row.charge.nozle_id);
|
|
4853
|
+
}) }),
|
|
4854
|
+
/* @__PURE__ */ jsx("tfoot", { children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
4855
|
+
/* @__PURE__ */ jsx("td", { style: table.foot(), colSpan: 2, children: "Total" }),
|
|
4856
|
+
/* @__PURE__ */ jsx("td", { style: table.foot(true), children: formatAmount(usage.amount_cents, usage.currency) })
|
|
4857
|
+
] }) })
|
|
4858
|
+
] }) })
|
|
4859
|
+
] });
|
|
4860
|
+
}
|
|
4861
|
+
var muted4 = T.mutedForeground;
|
|
4862
|
+
var BAR_AREA = 120;
|
|
4863
|
+
function unitsFor(period, featureCode) {
|
|
4864
|
+
return period.charges_usage.filter((charge) => charge.feature.code === featureCode).reduce((sum, charge) => sum + (Number(charge.units) || 0), 0);
|
|
4865
|
+
}
|
|
4866
|
+
function UsageTrend({
|
|
4867
|
+
periods,
|
|
4868
|
+
featureCode,
|
|
4869
|
+
isLoading = false,
|
|
4870
|
+
error = null,
|
|
4871
|
+
emptyMessage = "No past usage yet.",
|
|
4872
|
+
appearance
|
|
4873
|
+
}) {
|
|
4874
|
+
const { root, el } = useAppearance(appearance);
|
|
4875
|
+
if (isLoading) return /* @__PURE__ */ jsx("div", { ...root("usageTrend", { ...surface, height: 160, background: T.skeleton }), role: "status", "aria-label": "Loading usage trend" });
|
|
4876
|
+
if (error) return /* @__PURE__ */ jsx("p", { ...root("usageTrend", { ...surface, color: T.dangerText }), role: "alert", children: typeof error === "string" ? error : error.message });
|
|
4877
|
+
if (!periods || periods.length === 0) return /* @__PURE__ */ jsx("p", { ...root("usageTrend", { ...surface, color: muted4 }), role: "status", children: emptyMessage });
|
|
4878
|
+
const ordered = [...periods].sort((a, b) => Date.parse(a.from_datetime) - Date.parse(b.from_datetime));
|
|
4879
|
+
const points = ordered.map((period) => ({
|
|
4880
|
+
key: period.from_datetime,
|
|
4881
|
+
label: formatDay(period.from_datetime),
|
|
4882
|
+
value: featureCode ? unitsFor(period, featureCode) : period.amount_cents,
|
|
4883
|
+
text: featureCode ? `${unitsFor(period, featureCode).toLocaleString("en-US")} ${featureCode}` : formatAmount(period.amount_cents, period.currency)
|
|
4884
|
+
}));
|
|
4885
|
+
const max = Math.max(...points.map((point) => point.value), 0);
|
|
4886
|
+
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: [
|
|
4887
|
+
/* @__PURE__ */ jsx("div", { children: point.text }),
|
|
4888
|
+
/* @__PURE__ */ jsx(
|
|
4889
|
+
"div",
|
|
4890
|
+
{
|
|
4891
|
+
role: "img",
|
|
4892
|
+
"aria-label": `${point.label}: ${point.text}`,
|
|
4893
|
+
title: `${point.label}: ${point.text}`,
|
|
4894
|
+
...el("usageTrend__bar", {
|
|
4895
|
+
height: max > 0 ? Math.max(2, point.value / max * BAR_AREA) : 2,
|
|
4896
|
+
marginTop: 4,
|
|
4897
|
+
borderRadius: `min(4px, ${T.radius}) min(4px, ${T.radius}) 0 0`,
|
|
4898
|
+
background: T.primary
|
|
4899
|
+
})
|
|
4900
|
+
}
|
|
4901
|
+
),
|
|
4902
|
+
/* @__PURE__ */ jsx("div", { style: { marginTop: 4 }, children: point.label })
|
|
4903
|
+
] }, point.key)) }) });
|
|
4904
|
+
}
|
|
4033
4905
|
|
|
4034
|
-
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 };
|
|
4906
|
+
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, getUsageMeterColor, planFeatures, sortPlansByPrice, usageItems, useAppearance, useBillingContext, useBillingRefresh, useCheckout, useCheckout2 as useCheckoutSession, useCreditBalance, useCreditHistory, useEntitlement, useInvoiceDownload, useInvoices, useNozleBranding, useNozleClient, useOptionalBillingContext, usePlans, useSubscription, useUpgradePreview, useUsage };
|
|
4035
4907
|
//# sourceMappingURL=index.js.map
|
|
4036
4908
|
//# sourceMappingURL=index.js.map
|