@nozle-js/react 0.10.2 → 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/README.md +3 -1
- package/dist/chunk-NLPONQ5O.js +246 -0
- package/dist/chunk-NLPONQ5O.js.map +1 -0
- package/dist/index.cjs +2192 -1060
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +716 -69
- package/dist/index.d.ts +716 -69
- package/dist/index.js +1802 -931
- package/dist/index.js.map +1 -1
- package/dist/styles/preset.cjs.map +1 -1
- package/dist/styles/preset.d.cts +4 -0
- package/dist/styles/preset.d.ts +4 -0
- package/dist/styles/preset.js.map +1 -1
- package/dist/themes.cjs +230 -0
- package/dist/themes.cjs.map +1 -0
- package/dist/themes.d.cts +27 -0
- package/dist/themes.d.ts +27 -0
- package/dist/themes.js +126 -0
- package/dist/themes.js.map +1 -0
- package/dist/tokens-Bm4VWWb0.d.cts +82 -0
- package/dist/tokens-Bm4VWWb0.d.ts +82 -0
- package/package.json +22 -6
package/dist/index.js
CHANGED
|
@@ -1,9 +1,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,41 +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 (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
3228
|
-
const data = await res.json();
|
|
3229
|
-
if (!cancelled) setPlans(data.plans || []);
|
|
3230
|
-
}).catch((cause) => {
|
|
3231
|
-
if (!cancelled) setCheckoutError(`Plans could not be loaded: ${cause instanceof Error ? cause.message : "unknown error"}`);
|
|
3232
|
-
}).finally(() => {
|
|
3233
|
-
if (!cancelled) setLoading(false);
|
|
3234
|
-
});
|
|
3235
|
-
return () => {
|
|
3236
|
-
cancelled = true;
|
|
3237
|
-
};
|
|
3238
|
-
}, [client, propPlans]);
|
|
3239
3824
|
useEffect(() => {
|
|
3240
3825
|
if (currentPlanCode !== void 0) setCurrentPlan(currentPlanCode);
|
|
3241
3826
|
}, [currentPlanCode]);
|
|
@@ -3268,124 +3853,123 @@ function PricingTable({
|
|
|
3268
3853
|
onCheckoutError?.(error);
|
|
3269
3854
|
}
|
|
3270
3855
|
}
|
|
3271
|
-
const
|
|
3272
|
-
const
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
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 }),
|
|
3280
3864
|
processing && /* @__PURE__ */ jsx("p", { role: "status", children: "Payment is processing. Confirmation may take a little while." }),
|
|
3281
|
-
checkoutError && /* @__PURE__ */ jsx("p", { role: "alert", children: checkoutError }),
|
|
3282
|
-
/* @__PURE__ */ jsx("div", {
|
|
3865
|
+
(checkoutError ?? loadError) && /* @__PURE__ */ jsx("p", { role: "alert", style: { color: T.dangerText }, children: checkoutError ?? loadError }),
|
|
3866
|
+
/* @__PURE__ */ jsx("div", { ...el("pricingTable__grid", {
|
|
3283
3867
|
display: "grid",
|
|
3284
3868
|
gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
|
|
3285
3869
|
gap: "1.5rem"
|
|
3286
|
-
}, children: filtered.map((plan, i) => /* @__PURE__ */ jsx(
|
|
3870
|
+
}), children: filtered.map((plan, i) => /* @__PURE__ */ jsx(
|
|
3287
3871
|
Card,
|
|
3288
3872
|
{
|
|
3289
3873
|
plan,
|
|
3290
|
-
features: features
|
|
3874
|
+
features: featureLines(features, plan, i),
|
|
3291
3875
|
isCurrent: currentPlan === plan.code,
|
|
3876
|
+
isUpgrade: current && current.amount_currency === plan.amount_currency ? plan.amount_cents > current.amount_cents : void 0,
|
|
3292
3877
|
isHighlighted: highlightPlan === plan.code,
|
|
3878
|
+
filled: highlightStyle === "filled",
|
|
3879
|
+
highlightLabel,
|
|
3293
3880
|
isEnt: isEnterprise(plan),
|
|
3294
3881
|
enterpriseEmail,
|
|
3295
|
-
onSelect: () => void selectPlan(plan)
|
|
3882
|
+
onSelect: () => void selectPlan(plan),
|
|
3883
|
+
el,
|
|
3884
|
+
panel: planCardLayout === "panel"
|
|
3296
3885
|
},
|
|
3297
3886
|
plan.code
|
|
3298
|
-
)) })
|
|
3887
|
+
)) }),
|
|
3888
|
+
/* @__PURE__ */ jsx(NozleBranding, {})
|
|
3299
3889
|
] });
|
|
3300
3890
|
}
|
|
3301
3891
|
function PlanCard({
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
monthlyPrice,
|
|
3305
|
-
annualPrice,
|
|
3306
|
-
features,
|
|
3307
|
-
description,
|
|
3308
|
-
isAnnual,
|
|
3892
|
+
plan,
|
|
3893
|
+
features: featureOverride,
|
|
3309
3894
|
isCurrent,
|
|
3310
3895
|
returnUrl,
|
|
3311
|
-
onSelect
|
|
3896
|
+
onSelect,
|
|
3897
|
+
appearance
|
|
3312
3898
|
}) {
|
|
3313
|
-
const
|
|
3314
|
-
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
|
+
}
|
|
3315
3940
|
return /* @__PURE__ */ jsxs(
|
|
3316
3941
|
"div",
|
|
3317
3942
|
{
|
|
3318
3943
|
"data-testid": `plan-card-${id}`,
|
|
3319
3944
|
"aria-current": isCurrent ? "true" : void 0,
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
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,
|
|
3324
3952
|
padding: "1.5rem",
|
|
3325
3953
|
display: "flex",
|
|
3326
3954
|
flexDirection: "column",
|
|
3327
3955
|
gap: "1rem"
|
|
3328
|
-
},
|
|
3329
|
-
className: isCurrent ? "border-primary" : "",
|
|
3956
|
+
}),
|
|
3330
3957
|
children: [
|
|
3331
3958
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
3332
|
-
/* @__PURE__ */ jsx(
|
|
3333
|
-
|
|
3334
|
-
{
|
|
3335
|
-
style: {
|
|
3336
|
-
fontSize: "1.25rem",
|
|
3337
|
-
fontWeight: 600,
|
|
3338
|
-
color: "var(--nozle-foreground, var(--foreground))",
|
|
3339
|
-
margin: 0
|
|
3340
|
-
},
|
|
3341
|
-
children: name
|
|
3342
|
-
}
|
|
3343
|
-
),
|
|
3344
|
-
description && /* @__PURE__ */ jsx(
|
|
3345
|
-
"p",
|
|
3346
|
-
{
|
|
3347
|
-
style: {
|
|
3348
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))",
|
|
3349
|
-
fontSize: "0.875rem",
|
|
3350
|
-
margin: "0.25rem 0 0"
|
|
3351
|
-
},
|
|
3352
|
-
children: description
|
|
3353
|
-
}
|
|
3354
|
-
)
|
|
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 })
|
|
3355
3961
|
] }),
|
|
3356
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
3357
|
-
/* @__PURE__ */
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
fontWeight: 700,
|
|
3363
|
-
color: "var(--nozle-foreground, var(--foreground))"
|
|
3364
|
-
},
|
|
3365
|
-
children: [
|
|
3366
|
-
"$",
|
|
3367
|
-
price
|
|
3368
|
-
]
|
|
3369
|
-
}
|
|
3370
|
-
),
|
|
3371
|
-
/* @__PURE__ */ jsxs(
|
|
3372
|
-
"span",
|
|
3373
|
-
{
|
|
3374
|
-
style: {
|
|
3375
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))",
|
|
3376
|
-
marginLeft: "0.25rem"
|
|
3377
|
-
},
|
|
3378
|
-
children: [
|
|
3379
|
-
"/",
|
|
3380
|
-
interval
|
|
3381
|
-
]
|
|
3382
|
-
}
|
|
3383
|
-
)
|
|
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
|
+
] })
|
|
3384
3968
|
] }),
|
|
3385
3969
|
/* @__PURE__ */ jsx(
|
|
3386
3970
|
"ul",
|
|
3387
3971
|
{
|
|
3388
|
-
|
|
3972
|
+
...el("planCard__features", {
|
|
3389
3973
|
listStyle: "none",
|
|
3390
3974
|
padding: 0,
|
|
3391
3975
|
margin: 0,
|
|
@@ -3393,160 +3977,51 @@ function PlanCard({
|
|
|
3393
3977
|
flexDirection: "column",
|
|
3394
3978
|
gap: "0.5rem",
|
|
3395
3979
|
flex: 1
|
|
3396
|
-
},
|
|
3397
|
-
children: features.map((feature) => /* @__PURE__ */ jsxs(
|
|
3398
|
-
"
|
|
3399
|
-
{
|
|
3400
|
-
style: {
|
|
3401
|
-
display: "flex",
|
|
3402
|
-
alignItems: "center",
|
|
3403
|
-
gap: "0.5rem",
|
|
3404
|
-
color: "var(--nozle-foreground, var(--foreground))",
|
|
3405
|
-
fontSize: "0.875rem"
|
|
3406
|
-
},
|
|
3407
|
-
children: [
|
|
3408
|
-
/* @__PURE__ */ jsx(
|
|
3409
|
-
"span",
|
|
3410
|
-
{
|
|
3411
|
-
style: { color: "var(--nozle-primary, var(--primary))" },
|
|
3412
|
-
"aria-hidden": "true",
|
|
3413
|
-
children: "\u2713"
|
|
3414
|
-
}
|
|
3415
|
-
),
|
|
3416
|
-
feature
|
|
3417
|
-
]
|
|
3418
|
-
},
|
|
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" }),
|
|
3419
3983
|
feature
|
|
3420
|
-
))
|
|
3984
|
+
] }, feature))
|
|
3421
3985
|
}
|
|
3422
3986
|
),
|
|
3423
|
-
|
|
3424
|
-
"button",
|
|
3425
|
-
{
|
|
3426
|
-
onClick: () => onSelect?.({
|
|
3427
|
-
planCode: id,
|
|
3428
|
-
returnUrl: returnUrl ?? window.location.href
|
|
3429
|
-
}),
|
|
3430
|
-
style: {
|
|
3431
|
-
background: isCurrent ? "var(--nozle-muted, var(--muted))" : "var(--nozle-primary, var(--primary))",
|
|
3432
|
-
color: isCurrent ? "var(--nozle-muted-foreground, var(--muted-foreground))" : "var(--nozle-primary-foreground, var(--primary-foreground))",
|
|
3433
|
-
border: "none",
|
|
3434
|
-
borderRadius: "var(--nozle-radius, 0.5rem)",
|
|
3435
|
-
padding: "0.75rem 1.5rem",
|
|
3436
|
-
cursor: isCurrent ? "default" : "pointer",
|
|
3437
|
-
fontWeight: 500,
|
|
3438
|
-
width: "100%"
|
|
3439
|
-
},
|
|
3440
|
-
disabled: isCurrent,
|
|
3441
|
-
"aria-label": isCurrent ? `Current plan: ${name}` : `Select ${name}`,
|
|
3442
|
-
children: isCurrent ? "Current Plan" : "Get Started"
|
|
3443
|
-
}
|
|
3444
|
-
)
|
|
3987
|
+
cta
|
|
3445
3988
|
]
|
|
3446
3989
|
}
|
|
3447
3990
|
);
|
|
3448
3991
|
}
|
|
3449
3992
|
function PlanComparison({
|
|
3450
|
-
plans,
|
|
3451
|
-
features
|
|
3993
|
+
plans: catalogPlans,
|
|
3994
|
+
features: featureOverride,
|
|
3995
|
+
sort,
|
|
3996
|
+
appearance
|
|
3452
3997
|
}) {
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
"table",
|
|
3462
|
-
|
|
3463
|
-
style: {
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
},
|
|
3481
|
-
children: "Feature"
|
|
3482
|
-
}
|
|
3483
|
-
),
|
|
3484
|
-
plans.map((plan) => /* @__PURE__ */ jsx(
|
|
3485
|
-
"th",
|
|
3486
|
-
{
|
|
3487
|
-
style: {
|
|
3488
|
-
padding: "0.75rem 1rem",
|
|
3489
|
-
textAlign: "center",
|
|
3490
|
-
borderBottom: "1px solid var(--nozle-border, var(--border))",
|
|
3491
|
-
fontWeight: 600,
|
|
3492
|
-
color: "var(--nozle-foreground, var(--foreground))"
|
|
3493
|
-
},
|
|
3494
|
-
children: plan.name
|
|
3495
|
-
},
|
|
3496
|
-
plan.id
|
|
3497
|
-
))
|
|
3498
|
-
] }) }),
|
|
3499
|
-
/* @__PURE__ */ jsx("tbody", { children: features.map((feature) => /* @__PURE__ */ jsxs("tr", { children: [
|
|
3500
|
-
/* @__PURE__ */ jsx(
|
|
3501
|
-
"td",
|
|
3502
|
-
{
|
|
3503
|
-
style: {
|
|
3504
|
-
padding: "0.75rem 1rem",
|
|
3505
|
-
borderBottom: "1px solid var(--nozle-border, var(--border))",
|
|
3506
|
-
color: "var(--nozle-foreground, var(--foreground))"
|
|
3507
|
-
},
|
|
3508
|
-
children: feature.label
|
|
3509
|
-
}
|
|
3510
|
-
),
|
|
3511
|
-
plans.map((plan) => {
|
|
3512
|
-
const value = feature.values[plan.id];
|
|
3513
|
-
return /* @__PURE__ */ jsx(
|
|
3514
|
-
"td",
|
|
3515
|
-
{
|
|
3516
|
-
style: {
|
|
3517
|
-
padding: "0.75rem 1rem",
|
|
3518
|
-
textAlign: "center",
|
|
3519
|
-
borderBottom: "1px solid var(--nozle-border, var(--border))"
|
|
3520
|
-
},
|
|
3521
|
-
children: typeof value === "boolean" ? value ? /* @__PURE__ */ jsx(
|
|
3522
|
-
"span",
|
|
3523
|
-
{
|
|
3524
|
-
style: {
|
|
3525
|
-
color: "var(--nozle-primary, var(--primary))"
|
|
3526
|
-
},
|
|
3527
|
-
"aria-label": "Included",
|
|
3528
|
-
children: "\u2713"
|
|
3529
|
-
}
|
|
3530
|
-
) : /* @__PURE__ */ jsx(
|
|
3531
|
-
"span",
|
|
3532
|
-
{
|
|
3533
|
-
style: {
|
|
3534
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))"
|
|
3535
|
-
},
|
|
3536
|
-
"aria-label": "Not included",
|
|
3537
|
-
children: "\u2715"
|
|
3538
|
-
}
|
|
3539
|
-
) : /* @__PURE__ */ jsx("span", { children: value ?? "\u2014" })
|
|
3540
|
-
},
|
|
3541
|
-
plan.id
|
|
3542
|
-
);
|
|
3543
|
-
})
|
|
3544
|
-
] }, feature.key)) })
|
|
3545
|
-
]
|
|
3546
|
-
}
|
|
3547
|
-
)
|
|
3548
|
-
}
|
|
3549
|
-
);
|
|
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
|
+
] });
|
|
3550
4025
|
}
|
|
3551
4026
|
function UpgradeButton({
|
|
3552
4027
|
planCode,
|
|
@@ -3562,26 +4037,18 @@ function UpgradeButton({
|
|
|
3562
4037
|
onCheckoutStarted,
|
|
3563
4038
|
onDowngradeScheduled,
|
|
3564
4039
|
onUpgraded,
|
|
3565
|
-
onError
|
|
4040
|
+
onError,
|
|
4041
|
+
appearance
|
|
3566
4042
|
}) {
|
|
3567
4043
|
const [isOpen, setIsOpen] = useState(false);
|
|
4044
|
+
const { root } = useAppearance(appearance);
|
|
3568
4045
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3569
4046
|
/* @__PURE__ */ jsx(
|
|
3570
4047
|
"button",
|
|
3571
4048
|
{
|
|
3572
4049
|
type: "button",
|
|
3573
4050
|
onClick: () => setIsOpen(true),
|
|
3574
|
-
className,
|
|
3575
|
-
style: {
|
|
3576
|
-
padding: "0.75rem 1.5rem",
|
|
3577
|
-
borderRadius: "var(--nozle-radius, 0.5rem)",
|
|
3578
|
-
border: "none",
|
|
3579
|
-
background: "var(--nozle-primary, var(--primary))",
|
|
3580
|
-
color: "var(--nozle-primary-foreground, var(--primary-foreground))",
|
|
3581
|
-
cursor: "pointer",
|
|
3582
|
-
fontWeight: 500,
|
|
3583
|
-
...style
|
|
3584
|
-
},
|
|
4051
|
+
...root("upgradeButton", { ...buttonStyle(), ...style }, className),
|
|
3585
4052
|
children: label
|
|
3586
4053
|
}
|
|
3587
4054
|
),
|
|
@@ -3589,6 +4056,7 @@ function UpgradeButton({
|
|
|
3589
4056
|
UpgradeModal,
|
|
3590
4057
|
{
|
|
3591
4058
|
isOpen,
|
|
4059
|
+
appearance,
|
|
3592
4060
|
planCode,
|
|
3593
4061
|
returnUrl,
|
|
3594
4062
|
subscriptionId,
|
|
@@ -3647,11 +4115,13 @@ function PlanGate({
|
|
|
3647
4115
|
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
3648
4116
|
}
|
|
3649
4117
|
function UpgradePrompt({
|
|
3650
|
-
text = "Upgrade to access this feature",
|
|
4118
|
+
text: text2 = "Upgrade to access this feature",
|
|
3651
4119
|
planName: planName2,
|
|
3652
|
-
onUpgrade
|
|
4120
|
+
onUpgrade,
|
|
4121
|
+
appearance
|
|
3653
4122
|
}) {
|
|
3654
|
-
const
|
|
4123
|
+
const { root } = useAppearance(appearance);
|
|
4124
|
+
const displayText = planName2 ? `Upgrade to ${planName2} to access this feature` : text2;
|
|
3655
4125
|
function handleUpgrade() {
|
|
3656
4126
|
if (onUpgrade) {
|
|
3657
4127
|
onUpgrade();
|
|
@@ -3662,40 +4132,23 @@ function UpgradePrompt({
|
|
|
3662
4132
|
return /* @__PURE__ */ jsxs(
|
|
3663
4133
|
"div",
|
|
3664
4134
|
{
|
|
3665
|
-
|
|
4135
|
+
...root("upgradePrompt", {
|
|
3666
4136
|
display: "flex",
|
|
3667
4137
|
flexDirection: "column",
|
|
3668
4138
|
alignItems: "center",
|
|
3669
4139
|
gap: "0.75rem",
|
|
3670
4140
|
padding: "1rem",
|
|
3671
|
-
textAlign: "center"
|
|
3672
|
-
|
|
4141
|
+
textAlign: "center",
|
|
4142
|
+
...text.body
|
|
4143
|
+
}),
|
|
3673
4144
|
children: [
|
|
3674
|
-
/* @__PURE__ */ jsx(
|
|
3675
|
-
"p",
|
|
3676
|
-
{
|
|
3677
|
-
style: {
|
|
3678
|
-
color: "var(--nozle-foreground, var(--foreground))",
|
|
3679
|
-
fontSize: "0.875rem",
|
|
3680
|
-
margin: 0
|
|
3681
|
-
},
|
|
3682
|
-
children: displayText
|
|
3683
|
-
}
|
|
3684
|
-
),
|
|
4145
|
+
/* @__PURE__ */ jsx("p", { style: { margin: 0 }, children: displayText }),
|
|
3685
4146
|
/* @__PURE__ */ jsx(
|
|
3686
4147
|
"button",
|
|
3687
4148
|
{
|
|
4149
|
+
type: "button",
|
|
3688
4150
|
onClick: handleUpgrade,
|
|
3689
|
-
style: {
|
|
3690
|
-
padding: "0.5rem 1.25rem",
|
|
3691
|
-
borderRadius: "var(--nozle-radius, 0.5rem)",
|
|
3692
|
-
border: "none",
|
|
3693
|
-
background: "var(--nozle-primary, var(--primary))",
|
|
3694
|
-
color: "var(--nozle-primary-foreground, var(--primary-foreground))",
|
|
3695
|
-
cursor: "pointer",
|
|
3696
|
-
fontWeight: 500,
|
|
3697
|
-
fontSize: "0.875rem"
|
|
3698
|
-
},
|
|
4151
|
+
style: { ...buttonStyle(), padding: "0.5rem 1.25rem" },
|
|
3699
4152
|
children: "Upgrade"
|
|
3700
4153
|
}
|
|
3701
4154
|
)
|
|
@@ -3708,12 +4161,14 @@ function LockedOverlay({
|
|
|
3708
4161
|
upgradeText,
|
|
3709
4162
|
upgradePlanName,
|
|
3710
4163
|
onUpgrade,
|
|
3711
|
-
children
|
|
4164
|
+
children,
|
|
4165
|
+
appearance
|
|
3712
4166
|
}) {
|
|
4167
|
+
const { root, el } = useAppearance(appearance);
|
|
3713
4168
|
if (!locked) {
|
|
3714
4169
|
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
3715
4170
|
}
|
|
3716
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
4171
|
+
return /* @__PURE__ */ jsxs("div", { ...root("lockedOverlay", { position: "relative", display: "inline-block" }), children: [
|
|
3717
4172
|
/* @__PURE__ */ jsx(
|
|
3718
4173
|
"div",
|
|
3719
4174
|
{
|
|
@@ -3729,16 +4184,15 @@ function LockedOverlay({
|
|
|
3729
4184
|
/* @__PURE__ */ jsx(
|
|
3730
4185
|
"div",
|
|
3731
4186
|
{
|
|
3732
|
-
|
|
4187
|
+
...el("lockedOverlay__panel", {
|
|
3733
4188
|
position: "absolute",
|
|
3734
4189
|
inset: 0,
|
|
3735
4190
|
display: "flex",
|
|
3736
4191
|
alignItems: "center",
|
|
3737
4192
|
justifyContent: "center",
|
|
3738
|
-
background:
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
},
|
|
4193
|
+
background: `color-mix(in srgb, ${T.background} 92%, transparent)`,
|
|
4194
|
+
borderRadius: T.radius
|
|
4195
|
+
}),
|
|
3742
4196
|
role: "dialog",
|
|
3743
4197
|
"aria-label": "Feature locked \u2014 upgrade required",
|
|
3744
4198
|
children: /* @__PURE__ */ jsx(
|
|
@@ -3746,7 +4200,8 @@ function LockedOverlay({
|
|
|
3746
4200
|
{
|
|
3747
4201
|
text: upgradeText,
|
|
3748
4202
|
planName: upgradePlanName,
|
|
3749
|
-
onUpgrade
|
|
4203
|
+
onUpgrade,
|
|
4204
|
+
appearance
|
|
3750
4205
|
}
|
|
3751
4206
|
)
|
|
3752
4207
|
}
|
|
@@ -3754,68 +4209,109 @@ function LockedOverlay({
|
|
|
3754
4209
|
] });
|
|
3755
4210
|
}
|
|
3756
4211
|
function getUsageMeterColor(percentage) {
|
|
3757
|
-
if (percentage >= 95) return
|
|
3758
|
-
if (percentage >= 80) return
|
|
3759
|
-
return
|
|
4212
|
+
if (percentage >= 95) return T.danger;
|
|
4213
|
+
if (percentage >= 80) return T.warning;
|
|
4214
|
+
return T.primary;
|
|
3760
4215
|
}
|
|
3761
4216
|
function UsageMeter({
|
|
3762
4217
|
used,
|
|
3763
4218
|
limit,
|
|
3764
4219
|
label,
|
|
3765
|
-
showText = true
|
|
4220
|
+
showText = true,
|
|
4221
|
+
appearance
|
|
3766
4222
|
}) {
|
|
3767
|
-
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;
|
|
3768
4226
|
const color = getUsageMeterColor(percentage);
|
|
3769
|
-
return /* @__PURE__ */ jsxs(
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
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(
|
|
3784
4247
|
"div",
|
|
3785
4248
|
{
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
}
|
|
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
|
+
)
|
|
3792
4267
|
}
|
|
3793
4268
|
)
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
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
|
+
});
|
|
3797
4279
|
}
|
|
3798
4280
|
function UsageDashboard({
|
|
3799
4281
|
features = [],
|
|
3800
|
-
loading = false
|
|
4282
|
+
loading = false,
|
|
4283
|
+
error = null,
|
|
4284
|
+
emptyMessage = "No usage to show yet.",
|
|
4285
|
+
appearance
|
|
3801
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
|
+
});
|
|
3802
4297
|
if (loading) {
|
|
3803
|
-
return /* @__PURE__ */ jsx("div", {
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
)) });
|
|
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 });
|
|
3811
4305
|
}
|
|
3812
|
-
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)) });
|
|
3813
4307
|
}
|
|
3814
4308
|
function UsageAlert({
|
|
3815
4309
|
features,
|
|
3816
4310
|
threshold = 80,
|
|
3817
|
-
upgradeHref = "/plans"
|
|
4311
|
+
upgradeHref = "/plans",
|
|
4312
|
+
appearance
|
|
3818
4313
|
}) {
|
|
4314
|
+
const { root, el } = useAppearance(appearance);
|
|
3819
4315
|
const [dismissed, setDismissed] = useState(/* @__PURE__ */ new Set());
|
|
3820
4316
|
const alertFeatures = features.filter(
|
|
3821
4317
|
(f) => f.percentage >= threshold && !dismissed.has(f.key)
|
|
@@ -3826,15 +4322,19 @@ function UsageAlert({
|
|
|
3826
4322
|
const handleDismiss = (key) => {
|
|
3827
4323
|
setDismissed((prev) => new Set(prev).add(key));
|
|
3828
4324
|
};
|
|
3829
|
-
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(
|
|
3830
4326
|
"div",
|
|
3831
4327
|
{
|
|
3832
4328
|
role: "alert",
|
|
3833
|
-
className: "flex items-center justify-between rounded-md px-4 py-3 text-sm",
|
|
3834
4329
|
style: {
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
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)
|
|
3838
4338
|
},
|
|
3839
4339
|
children: [
|
|
3840
4340
|
/* @__PURE__ */ jsxs("span", { children: [
|
|
@@ -3857,8 +4357,8 @@ function UsageAlert({
|
|
|
3857
4357
|
{
|
|
3858
4358
|
href: upgradeHref,
|
|
3859
4359
|
style: {
|
|
4360
|
+
...text.action,
|
|
3860
4361
|
color: "inherit",
|
|
3861
|
-
fontWeight: 600,
|
|
3862
4362
|
textDecoration: "underline"
|
|
3863
4363
|
},
|
|
3864
4364
|
children: "Upgrade"
|
|
@@ -3868,16 +4368,17 @@ function UsageAlert({
|
|
|
3868
4368
|
/* @__PURE__ */ jsx(
|
|
3869
4369
|
"button",
|
|
3870
4370
|
{
|
|
4371
|
+
type: "button",
|
|
3871
4372
|
onClick: () => handleDismiss(f.key),
|
|
3872
4373
|
"aria-label": `Dismiss ${f.label} alert`,
|
|
3873
|
-
|
|
4374
|
+
...el("usageAlert__dismiss", {
|
|
3874
4375
|
background: "none",
|
|
3875
4376
|
border: "none",
|
|
3876
4377
|
cursor: "pointer",
|
|
3877
4378
|
padding: "0 0 0 0.75rem",
|
|
3878
|
-
|
|
4379
|
+
...text.action,
|
|
3879
4380
|
color: "inherit"
|
|
3880
|
-
},
|
|
4381
|
+
}),
|
|
3881
4382
|
children: "\xD7"
|
|
3882
4383
|
}
|
|
3883
4384
|
)
|
|
@@ -3886,44 +4387,247 @@ function UsageAlert({
|
|
|
3886
4387
|
f.key
|
|
3887
4388
|
)) });
|
|
3888
4389
|
}
|
|
3889
|
-
var
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
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
|
+
] });
|
|
3909
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") }
|
|
3910
4615
|
};
|
|
3911
|
-
function PlanBadge({ plan, tier = "free" }) {
|
|
4616
|
+
function PlanBadge({ plan, tier = "free", appearance }) {
|
|
4617
|
+
const { root } = useAppearance(appearance);
|
|
3912
4618
|
const style = TIER_STYLES[tier] ?? TIER_STYLES.free;
|
|
3913
4619
|
return /* @__PURE__ */ jsx(
|
|
3914
4620
|
"span",
|
|
3915
4621
|
{
|
|
3916
|
-
|
|
4622
|
+
...root("planBadge", {
|
|
4623
|
+
...text.label,
|
|
3917
4624
|
...style,
|
|
3918
4625
|
display: "inline-flex",
|
|
3919
4626
|
alignItems: "center",
|
|
3920
4627
|
padding: "0.125rem 0.5rem",
|
|
3921
4628
|
borderRadius: "9999px",
|
|
3922
|
-
fontSize: "0.75rem",
|
|
3923
|
-
fontWeight: 600,
|
|
3924
|
-
lineHeight: "1.25rem",
|
|
3925
4629
|
whiteSpace: "nowrap"
|
|
3926
|
-
},
|
|
4630
|
+
}),
|
|
3927
4631
|
children: plan
|
|
3928
4632
|
}
|
|
3929
4633
|
);
|
|
@@ -3940,8 +4644,10 @@ var BRAND_LABELS = {
|
|
|
3940
4644
|
function PaymentMethodDisplay({
|
|
3941
4645
|
paymentMethod,
|
|
3942
4646
|
onUpdatePaymentMethod,
|
|
3943
|
-
updateHref = "/billing/update-payment"
|
|
4647
|
+
updateHref = "/billing/update-payment",
|
|
4648
|
+
appearance
|
|
3944
4649
|
}) {
|
|
4650
|
+
const { root } = useAppearance(appearance);
|
|
3945
4651
|
const handleUpdate = () => {
|
|
3946
4652
|
if (onUpdatePaymentMethod) {
|
|
3947
4653
|
onUpdatePaymentMethod();
|
|
@@ -3949,7 +4655,7 @@ function PaymentMethodDisplay({
|
|
|
3949
4655
|
window.location.href = updateHref;
|
|
3950
4656
|
}
|
|
3951
4657
|
};
|
|
3952
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
4658
|
+
return /* @__PURE__ */ jsxs("div", { ...root("paymentMethod", { ...surface, display: "flex", flexDirection: "column", gap: "0.75rem" }), children: [
|
|
3953
4659
|
paymentMethod ? /* @__PURE__ */ jsxs(
|
|
3954
4660
|
"div",
|
|
3955
4661
|
{
|
|
@@ -3958,80 +4664,245 @@ function PaymentMethodDisplay({
|
|
|
3958
4664
|
alignItems: "center",
|
|
3959
4665
|
gap: "0.75rem",
|
|
3960
4666
|
padding: "0.75rem 1rem",
|
|
3961
|
-
border:
|
|
3962
|
-
borderRadius:
|
|
3963
|
-
fontSize: "0.875rem"
|
|
4667
|
+
border: border(),
|
|
4668
|
+
borderRadius: `calc(${T.radius} * 0.6)`
|
|
3964
4669
|
},
|
|
3965
4670
|
children: [
|
|
3966
|
-
/* @__PURE__ */ jsx(
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
"span",
|
|
3978
|
-
{
|
|
3979
|
-
style: {
|
|
3980
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))"
|
|
3981
|
-
},
|
|
3982
|
-
children: [
|
|
3983
|
-
"\u2022\u2022\u2022\u2022 ",
|
|
3984
|
-
paymentMethod.last4
|
|
3985
|
-
]
|
|
3986
|
-
}
|
|
3987
|
-
),
|
|
3988
|
-
/* @__PURE__ */ jsxs(
|
|
3989
|
-
"span",
|
|
3990
|
-
{
|
|
3991
|
-
style: {
|
|
3992
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))",
|
|
3993
|
-
fontSize: "0.75rem"
|
|
3994
|
-
},
|
|
3995
|
-
children: [
|
|
3996
|
-
"Expires ",
|
|
3997
|
-
String(paymentMethod.expMonth).padStart(2, "0"),
|
|
3998
|
-
"/",
|
|
3999
|
-
String(paymentMethod.expYear).slice(-2)
|
|
4000
|
-
]
|
|
4001
|
-
}
|
|
4002
|
-
)
|
|
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
|
+
] })
|
|
4003
4682
|
]
|
|
4004
4683
|
}
|
|
4005
|
-
) : /* @__PURE__ */ jsx(
|
|
4006
|
-
"p",
|
|
4007
|
-
{
|
|
4008
|
-
style: {
|
|
4009
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))",
|
|
4010
|
-
fontSize: "0.875rem"
|
|
4011
|
-
},
|
|
4012
|
-
children: "No payment method on file."
|
|
4013
|
-
}
|
|
4014
|
-
),
|
|
4684
|
+
) : /* @__PURE__ */ jsx("p", { style: { ...text.secondary, margin: 0 }, children: "No payment method on file." }),
|
|
4015
4685
|
/* @__PURE__ */ jsx(
|
|
4016
4686
|
"button",
|
|
4017
4687
|
{
|
|
4688
|
+
type: "button",
|
|
4018
4689
|
onClick: handleUpdate,
|
|
4019
4690
|
style: {
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
color: "var(--nozle-primary, var(--primary))",
|
|
4691
|
+
...buttonStyle("outline"),
|
|
4692
|
+
alignSelf: "flex-start",
|
|
4023
4693
|
padding: "0.375rem 0.875rem",
|
|
4024
|
-
borderRadius:
|
|
4025
|
-
fontSize: "0.875rem",
|
|
4026
|
-
fontWeight: 500,
|
|
4027
|
-
cursor: "pointer"
|
|
4694
|
+
borderRadius: buttonRadius(`calc(${T.radius} / 2)`)
|
|
4028
4695
|
},
|
|
4029
4696
|
children: "Update Payment Method"
|
|
4030
4697
|
}
|
|
4031
4698
|
)
|
|
4032
4699
|
] });
|
|
4033
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
|
+
}
|
|
4034
4905
|
|
|
4035
|
-
export { BillingContext, BillingPortal, BillingPortalError, BillingProvider, CancellationControl, Checkout, CheckoutButton, FeatureGate, LockedOverlay, PaymentMethodDisplay, PlanBadge, PlanCard, PlanChangeControl, PlanComparison, PlanGate, PricingTable, UpgradeButton, UpgradeModal, UpgradePrompt, UsageAlert, UsageDashboard, UsageGate, UsageMeter, getUsageMeterColor, useBillingContext, useCheckout, useCheckout2 as useCheckoutSession, useNozleClient, useOptionalBillingContext, usePlans };
|
|
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 };
|
|
4036
4907
|
//# sourceMappingURL=index.js.map
|
|
4037
4908
|
//# sourceMappingURL=index.js.map
|