@nozle-js/react 0.10.1 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,16 +1,485 @@
1
- 'use strict';
1
+ "use client";'use strict';
2
2
 
3
- var React = require('react');
3
+ var React2 = require('react');
4
4
  var jsxRuntime = require('react/jsx-runtime');
5
+ var reactQuery = require('@tanstack/react-query');
5
6
  var reactStripeJs = require('@stripe/react-stripe-js');
6
7
  var stripeJs = require('@stripe/stripe-js');
7
8
 
8
9
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
9
10
 
10
- var React__default = /*#__PURE__*/_interopDefault(React);
11
+ var React2__default = /*#__PURE__*/_interopDefault(React2);
11
12
 
12
13
  // src/components/portal/BillingPortal.tsx
13
14
 
15
+ // src/appearance/tokens.ts
16
+ var CSS_VAR = {
17
+ colorPrimary: "--nozle-primary",
18
+ colorPrimaryForeground: "--nozle-primary-foreground",
19
+ colorBackground: "--nozle-background",
20
+ colorCard: "--nozle-card",
21
+ colorForeground: "--nozle-foreground",
22
+ colorMuted: "--nozle-muted",
23
+ colorMutedForeground: "--nozle-muted-foreground",
24
+ colorAccent: "--nozle-accent",
25
+ colorAccentForeground: "--nozle-accent-foreground",
26
+ colorBorder: "--nozle-border",
27
+ colorDanger: "--nozle-destructive",
28
+ colorWarning: "--nozle-warning",
29
+ colorSuccess: "--nozle-success",
30
+ colorSecondary: "--nozle-secondary",
31
+ colorInput: "--nozle-input",
32
+ colorHighlight: "--nozle-highlight",
33
+ colorHighlightForeground: "--nozle-highlight-foreground",
34
+ fontFamily: "--nozle-font",
35
+ fontFamilyNumbers: "--nozle-font-numbers",
36
+ labelFont: "--nozle-label-font",
37
+ labelTextTransform: "--nozle-label-transform",
38
+ labelLetterSpacing: "--nozle-label-spacing",
39
+ fontTitle: "--nozle-font-title",
40
+ fontHeading: "--nozle-font-heading",
41
+ fontMetric: "--nozle-font-metric",
42
+ fontBody: "--nozle-font-body",
43
+ fontMeta: "--nozle-font-meta",
44
+ fontAction: "--nozle-font-action",
45
+ borderRadius: "--nozle-radius",
46
+ borderWidth: "--nozle-border-width",
47
+ shadow: "--nozle-shadow",
48
+ buttonRadius: "--nozle-button-radius",
49
+ buttonBorderWidth: "--nozle-button-border-width",
50
+ buttonBorderColor: "--nozle-button-border",
51
+ buttonShadow: "--nozle-button-shadow"
52
+ };
53
+ var LIGHT = {
54
+ colorPrimary: "#4f46e5",
55
+ colorPrimaryForeground: "#ffffff",
56
+ colorBackground: "#ffffff",
57
+ colorCard: "#ffffff",
58
+ colorForeground: "#111827",
59
+ colorMuted: "#f3f4f6",
60
+ colorAccent: "#eef2ff",
61
+ colorAccentForeground: "#3730a3",
62
+ colorBorder: "#e5e7eb",
63
+ colorDanger: "#dc2626",
64
+ colorWarning: "#d97706",
65
+ colorSuccess: "#16a34a",
66
+ fontFamily: "system-ui, -apple-system, 'Segoe UI', sans-serif",
67
+ borderRadius: "12px",
68
+ borderWidth: "1px",
69
+ shadow: "0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04)"
70
+ };
71
+ var ALIASES = {
72
+ colorPrimary: ["--nozle-pricing-highlight", "--nozle-portal-accent"],
73
+ colorBackground: ["--nozle-pricing-bg", "--nozle-portal-background"],
74
+ colorCard: ["--nozle-pricing-card-bg"],
75
+ colorForeground: ["--nozle-portal-text"],
76
+ colorMuted: ["--nozle-portal-muted-background"],
77
+ colorMutedForeground: ["--nozle-portal-muted"],
78
+ colorBorder: ["--nozle-pricing-border", "--nozle-portal-border"],
79
+ borderRadius: ["--nozle-pricing-radius"]
80
+ };
81
+ function chain(key, last = LIGHT[key]) {
82
+ return [CSS_VAR[key], ...ALIASES[key] ?? []].reduceRight(
83
+ (fallback, name) => `var(${name}, ${fallback})`,
84
+ last
85
+ );
86
+ }
87
+ var soft = (color, base, percent) => `color-mix(in srgb, ${color} ${percent}%, ${base})`;
88
+ var primary = chain("colorPrimary");
89
+ var card = chain("colorCard");
90
+ var danger = chain("colorDanger");
91
+ var warning = chain("colorWarning");
92
+ var success = chain("colorSuccess");
93
+ var foreground = chain("colorForeground");
94
+ var primaryForeground = chain("colorPrimaryForeground");
95
+ var font = chain("fontFamily");
96
+ var DERIVED_MUTED_FOREGROUND = `color-mix(in srgb, ${foreground} 62%, ${card})`;
97
+ var T = {
98
+ primary,
99
+ primaryForeground,
100
+ highlight: `var(--nozle-highlight, ${primary})`,
101
+ highlightForeground: `var(--nozle-highlight-foreground, ${primaryForeground})`,
102
+ background: chain("colorBackground"),
103
+ card,
104
+ foreground,
105
+ muted: chain("colorMuted"),
106
+ mutedForeground: chain("colorMutedForeground", DERIVED_MUTED_FOREGROUND),
107
+ accent: chain("colorAccent"),
108
+ secondary: `var(--nozle-secondary, ${chain("colorMuted")})`,
109
+ input: `var(--nozle-input, ${chain("colorBorder")})`,
110
+ accentForeground: chain("colorAccentForeground"),
111
+ border: chain("colorBorder"),
112
+ danger,
113
+ warning,
114
+ font,
115
+ fontNumbers: "var(--nozle-font-numbers, inherit)",
116
+ labelFont: `var(--nozle-label-font, 600 12px/1.4 ${font})`,
117
+ fontTitle: `var(--nozle-font-title, 700 20px/1.25 ${font})`,
118
+ fontHeading: `var(--nozle-font-heading, 600 16px/1.35 ${font})`,
119
+ fontMetric: `var(--nozle-font-metric, 700 28px/1.1 var(--nozle-font-numbers, ${font}))`,
120
+ fontBody: `var(--nozle-font-body, 400 14px/1.5 ${font})`,
121
+ fontMeta: `var(--nozle-font-meta, 400 12px/1.4 ${font})`,
122
+ fontAction: `var(--nozle-font-action, 600 14px/1.4 ${font})`,
123
+ labelTransform: "var(--nozle-label-transform, none)",
124
+ labelSpacing: "var(--nozle-label-spacing, normal)",
125
+ radius: chain("borderRadius"),
126
+ borderWidth: chain("borderWidth"),
127
+ shadow: chain("shadow"),
128
+ /** Tinted backgrounds and borders for notices, derived from the matching colour. */
129
+ dangerSoft: soft(danger, card, 10),
130
+ dangerBorder: soft(danger, card, 30),
131
+ warningSoft: soft(warning, card, 12),
132
+ warningBorder: soft(warning, card, 35),
133
+ /** Status text: the status colour mixed with the text colour, so it stays readable on its soft background. */
134
+ dangerText: soft(danger, foreground, 70),
135
+ warningText: soft(warning, foreground, 70),
136
+ successText: soft(success, foreground, 70),
137
+ successSoft: soft(success, card, 12),
138
+ primarySoft: soft(primary, card, 10),
139
+ skeleton: soft(foreground, card, 8),
140
+ /** Table header band and the lighter line between table rows. */
141
+ tableHeader: soft(chain("colorMuted"), card, 50),
142
+ rowLine: soft(chain("colorBorder"), card, 70)
143
+ };
144
+ var text = {
145
+ heading: { font: T.fontHeading, color: T.foreground },
146
+ metric: { font: T.fontMetric, fontVariantNumeric: "tabular-nums", color: T.foreground },
147
+ body: { font: T.fontBody, color: T.foreground },
148
+ secondary: { font: T.fontBody, color: T.mutedForeground },
149
+ meta: { font: T.fontMeta, color: T.mutedForeground },
150
+ label: {
151
+ font: T.labelFont,
152
+ letterSpacing: T.labelSpacing,
153
+ textTransform: T.labelTransform,
154
+ color: T.mutedForeground
155
+ },
156
+ action: { font: T.fontAction }
157
+ };
158
+ var border = (color = T.border) => `${T.borderWidth} solid ${color}`;
159
+ var buttonBorderWidth = `var(--nozle-button-border-width, ${T.borderWidth})`;
160
+ var primaryButtonBorder = (color) => `${buttonBorderWidth} solid var(--nozle-button-border, ${color})`;
161
+ var buttonBorder = (color) => `${buttonBorderWidth} solid ${color}`;
162
+ var buttonRadius = (fallback) => `var(--nozle-button-radius, ${fallback})`;
163
+ var buttonShadow = "var(--nozle-button-shadow, none)";
164
+ function addTheme(target, theme) {
165
+ if (!theme) return;
166
+ Object.assign(target.variables, theme.variables);
167
+ if (theme.planCardLayout) target.planCardLayout = theme.planCardLayout;
168
+ for (const [key, value] of Object.entries(theme.elements ?? {})) {
169
+ const current = target.elements[key] ?? {};
170
+ target.elements[key] = typeof value === "string" ? { ...current, className: [current.className, value].filter(Boolean).join(" ") } : { ...current, style: { ...current.style, ...value } };
171
+ }
172
+ }
173
+ function mergeAppearance(...layers) {
174
+ const resolved = { variables: {}, elements: {} };
175
+ for (const layer of layers) {
176
+ if (!layer) continue;
177
+ const themes = Array.isArray(layer.theme) ? layer.theme : [layer.theme];
178
+ themes.forEach((theme) => addTheme(resolved, theme));
179
+ addTheme(resolved, layer);
180
+ }
181
+ return resolved;
182
+ }
183
+ function toCssVars(variables) {
184
+ const style = {};
185
+ for (const [key, value] of Object.entries(variables)) {
186
+ if (value == null || value === "") continue;
187
+ if (key === "colorScheme") style.colorScheme = value;
188
+ else style[CSS_VAR[key]] = value;
189
+ }
190
+ return style;
191
+ }
192
+ var surface = {
193
+ ...text.body,
194
+ background: T.card,
195
+ border: border(),
196
+ borderRadius: T.radius,
197
+ boxShadow: T.shadow,
198
+ padding: "1.25rem"
199
+ };
200
+ var table = {
201
+ frame: {
202
+ ...text.body,
203
+ background: T.card,
204
+ border: border(),
205
+ borderRadius: T.radius,
206
+ overflowX: "auto",
207
+ minWidth: 0,
208
+ maxWidth: "100%"
209
+ },
210
+ table: { width: "100%", borderCollapse: "collapse", font: "inherit" },
211
+ head: (numeric = false) => ({
212
+ height: "44px",
213
+ padding: "0 20px",
214
+ textAlign: numeric ? "right" : "left",
215
+ verticalAlign: "middle",
216
+ ...text.label,
217
+ whiteSpace: "nowrap",
218
+ background: T.tableHeader,
219
+ borderBottom: border()
220
+ }),
221
+ /** `last`: the final body row, whose line the frame already draws. */
222
+ cell: (numeric = false, last = false) => ({
223
+ height: "48px",
224
+ padding: "14px 20px",
225
+ verticalAlign: "middle",
226
+ whiteSpace: "nowrap",
227
+ textAlign: numeric ? "right" : "left",
228
+ borderBottom: last ? "none" : border(T.rowLine),
229
+ ...numeric && { fontFamily: T.fontNumbers, fontVariantNumeric: "tabular-nums" }
230
+ }),
231
+ foot: (numeric = false) => ({
232
+ ...table.cell(numeric, true),
233
+ borderTop: border(),
234
+ background: T.tableHeader,
235
+ fontWeight: 600
236
+ })
237
+ };
238
+ function buttonStyle(variant = "primary", busy = false) {
239
+ const colors = {
240
+ primary: { background: T.primary, color: T.primaryForeground, border: primaryButtonBorder(T.primary), boxShadow: buttonShadow },
241
+ secondary: { background: T.secondary, color: T.foreground, border: buttonBorder(T.input) },
242
+ outline: { background: "transparent", color: T.primary, border: buttonBorder(T.primary) }
243
+ };
244
+ return {
245
+ ...colors[variant],
246
+ padding: "0.75rem 1.5rem",
247
+ borderRadius: buttonRadius(`calc(${T.radius} * 0.75)`),
248
+ ...text.action,
249
+ cursor: busy ? "wait" : "pointer",
250
+ opacity: busy ? 0.7 : 1,
251
+ transition: "filter 0.15s ease"
252
+ };
253
+ }
254
+ var AppearanceContext = React2.createContext(void 0);
255
+ function AppearanceProvider({
256
+ appearance,
257
+ children
258
+ }) {
259
+ return /* @__PURE__ */ jsxRuntime.jsx(AppearanceContext.Provider, { value: appearance, children });
260
+ }
261
+ function useAppearance(appearance) {
262
+ const inherited = React2.useContext(AppearanceContext);
263
+ return React2.useMemo(() => {
264
+ const { variables, elements, planCardLayout = "card" } = mergeAppearance(inherited, appearance);
265
+ const vars = toCssVars(variables);
266
+ const el = (key, style, className) => {
267
+ const override = elements[key];
268
+ return {
269
+ className: ["nozle-" + key, className, override?.className].filter(Boolean).join(" "),
270
+ style: { ...style, ...override?.style }
271
+ };
272
+ };
273
+ return {
274
+ el,
275
+ planCardLayout,
276
+ vars,
277
+ root: (key, style, className) => {
278
+ const props = el(key, style, className);
279
+ return { ...props, style: { ...vars, ...props.style } };
280
+ }
281
+ };
282
+ }, [inherited, appearance]);
283
+ }
284
+ function createClient(publishableKey, baseUrl) {
285
+ let end = baseUrl.length;
286
+ while (end > 0 && baseUrl[end - 1] === "/") end--;
287
+ const base = baseUrl.slice(0, end);
288
+ return {
289
+ publishableKey,
290
+ baseUrl: base,
291
+ catalogFetch(path, init) {
292
+ return fetch(`${base}${path}`, {
293
+ ...init,
294
+ headers: {
295
+ Authorization: `Bearer ${publishableKey}`,
296
+ "Content-Type": "application/json",
297
+ ...init?.headers
298
+ }
299
+ });
300
+ }
301
+ };
302
+ }
303
+ var BillingRequestError = class extends Error {
304
+ constructor(status, code, requestId) {
305
+ super(`Billing request failed: ${status}${code ? ` ${code}` : ""}`);
306
+ this.status = status;
307
+ this.code = code;
308
+ this.requestId = requestId;
309
+ this.name = "BillingRequestError";
310
+ }
311
+ status;
312
+ code;
313
+ requestId;
314
+ };
315
+ function createBillingFetch(endpoint, headers) {
316
+ let end = endpoint.length;
317
+ while (end > 0 && endpoint[end - 1] === "/") end--;
318
+ const base = endpoint.slice(0, end);
319
+ return async (route, params = {}) => {
320
+ const query = new URLSearchParams();
321
+ for (const [key, value] of Object.entries(params)) {
322
+ if (value !== void 0 && value !== "") query.set(key, String(value));
323
+ }
324
+ const search = query.toString();
325
+ const response = await fetch(`${base}/${route}${search ? `?${search}` : ""}`, {
326
+ credentials: "same-origin",
327
+ headers: { Accept: "application/json", ...headers ? await headers() : {} }
328
+ });
329
+ if (!response.ok) {
330
+ const body = await response.json().catch(() => ({}));
331
+ throw new BillingRequestError(response.status, body.code, body.request_id);
332
+ }
333
+ return await response.json();
334
+ };
335
+ }
336
+ function createQueryClient() {
337
+ return new reactQuery.QueryClient({
338
+ defaultOptions: {
339
+ queries: {
340
+ staleTime: 3e4,
341
+ // Retry only failures that may pass on a second try; a 4xx will not.
342
+ retry: (count, error) => count < 2 && !(error instanceof BillingRequestError && error.status < 500)
343
+ }
344
+ }
345
+ });
346
+ }
347
+ var BillingContext = React2.createContext(null);
348
+ function BillingProvider({
349
+ publishableKey,
350
+ createCheckout,
351
+ verifyCheckout,
352
+ getCheckoutStatus,
353
+ baseUrl = "https://api.nozle.app/engine",
354
+ billingEndpoint,
355
+ billingHeaders,
356
+ queryClient,
357
+ appearance,
358
+ children
359
+ }) {
360
+ if (!publishableKey.startsWith("pk_")) {
361
+ throw new Error(
362
+ "BillingProvider publishableKey must be a publishable key (pk_)"
363
+ );
364
+ }
365
+ const client = React2.useMemo(
366
+ () => createClient(publishableKey, baseUrl),
367
+ [publishableKey, baseUrl]
368
+ );
369
+ const [ownQueryClient] = React2.useState(createQueryClient);
370
+ const activeQueryClient = queryClient ?? ownQueryClient;
371
+ const billingFetch = React2.useMemo(
372
+ () => billingEndpoint ? createBillingFetch(billingEndpoint, billingHeaders) : void 0,
373
+ [billingEndpoint, billingHeaders]
374
+ );
375
+ const contextValue = React2.useMemo(
376
+ () => ({
377
+ client,
378
+ createCheckout,
379
+ verifyCheckout,
380
+ getCheckoutStatus,
381
+ queryClient: activeQueryClient,
382
+ billingFetch,
383
+ billingEndpoint
384
+ }),
385
+ [client, createCheckout, verifyCheckout, getCheckoutStatus, activeQueryClient, billingFetch, billingEndpoint]
386
+ );
387
+ return React2__default.default.createElement(
388
+ BillingContext.Provider,
389
+ { value: contextValue },
390
+ React2__default.default.createElement(AppearanceProvider, { appearance, children })
391
+ );
392
+ }
393
+ function useOptionalBillingContext() {
394
+ return React2.useContext(BillingContext);
395
+ }
396
+ function useBillingContext() {
397
+ const context = useOptionalBillingContext();
398
+ if (!context) {
399
+ throw new Error("useBillingContext must be used within a BillingProvider");
400
+ }
401
+ return context;
402
+ }
403
+ function useNozleClient() {
404
+ return useBillingContext().client;
405
+ }
406
+ var idleQueryClient = new reactQuery.QueryClient();
407
+ function catalogQuery(client) {
408
+ return {
409
+ queryKey: ["nozle", "plans", client?.baseUrl, client?.publishableKey],
410
+ queryFn: async ({ signal }) => {
411
+ const response = await client.catalogFetch("/api/v1/plans", { signal });
412
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
413
+ const data = await response.json();
414
+ return { plans: data.plans ?? [], nozleBranding: data.meta?.nozle_branding === true };
415
+ },
416
+ staleTime: 5 * 6e4,
417
+ // The pricing page shows a load error at once; the app can call refetch().
418
+ retry: false
419
+ };
420
+ }
421
+ function usePlans() {
422
+ const { client, queryClient } = useBillingContext();
423
+ const query = reactQuery.useQuery(catalogQuery(client), queryClient);
424
+ return {
425
+ plans: query.data?.plans ?? [],
426
+ isLoading: query.isPending,
427
+ error: query.error ?? null,
428
+ refetch: query.refetch
429
+ };
430
+ }
431
+
432
+ // src/components/branding/logo.ts
433
+ var LOGO_VIEW_BOX = "0 0 3208 1122";
434
+ 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";
435
+ 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";
436
+ var BrandingScope = React2.createContext(false);
437
+ function useNozleBranding() {
438
+ const billing = useOptionalBillingContext();
439
+ const { data } = reactQuery.useQuery(
440
+ // Reads the catalog request the components already make: mounting this must not refetch a
441
+ // failed catalog, or a table that shows the error would loop back into loading.
442
+ { ...catalogQuery(billing?.client), enabled: Boolean(billing), refetchOnMount: false, retryOnMount: false },
443
+ billing?.queryClient ?? idleQueryClient
444
+ );
445
+ return data?.nozleBranding === true;
446
+ }
447
+ function PoweredByNozle() {
448
+ const { root } = useAppearance();
449
+ return /* @__PURE__ */ jsxRuntime.jsxs(
450
+ "a",
451
+ {
452
+ href: "https://nozle.dev",
453
+ target: "_blank",
454
+ rel: "noopener noreferrer",
455
+ ...root("poweredBy", {
456
+ ...text.meta,
457
+ display: "flex",
458
+ alignItems: "center",
459
+ justifyContent: "center",
460
+ gap: "6px",
461
+ marginTop: "1rem",
462
+ textDecoration: "none"
463
+ }),
464
+ children: [
465
+ "Powered by",
466
+ /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: LOGO_VIEW_BOX, height: "14", width: "40", role: "img", "aria-label": "Nozle", style: { color: T.foreground }, children: [
467
+ /* @__PURE__ */ jsxRuntime.jsx("path", { fill: "#F34213", d: LOGO_MARK_PATH }),
468
+ /* @__PURE__ */ jsxRuntime.jsx("path", { fill: "currentColor", d: LOGO_WORDMARK_PATH })
469
+ ] })
470
+ ]
471
+ }
472
+ );
473
+ }
474
+ function NozleBranding({ show, children }) {
475
+ const nested = React2.useContext(BrandingScope);
476
+ const fromCatalog = useNozleBranding();
477
+ return /* @__PURE__ */ jsxRuntime.jsxs(BrandingScope.Provider, { value: true, children: [
478
+ children,
479
+ !nested && (show ?? fromCatalog) && /* @__PURE__ */ jsxRuntime.jsx(PoweredByNozle, {})
480
+ ] });
481
+ }
482
+
14
483
  // src/components/portal/client.ts
15
484
  var BillingPortalError = class extends Error {
16
485
  constructor(code) {
@@ -102,7 +571,7 @@ var identityQuery = `query SdkPortalIdentity {
102
571
  addressLine1 addressLine2 city state zipcode country
103
572
  shippingAddress { addressLine1 addressLine2 city state zipcode country }
104
573
  }
105
- customerPortalOrganization { id name }
574
+ customerPortalOrganization { id name nozleBranding }
106
575
  }`;
107
576
  var subscriptionsQuery = `query SdkPortalSubscriptions($page: Int) {
108
577
  customerPortalSubscriptions(status: [active], limit: 20, page: $page) {
@@ -151,40 +620,55 @@ var topUpMutation = `mutation SdkPortalTopUp($input: CreateCustomerPortalWalletT
151
620
  }`;
152
621
 
153
622
  // src/components/portal/styles.ts
623
+ var line = `${T.borderWidth} solid ${T.border}`;
624
+ var r = (factor) => `calc(${T.radius} * ${factor})`;
154
625
  var portalStyles = `
155
- .nozle-billing-portal{color:var(--nozle-portal-text,#202922);background:var(--nozle-portal-background,#fff);font:14px/1.55 system-ui,sans-serif;box-sizing:border-box;width:100%;min-width:0;padding:clamp(18px,4vw,40px);border-radius:12px}
626
+ .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}}
156
627
  .nozle-billing-portal *{box-sizing:border-box}
157
628
  .nozle-billing-portal h2,.nozle-billing-portal h3,.nozle-billing-portal p{margin:0}
158
- .nozle-billing-portal h2{font-size:24px;letter-spacing:-.6px;line-height:1.3}
159
- .nozle-billing-portal h3{font-size:16px;line-height:1.4}
629
+ .nozle-billing-portal h2{font:${T.fontTitle}}
630
+ .nozle-billing-portal h3{font:${T.fontHeading}}
160
631
  .nozle-billing-portal header{margin-bottom:30px}
161
- .nozle-billing-portal section{margin-top:28px;padding-top:24px;border-top:1px solid var(--nozle-portal-border,#e6e9e5)}
162
- .nozle-billing-portal button,.nozle-billing-portal .nzp-link{appearance:none;display:inline-flex;align-items:center;justify-content:center;font:inherit;font-weight:550;line-height:1.4;border:1px solid var(--nozle-portal-border,#dce2d9);border-radius:7px;background:var(--nozle-portal-background,#fff);color:var(--nozle-portal-text,#202922);padding:9px 14px;cursor:pointer;text-decoration:none;white-space:normal}
163
- .nozle-billing-portal button:hover,.nozle-billing-portal .nzp-link:hover{background:var(--nozle-portal-muted-background,#f4f6f2)}
632
+ .nozle-billing-portal section{margin-top:28px;padding-top:24px;border-top:${line}}
633
+ .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}
634
+ .nozle-billing-portal button:hover,.nozle-billing-portal .nzp-link:hover{background:${T.muted}}
164
635
  .nozle-billing-portal button:disabled{opacity:.55;cursor:wait}
165
- .nozle-billing-portal :focus-visible{outline:2px solid var(--nozle-portal-accent,#526949);outline-offset:3px}
166
- .nozle-billing-portal .nzp-primary{background:var(--nozle-portal-accent,#526949);border-color:var(--nozle-portal-accent,#526949);color:#fff}
167
- .nozle-billing-portal .nzp-primary:hover{filter:brightness(.92);background:var(--nozle-portal-accent,#526949)}
168
- .nozle-billing-portal .nzp-muted{color:var(--nozle-portal-muted,#687166);font-size:13px}
636
+ .nozle-billing-portal :focus-visible{outline:2px solid ${T.primary};outline-offset:3px}
637
+ .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}}
638
+ .nozle-billing-portal .nzp-primary:hover{filter:brightness(.92);background:${T.primary}}
639
+ .nozle-billing-portal .nzp-muted{color:${T.mutedForeground}}
640
+ .nozle-billing-portal .nzp-notice{color:${T.successText}}
169
641
  .nozle-billing-portal .nzp-row{display:flex;align-items:center;justify-content:space-between;gap:14px;flex-wrap:wrap}
170
642
  .nozle-billing-portal .nzp-stack{display:grid;gap:12px;margin-top:16px}
171
- .nozle-billing-portal .nzp-card{border:1px solid var(--nozle-portal-border,#e0e6dc);border-radius:10px;padding:20px;min-width:0}
172
- .nozle-billing-portal .nzp-price{font-size:26px;letter-spacing:-.5px;margin:12px 0 4px}
173
- .nozle-billing-portal .nzp-badge{display:inline-block;font-size:11px;font-weight:600;letter-spacing:.3px;border-radius:5px;background:var(--nozle-portal-muted-background,#edf3e8);padding:3px 7px}
174
- .nozle-billing-portal .nzp-empty{border:1px dashed var(--nozle-portal-border,#dce2d9);border-radius:8px;padding:22px;text-align:center;color:var(--nozle-portal-muted,#687166);margin-top:16px}
175
- .nozle-billing-portal .nzp-error{color:#922e25;background:#fff4f2;border:1px solid #f2d2ce;border-radius:8px;padding:14px;margin:16px 0;display:flex;gap:12px;align-items:center;flex-wrap:wrap}
176
- .nozle-billing-portal .nzp-table{overflow-x:auto;margin-top:16px}
177
- .nozle-billing-portal table{width:100%;border-collapse:collapse;text-align:left;font-size:13px}
178
- .nozle-billing-portal th{font-weight:500;color:var(--nozle-portal-muted,#687166);background:var(--nozle-portal-muted-background,#f7f8f5)}
179
- .nozle-billing-portal th,.nozle-billing-portal td{padding:12px 10px;border-bottom:1px solid var(--nozle-portal-border,#e6e9e5);white-space:nowrap}
180
- .nozle-billing-portal label{display:grid;gap:6px;font-size:13px;font-weight:500;min-width:0}
181
- .nozle-billing-portal input,.nozle-billing-portal select{width:100%;min-width:0;font:inherit;color:inherit;background:var(--nozle-portal-background,#fff);border:1px solid var(--nozle-portal-border,#dce2d9);border-radius:6px;padding:9px 10px}
643
+ .nozle-billing-portal .nzp-card{background:${T.card};border:${line};border-radius:${r(0.85)};box-shadow:${T.shadow};padding:20px;min-width:0}
644
+ .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}}
645
+ .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}
646
+ .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}
647
+ .nozle-billing-portal .nzp-card-actions>.nozle-cancellation>button{margin-left:auto}
648
+ .nozle-billing-portal .nzp-price{font:${T.fontMetric};font-variant-numeric:tabular-nums;margin:12px 0 4px}
649
+ .nozle-billing-portal .nzp-price .nzp-muted{font:${T.fontMeta}}
650
+ .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}
651
+ .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}
652
+ .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}
653
+ .nozle-billing-portal .nzp-table{overflow-x:auto;margin-top:16px;background:${T.card};border:${line};border-radius:${T.radius}}
654
+ .nozle-billing-portal table{width:100%;border-collapse:collapse;text-align:left;font:inherit}
655
+ .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}
656
+ .nozle-billing-portal td{height:48px;padding:14px 20px;border-bottom:${T.borderWidth} solid ${T.rowLine};white-space:nowrap}
657
+ .nozle-billing-portal tbody tr:last-child td{border-bottom:0}
658
+ .nozle-billing-portal .nzp-num{text-align:right}
659
+ .nozle-billing-portal td.nzp-num{font-family:${T.fontNumbers};font-variant-numeric:tabular-nums}
660
+ .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}}
661
+ .nozle-billing-portal .nzp-search:focus-within{border-color:${T.foreground}}
662
+ .nozle-billing-portal .nzp-search input{flex:1;height:100%;border:0;padding:0;background:transparent;color:${T.foreground};outline:none}
663
+ .nozle-billing-portal .nzp-search input::placeholder{color:${T.mutedForeground}}
664
+ .nozle-billing-portal label{display:grid;gap:6px;font-weight:500;min-width:0}
665
+ .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}
182
666
  .nozle-billing-portal .nzp-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,220px),1fr));gap:16px;margin:20px 0}
183
667
  .nozle-billing-portal .nzp-actions{display:flex;align-items:flex-end;gap:10px;flex-wrap:wrap;margin-top:20px}
184
668
  .nozle-billing-portal dl{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,210px),1fr));gap:18px;margin:16px 0 0}
185
- .nozle-billing-portal dt{font-size:12px;color:var(--nozle-portal-muted,#687166)}
669
+ .nozle-billing-portal dt{font:${T.fontMeta};color:${T.mutedForeground}}
186
670
  .nozle-billing-portal dd{margin:3px 0 0;overflow-wrap:anywhere}
187
- .nozle-billing-portal footer{margin-top:30px;text-align:center;color:var(--nozle-portal-muted,#687166);font-size:12px}
671
+ .nozle-billing-portal footer{margin-top:30px;text-align:center;color:${T.mutedForeground};font:${T.fontMeta}}
188
672
  `;
189
673
 
190
674
  // src/components/portal/timezones.ts
@@ -368,6 +852,25 @@ function compareDecimalStrings(left, right) {
368
852
  }
369
853
  return magnitude * a.sign;
370
854
  }
855
+ function formatDecimalString(value, maximumFractionDigits = 12) {
856
+ const parsed = parseDecimal(value);
857
+ if (!parsed) return value;
858
+ const fractionLimit = Math.max(
859
+ 0,
860
+ Math.min(30, Math.trunc(maximumFractionDigits))
861
+ );
862
+ const firstGroupLength = parsed.integer.length % 3 || 3;
863
+ const groups = [parsed.integer.slice(0, firstGroupLength)];
864
+ for (let index = firstGroupLength; index < parsed.integer.length; index += 3) {
865
+ groups.push(parsed.integer.slice(index, index + 3));
866
+ }
867
+ const groupedInteger = groups.join(",");
868
+ const fraction = parsed.fraction.slice(0, fractionLimit);
869
+ return `${parsed.sign < 0 ? "-" : ""}${groupedInteger}${fraction ? `.${fraction}` : ""}`;
870
+ }
871
+ function creditUnitLabel(value, unitName) {
872
+ return compareDecimalStrings(value, "1") === 0 ? unitName : `${unitName}s`;
873
+ }
371
874
 
372
875
  // src/components/portal/amounts.ts
373
876
  function walletAmountCents(credits, rate, currencyDigits) {
@@ -388,16 +891,16 @@ function walletAmountCents(credits, rate, currencyDigits) {
388
891
  return `${padded.slice(0, -scale)}.${padded.slice(-scale)}`;
389
892
  }
390
893
  var styles = `
391
- .nozle-cancellation{font:inherit;color:var(--nozle-portal-text,#202922)}
392
- .nozle-cancellation button{font:inherit;padding:9px 14px;border:1px solid var(--nozle-portal-border,#dce2d9);border-radius:7px;background:var(--nozle-portal-background,#fff);color:inherit;cursor:pointer}
894
+ .nozle-cancellation{font:${T.fontBody};color:${T.foreground}}
895
+ .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}
393
896
  .nozle-cancellation button:disabled{opacity:.55;cursor:wait}
394
- .nozle-cancellation :focus-visible{outline:2px solid var(--nozle-portal-accent,#526949);outline-offset:3px}
897
+ .nozle-cancellation :focus-visible{outline:2px solid ${T.primary};outline-offset:3px}
395
898
  .nozle-cancellation .nzc-backdrop{position:fixed;inset:0;background:#0006;z-index:1000;display:flex;align-items:center;justify-content:center;padding:20px}
396
- .nozle-cancellation .nzc-dialog{box-sizing:border-box;background:var(--nozle-portal-background,#fff);border:1px solid var(--nozle-portal-border,#dce2d9);border-radius:12px;padding:24px;width:100%;max-width:440px;max-height:90dvh;overflow:auto;box-shadow:0 12px 48px #0003}
397
- .nozle-cancellation .nzc-dialog h3{margin:0 0 16px;font-size:20px}
398
- .nozle-cancellation .nzc-dialog p{margin:12px 0;line-height:1.55}
899
+ .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}
900
+ .nozle-cancellation .nzc-dialog h3{margin:0 0 16px;font:${T.fontTitle};letter-spacing:normal;color:${T.foreground}}
901
+ .nozle-cancellation .nzc-dialog p{margin:12px 0}
399
902
  .nozle-cancellation .nzc-buttons{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:10px;margin-top:20px}
400
- .nozle-cancellation [role=alert]{color:#922e25}
903
+ .nozle-cancellation [role=alert],.nozle-cancellation .nzc-danger{color:${T.dangerText}}
401
904
  `;
402
905
  function formatDate(value, locale, timezone) {
403
906
  const timeZone = timezone.startsWith("TZ_") ? portalTimezones[timezone] ?? "UTC" : timezone;
@@ -427,27 +930,29 @@ function CancellationFlow({
427
930
  onError,
428
931
  locale = "en-US",
429
932
  timezone = "UTC",
430
- nonce
933
+ nonce,
934
+ appearance
431
935
  }) {
432
- const [open, setOpen] = React.useState(false);
433
- const [busy, setBusy] = React.useState(false);
434
- const [preview, setPreview] = React.useState();
435
- const [failure, setFailure] = React.useState();
436
- const [notice, setNotice] = React.useState("");
437
- const [observed, setObserved] = React.useState(
936
+ const { root } = useAppearance(appearance);
937
+ const [open, setOpen] = React2.useState(false);
938
+ const [busy, setBusy] = React2.useState(false);
939
+ const [preview, setPreview] = React2.useState();
940
+ const [failure, setFailure] = React2.useState();
941
+ const [notice, setNotice] = React2.useState("");
942
+ const [observed, setObserved] = React2.useState(
438
943
  subscription
439
944
  );
440
- const request = React.useRef(void 0);
441
- const inFlight = React.useRef(null);
442
- const trigger = React.useRef(null);
443
- const dialog = React.useRef(null);
444
- const titleId = React.useId();
445
- const descriptionId = React.useId();
945
+ const request = React2.useRef(void 0);
946
+ const inFlight = React2.useRef(null);
947
+ const trigger = React2.useRef(null);
948
+ const dialog = React2.useRef(null);
949
+ const titleId = React2.useId();
950
+ const descriptionId = React2.useId();
446
951
  const active = observed?.status === "active";
447
952
  const ending = observed?.endingAt;
448
953
  const expired = Boolean(ending && Date.parse(ending) <= Date.now());
449
954
  const operation = ending ? "uncancel" : "cancel";
450
- React.useEffect(
955
+ React2.useEffect(
451
956
  () => setObserved(subscription),
452
957
  [
453
958
  subscription.id,
@@ -458,8 +963,8 @@ function CancellationFlow({
458
963
  subscription.pendingPlanName
459
964
  ]
460
965
  );
461
- React.useEffect(() => () => inFlight.current?.abort(), []);
462
- React.useEffect(() => {
966
+ React2.useEffect(() => () => inFlight.current?.abort(), []);
967
+ React2.useEffect(() => {
463
968
  if (!open) return;
464
969
  dialog.current?.focus();
465
970
  return () => trigger.current?.focus();
@@ -615,7 +1120,7 @@ function CancellationFlow({
615
1120
  );
616
1121
  });
617
1122
  }
618
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nozle-cancellation", children: [
1123
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ...root("cancellationControl", void 0, "nozle-cancellation"), children: [
619
1124
  /* @__PURE__ */ jsxRuntime.jsx("style", { nonce, children: styles }),
620
1125
  notice && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", children: notice }),
621
1126
  active && !expired && /* @__PURE__ */ jsxRuntime.jsx("button", { ref: trigger, type: "button", onClick: () => void loadPreview(), children: ending ? "Keep my subscription" : "Cancel subscription" }),
@@ -700,6 +1205,7 @@ function CancellationFlow({
700
1205
  "button",
701
1206
  {
702
1207
  type: "button",
1208
+ className: preview.operation === "cancel" ? "nzc-danger" : void 0,
703
1209
  disabled: busy,
704
1210
  onClick: () => void confirm(),
705
1211
  children: request.current ? "Check status / retry" : preview.operation === "cancel" ? "Confirm cancellation" : "Confirm keep subscription"
@@ -711,66 +1217,6 @@ function CancellationFlow({
711
1217
  ) })
712
1218
  ] });
713
1219
  }
714
- function createClient(publishableKey, baseUrl) {
715
- let end = baseUrl.length;
716
- while (end > 0 && baseUrl[end - 1] === "/") end--;
717
- const base = baseUrl.slice(0, end);
718
- return {
719
- publishableKey,
720
- baseUrl: base,
721
- catalogFetch(path, init) {
722
- return fetch(`${base}${path}`, {
723
- ...init,
724
- headers: {
725
- Authorization: `Bearer ${publishableKey}`,
726
- "Content-Type": "application/json",
727
- ...init?.headers
728
- }
729
- });
730
- }
731
- };
732
- }
733
- var BillingContext = React.createContext(null);
734
- function BillingProvider({
735
- publishableKey,
736
- createCheckout,
737
- verifyCheckout,
738
- getCheckoutStatus,
739
- baseUrl = "https://api.nozle.app/engine",
740
- children
741
- }) {
742
- if (!publishableKey.startsWith("pk_")) {
743
- throw new Error(
744
- "BillingProvider publishableKey must be a publishable key (pk_)"
745
- );
746
- }
747
- const client = React.useMemo(
748
- () => createClient(publishableKey, baseUrl),
749
- [publishableKey, baseUrl]
750
- );
751
- const contextValue = React.useMemo(
752
- () => ({ client, createCheckout, verifyCheckout, getCheckoutStatus }),
753
- [client, createCheckout, verifyCheckout, getCheckoutStatus]
754
- );
755
- return React__default.default.createElement(
756
- BillingContext.Provider,
757
- { value: contextValue },
758
- children
759
- );
760
- }
761
- function useOptionalBillingContext() {
762
- return React.useContext(BillingContext);
763
- }
764
- function useBillingContext() {
765
- const context = useOptionalBillingContext();
766
- if (!context) {
767
- throw new Error("useBillingContext must be used within a BillingProvider");
768
- }
769
- return context;
770
- }
771
- function useNozleClient() {
772
- return useBillingContext().client;
773
- }
774
1220
 
775
1221
  // src/components/billing/razorpay-checkout.ts
776
1222
  var loadingScript;
@@ -985,14 +1431,16 @@ function CheckoutButton({
985
1431
  onStripeClientSecret,
986
1432
  onSuccess,
987
1433
  onComplete,
988
- onScheduled
1434
+ onScheduled,
1435
+ appearance
989
1436
  }) {
1437
+ const { root, vars } = useAppearance(appearance);
990
1438
  const { createCheckout, verifyCheckout, getCheckoutStatus } = useBillingContext();
991
- const attempt = React.useRef(void 0);
992
- const inFlight = React.useRef(false);
993
- const [processing, setProcessing] = React.useState(false);
994
- const [loading, setLoading] = React.useState(false);
995
- const [error, setError] = React.useState(null);
1439
+ const attempt = React2.useRef(void 0);
1440
+ const inFlight = React2.useRef(false);
1441
+ const [processing, setProcessing] = React2.useState(false);
1442
+ const [loading, setLoading] = React2.useState(false);
1443
+ const [error, setError] = React2.useState(null);
996
1444
  async function handleClick() {
997
1445
  if (inFlight.current) return;
998
1446
  inFlight.current = true;
@@ -1049,51 +1497,79 @@ function CheckoutButton({
1049
1497
  type: "button",
1050
1498
  onClick: () => void handleClick(),
1051
1499
  disabled: loading,
1052
- className,
1053
- style: {
1054
- padding: "0.75rem 1.5rem",
1055
- borderRadius: "var(--nozle-radius, 0.5rem)",
1056
- border: "none",
1057
- background: "var(--nozle-primary, var(--primary))",
1058
- color: "var(--nozle-primary-foreground, var(--primary-foreground))",
1059
- cursor: loading ? "not-allowed" : "pointer",
1060
- fontWeight: 500,
1061
- opacity: loading ? 0.7 : 1,
1062
- ...style
1063
- },
1500
+ ...root("checkoutButton", { ...buttonStyle("primary", loading), ...style }, className),
1064
1501
  "aria-busy": loading,
1065
1502
  children: loading ? "Loading..." : label
1066
1503
  }
1067
1504
  ),
1068
- processing && /* @__PURE__ */ jsxRuntime.jsx("span", { role: "status", children: "Payment is processing. Confirmation may take a little while." }),
1069
- error && /* @__PURE__ */ jsxRuntime.jsx("span", { role: "alert", children: error.message })
1505
+ processing && /* @__PURE__ */ jsxRuntime.jsx("span", { role: "status", style: { ...vars, ...text.body }, children: "Payment is processing. Confirmation may take a little while." }),
1506
+ error && /* @__PURE__ */ jsxRuntime.jsx("span", { role: "alert", style: { ...vars, ...text.body, color: T.dangerText }, children: error.message })
1070
1507
  ] });
1071
1508
  }
1072
- var CheckoutContext = React.createContext(null);
1509
+ var CheckoutContext = React2.createContext(null);
1073
1510
  function useCheckout() {
1074
- const ctx = React.useContext(CheckoutContext);
1511
+ const ctx = React2.useContext(CheckoutContext);
1075
1512
  if (ctx === null) {
1076
1513
  throw new Error("useCheckout must be used inside a <Checkout> component");
1077
1514
  }
1078
1515
  return ctx;
1079
1516
  }
1080
- function buildStripeAppearance() {
1081
- if (typeof document === "undefined") {
1082
- return { theme: "stripe" };
1083
- }
1084
- const style = getComputedStyle(document.documentElement);
1085
- const get = (prop, fallback) => style.getPropertyValue(prop).trim() || fallback;
1086
- return {
1087
- theme: "stripe",
1088
- variables: {
1089
- colorPrimary: get("--nozle-primary", "#16a34a"),
1090
- colorBackground: get("--nozle-background", "#f8fafc"),
1091
- colorText: get("--nozle-foreground", "#1e293b"),
1092
- colorDanger: get("--nozle-destructive", "#dc2626"),
1093
- borderRadius: get("--nozle-radius", "0.5rem"),
1094
- fontFamily: "inherit"
1517
+ function rgb(color) {
1518
+ const match = color?.match(/^rgba?\(\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)/);
1519
+ return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
1520
+ }
1521
+ function stripeColor(color) {
1522
+ return rgb(color) ? color : color?.startsWith("#") ? color : void 0;
1523
+ }
1524
+ function toStripeAppearance(colors) {
1525
+ const background = rgb(colors.background);
1526
+ const dark = background ? (0.2126 * background[0] + 0.7152 * background[1] + 0.0722 * background[2]) / 255 < 0.5 : false;
1527
+ const variables = Object.fromEntries(
1528
+ Object.entries({
1529
+ colorPrimary: stripeColor(colors.primary),
1530
+ colorBackground: stripeColor(colors.background),
1531
+ colorText: stripeColor(colors.foreground),
1532
+ colorDanger: stripeColor(colors.danger),
1533
+ borderRadius: colors.radius,
1534
+ fontFamily: colors.font
1535
+ }).filter(([, value]) => value)
1536
+ );
1537
+ return { theme: dark ? "night" : "stripe", variables };
1538
+ }
1539
+ function useStripeAppearance(appearanceKey) {
1540
+ const probe = React2.useRef(null);
1541
+ const [stripeAppearance, setStripeAppearance] = React2.useState(null);
1542
+ React2.useEffect(() => {
1543
+ if (!probe.current) return;
1544
+ const style = getComputedStyle(probe.current);
1545
+ setStripeAppearance(
1546
+ toStripeAppearance({
1547
+ primary: style.color,
1548
+ background: style.backgroundColor,
1549
+ foreground: style.borderTopColor,
1550
+ danger: style.borderBottomColor,
1551
+ radius: style.borderTopLeftRadius,
1552
+ font: style.fontFamily
1553
+ })
1554
+ );
1555
+ }, [appearanceKey]);
1556
+ const probeElement = /* @__PURE__ */ jsxRuntime.jsx(
1557
+ "span",
1558
+ {
1559
+ ref: probe,
1560
+ "aria-hidden": "true",
1561
+ style: {
1562
+ display: "none",
1563
+ color: T.primary,
1564
+ backgroundColor: T.card,
1565
+ borderTop: `1px solid ${T.foreground}`,
1566
+ borderBottom: `1px solid ${T.danger}`,
1567
+ borderRadius: T.radius,
1568
+ fontFamily: T.font
1569
+ }
1095
1570
  }
1096
- };
1571
+ );
1572
+ return { probeElement, stripeAppearance };
1097
1573
  }
1098
1574
  function CheckoutInner({
1099
1575
  submitLabel,
@@ -1103,14 +1579,16 @@ function CheckoutInner({
1103
1579
  className,
1104
1580
  style,
1105
1581
  returnUrl,
1106
- children
1582
+ children,
1583
+ appearance
1107
1584
  }) {
1108
1585
  const stripe = reactStripeJs.useStripe();
1109
1586
  const elements = reactStripeJs.useElements();
1110
- const [isProcessing, setIsProcessing] = React.useState(false);
1111
- const [error, setError] = React.useState(null);
1112
- const [isReady, setIsReady] = React.useState(false);
1113
- const inFlight = React.useRef(false);
1587
+ const { el } = useAppearance(appearance);
1588
+ const [isProcessing, setIsProcessing] = React2.useState(false);
1589
+ const [error, setError] = React2.useState(null);
1590
+ const [isReady, setIsReady] = React2.useState(false);
1591
+ const inFlight = React2.useRef(false);
1114
1592
  async function confirmPayment() {
1115
1593
  if (!stripe || !elements || inFlight.current) return;
1116
1594
  inFlight.current = true;
@@ -1140,7 +1618,7 @@ function CheckoutInner({
1140
1618
  setIsProcessing(false);
1141
1619
  }
1142
1620
  }
1143
- return /* @__PURE__ */ jsxRuntime.jsx(CheckoutContext.Provider, { value: { confirmPayment, isProcessing, error }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style, children: [
1621
+ return /* @__PURE__ */ jsxRuntime.jsx(CheckoutContext.Provider, { value: { confirmPayment, isProcessing, error }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1144
1622
  /* @__PURE__ */ jsxRuntime.jsx("style", { children: `@keyframes nozle-skeleton-pulse { 0%{background-position:200% 0} 100%{background-position:-200% 0} }` }),
1145
1623
  !isReady && /* @__PURE__ */ jsxRuntime.jsx(
1146
1624
  "div",
@@ -1148,8 +1626,8 @@ function CheckoutInner({
1148
1626
  "data-testid": "checkout-skeleton",
1149
1627
  style: {
1150
1628
  height: "200px",
1151
- borderRadius: "var(--nozle-radius, 0.5rem)",
1152
- background: "linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%)",
1629
+ borderRadius: T.radius,
1630
+ background: `linear-gradient(90deg, ${T.muted} 25%, ${T.skeleton} 50%, ${T.muted} 75%)`,
1153
1631
  backgroundSize: "200% 100%",
1154
1632
  animation: "nozle-skeleton-pulse 1.5s ease-in-out infinite"
1155
1633
  }
@@ -1164,16 +1642,7 @@ function CheckoutInner({
1164
1642
  }
1165
1643
  }
1166
1644
  ),
1167
- error !== null && /* @__PURE__ */ jsxRuntime.jsx(
1168
- "p",
1169
- {
1170
- style: {
1171
- color: "var(--nozle-destructive, #dc2626)",
1172
- margin: "0.5rem 0 0"
1173
- },
1174
- children: error.message
1175
- }
1176
- ),
1645
+ error !== null && /* @__PURE__ */ jsxRuntime.jsx("p", { ...el("checkout__error", { color: T.dangerText, margin: "0.5rem 0 0" }), children: error.message }),
1177
1646
  children ?? /* @__PURE__ */ jsxRuntime.jsx(
1178
1647
  "button",
1179
1648
  {
@@ -1181,18 +1650,7 @@ function CheckoutInner({
1181
1650
  onClick: () => void confirmPayment(),
1182
1651
  disabled: isProcessing || !stripe,
1183
1652
  "aria-busy": isProcessing,
1184
- style: {
1185
- padding: "0.75rem 1.5rem",
1186
- borderRadius: "var(--nozle-radius, 0.5rem)",
1187
- border: "none",
1188
- background: "var(--nozle-primary, var(--primary))",
1189
- color: "var(--nozle-primary-foreground, var(--primary-foreground))",
1190
- cursor: isProcessing ? "not-allowed" : "pointer",
1191
- fontWeight: 500,
1192
- opacity: isProcessing ? 0.7 : 1,
1193
- marginTop: "1rem",
1194
- width: "100%"
1195
- },
1653
+ ...el("checkout__submit", { ...buttonStyle("primary", isProcessing), marginTop: "1rem", width: "100%" }),
1196
1654
  children: isProcessing ? "Processing..." : submitLabel ?? "Pay now"
1197
1655
  }
1198
1656
  )
@@ -1210,19 +1668,21 @@ function StripeCheckout({
1210
1668
  onReady,
1211
1669
  className,
1212
1670
  style,
1213
- children
1671
+ children,
1672
+ appearance: themeAppearance
1214
1673
  }) {
1215
- const stripePromise = React.useMemo(
1674
+ const { root } = useAppearance(themeAppearance);
1675
+ const { probeElement, stripeAppearance } = useStripeAppearance(JSON.stringify(root("checkout").style));
1676
+ const stripePromise = React2.useMemo(
1216
1677
  () => stripeJs.loadStripe(publishableKey, stripeAccount ? { stripeAccount } : void 0),
1217
1678
  [publishableKey, stripeAccount]
1218
1679
  );
1219
- const appearance = React.useMemo(() => buildStripeAppearance(), []);
1220
- const embeddedOptions = React.useMemo(
1680
+ const embeddedOptions = React2.useMemo(
1221
1681
  () => ({ clientSecret, onComplete }),
1222
1682
  [clientSecret, onComplete]
1223
1683
  );
1224
1684
  if (clientSecret.startsWith("cs_")) {
1225
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className, style, children: /* @__PURE__ */ jsxRuntime.jsx(
1685
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ...root("checkout", { ...text.body, ...style }, className), children: /* @__PURE__ */ jsxRuntime.jsx(
1226
1686
  reactStripeJs.EmbeddedCheckoutProvider,
1227
1687
  {
1228
1688
  stripe: stripePromise,
@@ -1231,26 +1691,29 @@ function StripeCheckout({
1231
1691
  }
1232
1692
  ) });
1233
1693
  }
1234
- const options = { clientSecret, appearance };
1235
- return /* @__PURE__ */ jsxRuntime.jsx(reactStripeJs.Elements, { stripe: stripePromise, options, children: /* @__PURE__ */ jsxRuntime.jsx(
1236
- CheckoutInner,
1237
- {
1238
- submitLabel,
1239
- onSuccess,
1240
- onError,
1241
- onReady,
1242
- className,
1243
- style,
1244
- returnUrl,
1245
- children
1246
- }
1247
- ) }, clientSecret);
1694
+ const options = stripeAppearance && { clientSecret, appearance: stripeAppearance };
1695
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ...root("checkout", { ...text.body, ...style }, className), children: [
1696
+ probeElement,
1697
+ options && /* @__PURE__ */ jsxRuntime.jsx(reactStripeJs.Elements, { stripe: stripePromise, options, children: /* @__PURE__ */ jsxRuntime.jsx(
1698
+ CheckoutInner,
1699
+ {
1700
+ submitLabel,
1701
+ onSuccess,
1702
+ onError,
1703
+ onReady,
1704
+ returnUrl,
1705
+ children,
1706
+ appearance: themeAppearance
1707
+ }
1708
+ ) }, clientSecret)
1709
+ ] });
1248
1710
  }
1249
1711
  function ProviderCheckout(props) {
1250
1712
  const billing = useOptionalBillingContext();
1251
- const [busy, setBusy] = React.useState(false);
1252
- const [error, setError] = React.useState(null);
1253
- const [processing, setProcessing] = React.useState(false);
1713
+ const { root, el } = useAppearance(props.appearance);
1714
+ const [busy, setBusy] = React2.useState(false);
1715
+ const [error, setError] = React2.useState(null);
1716
+ const [processing, setProcessing] = React2.useState(false);
1254
1717
  const pay = async () => {
1255
1718
  if (busy) return;
1256
1719
  setBusy(true);
@@ -1297,17 +1760,19 @@ function ProviderCheckout(props) {
1297
1760
  submitLabel: props.submitLabel,
1298
1761
  onSuccess: props.onSuccess,
1299
1762
  onComplete: props.onComplete,
1300
- onError: props.onError
1763
+ onError: props.onError,
1764
+ className: props.className,
1765
+ appearance: props.appearance
1301
1766
  }
1302
1767
  );
1303
1768
  }
1304
- return /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", children: "Payment setup is incomplete. Contact support to continue." });
1769
+ return /* @__PURE__ */ jsxRuntime.jsx("p", { ...root("checkout", { ...text.body, color: T.dangerText }, props.className), role: "alert", children: "Payment setup is incomplete. Contact support to continue." });
1305
1770
  }
1306
1771
  return /* @__PURE__ */ jsxRuntime.jsx(
1307
1772
  CheckoutContext.Provider,
1308
1773
  {
1309
1774
  value: { confirmPayment: pay, isProcessing: busy, error },
1310
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: props.className, children: [
1775
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { ...root("checkout", text.body, props.className), children: [
1311
1776
  /* @__PURE__ */ jsxRuntime.jsx(
1312
1777
  "button",
1313
1778
  {
@@ -1315,39 +1780,42 @@ function ProviderCheckout(props) {
1315
1780
  disabled: busy,
1316
1781
  "aria-busy": busy,
1317
1782
  onClick: () => void pay(),
1783
+ ...el("checkout__submit", buttonStyle("primary", busy)),
1318
1784
  children: busy ? "Processing\u2026" : props.submitLabel || "Pay now"
1319
1785
  }
1320
1786
  ),
1321
1787
  processing && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", children: "Payment is processing. Confirmation may take a little while." }),
1322
- error && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", children: error.message })
1788
+ error && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", ...el("checkout__error", { color: T.dangerText }), children: error.message })
1323
1789
  ] })
1324
1790
  }
1325
1791
  );
1326
1792
  }
1327
1793
  function Checkout(props) {
1328
- return "checkout" in props ? /* @__PURE__ */ jsxRuntime.jsx(ProviderCheckout, { ...props }) : /* @__PURE__ */ jsxRuntime.jsx(StripeCheckout, { ...props });
1794
+ return /* @__PURE__ */ jsxRuntime.jsx(NozleBranding, { children: "checkout" in props ? /* @__PURE__ */ jsxRuntime.jsx(ProviderCheckout, { ...props }) : /* @__PURE__ */ jsxRuntime.jsx(StripeCheckout, { ...props }) });
1329
1795
  }
1330
1796
  var styles2 = `
1331
- .nozle-upgrade-modal{position:fixed;inset:0;background:#0006;display:flex;align-items:center;justify-content:center;z-index:1000;padding:20px;color:var(--nozle-portal-text,#202922);font:inherit}
1332
- .nozle-upgrade-modal .nzu-dialog{box-sizing:border-box;background:var(--nozle-portal-background,#fff);border:1px solid var(--nozle-portal-border,#dce2d9);border-radius:12px;padding:24px;max-width:480px;width:100%;max-height:90dvh;overflow:auto;box-shadow:0 12px 48px #0003}
1333
- .nozle-upgrade-modal h2{font-size:20px;margin:0 0 16px}
1334
- .nozle-upgrade-modal p{line-height:1.55}
1335
- .nozle-upgrade-modal button{font:inherit;padding:9px 14px;border:1px solid var(--nozle-portal-border,#dce2d9);border-radius:7px;background:var(--nozle-portal-background,#fff);color:inherit;cursor:pointer}
1336
- .nozle-upgrade-modal button:disabled{opacity:.55;cursor:wait}
1337
- .nozle-upgrade-modal :focus-visible{outline:2px solid var(--nozle-portal-accent,#526949);outline-offset:3px}
1338
- .nozle-upgrade-modal .nzu-buttons{display:flex;flex-wrap:wrap;gap:10px;justify-content:flex-end;margin-top:20px}
1339
- .nozle-upgrade-modal [role=alert]{color:#922e25}
1797
+ .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}}
1798
+ .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}
1799
+ .nozle-upgradeModal h2{font:${T.fontTitle};letter-spacing:normal;color:${T.foreground};margin:0 0 16px}
1800
+ .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}
1801
+ .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}}
1802
+ .nozle-upgradeModal button:disabled{opacity:.55;cursor:wait}
1803
+ .nozle-upgradeModal :focus-visible{outline:2px solid ${T.primary};outline-offset:3px}
1804
+ .nozle-upgradeModal .nozle-upgradeModal__actions{display:flex;flex-wrap:wrap;gap:10px;justify-content:flex-end;margin-top:20px}
1805
+ .nozle-upgradeModal .nzu-back{margin-right:auto}
1806
+ .nozle-upgradeModal [role=alert]{color:${T.dangerText}}
1340
1807
  `;
1341
1808
  function UpgradeModal(props) {
1342
1809
  const billing = useOptionalBillingContext();
1343
- const [processing, setProcessing] = React.useState(false);
1344
- const [error, setError] = React.useState(null);
1345
- const [confirming, setConfirming] = React.useState(false);
1346
- const inFlight = React.useRef(false);
1347
- const dialog = React.useRef(null);
1348
- const titleId = React.useId();
1810
+ const [processing, setProcessing] = React2.useState(false);
1811
+ const [error, setError] = React2.useState(null);
1812
+ const [confirming, setConfirming] = React2.useState(false);
1813
+ const inFlight = React2.useRef(false);
1814
+ const dialog = React2.useRef(null);
1815
+ const titleId = React2.useId();
1349
1816
  const busy = Boolean(props.busy || confirming);
1350
- React.useEffect(() => {
1817
+ const { root, el } = useAppearance(props.appearance);
1818
+ React2.useEffect(() => {
1351
1819
  if (!props.isOpen) return;
1352
1820
  const previous = document.activeElement;
1353
1821
  setError(null);
@@ -1406,17 +1874,17 @@ function UpgradeModal(props) {
1406
1874
  }
1407
1875
  }
1408
1876
  if (!props.isOpen) return null;
1409
- const money2 = (value) => new Intl.NumberFormat(props.locale ?? "en-US", {
1877
+ const money3 = (value) => new Intl.NumberFormat(props.locale ?? "en-US", {
1410
1878
  style: "currency",
1411
1879
  currency: props.preview?.currency ?? "USD"
1412
1880
  }).format(value);
1413
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nozle-upgrade-modal", children: [
1881
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ...root("upgradeModal", void 0, "nozle-upgrade-modal"), children: [
1414
1882
  /* @__PURE__ */ jsxRuntime.jsx("style", { nonce: props.nonce, children: styles2 }),
1415
1883
  /* @__PURE__ */ jsxRuntime.jsxs(
1416
1884
  "div",
1417
1885
  {
1418
1886
  ref: dialog,
1419
- className: "nzu-dialog",
1887
+ ...el("upgradeModal__dialog"),
1420
1888
  role: "dialog",
1421
1889
  "aria-modal": "true",
1422
1890
  "aria-labelledby": titleId,
@@ -1454,15 +1922,15 @@ function UpgradeModal(props) {
1454
1922
  props.preview && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1455
1923
  props.preview.credit > 0 && /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
1456
1924
  "Credits applied: ",
1457
- money2(-props.preview.credit)
1925
+ money3(-props.preview.credit)
1458
1926
  ] }),
1459
1927
  /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
1460
1928
  "New plan charge: ",
1461
- money2(props.preview.debit)
1929
+ money3(props.preview.debit)
1462
1930
  ] }),
1463
1931
  /* @__PURE__ */ jsxRuntime.jsx("p", { children: /* @__PURE__ */ jsxRuntime.jsxs("strong", { children: [
1464
1932
  "Due today: ",
1465
- money2(props.preview.net)
1933
+ money3(props.preview.net)
1466
1934
  ] }) }),
1467
1935
  /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
1468
1936
  "Next billing date:",
@@ -1474,18 +1942,21 @@ function UpgradeModal(props) {
1474
1942
  ] })
1475
1943
  ] }),
1476
1944
  props.children,
1477
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nzu-buttons", children: [
1945
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { ...el("upgradeModal__actions"), children: [
1946
+ props.onBack && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "nzu-back", onClick: props.onBack, disabled: busy, children: "Back" }),
1478
1947
  /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: props.onCancel, disabled: busy, children: "Close" }),
1479
1948
  /* @__PURE__ */ jsxRuntime.jsx(
1480
1949
  "button",
1481
1950
  {
1482
1951
  type: "button",
1952
+ className: "nzu-primary",
1483
1953
  onClick: () => void handleConfirm(),
1484
1954
  disabled: busy || props.confirmDisabled,
1485
1955
  children: busy ? "Checking subscription\u2026" : props.confirmLabel ?? "Confirm"
1486
1956
  }
1487
1957
  )
1488
- ] })
1958
+ ] }),
1959
+ /* @__PURE__ */ jsxRuntime.jsx(NozleBranding, {})
1489
1960
  ]
1490
1961
  }
1491
1962
  )
@@ -1533,67 +2004,238 @@ function formatBillingDate(value, locale = "en-US", timezone) {
1533
2004
  }).format(new Date(value));
1534
2005
  }
1535
2006
  }
1536
- var styles3 = `
1537
- .nozle-plan-change{font:inherit;color:var(--nozle-portal-text,#202922);margin-top:12px}
1538
- .nozle-plan-change button{font:inherit;padding:9px 14px;border:1px solid var(--nozle-portal-border,#dce2d9);border-radius:7px;background:var(--nozle-portal-background,#fff);color:inherit;cursor:pointer}
1539
- .nozle-plan-change button:disabled{opacity:.55;cursor:wait}
1540
- .nozle-plan-change :focus-visible{outline:2px solid var(--nozle-portal-accent,#526949);outline-offset:3px}
1541
- .nozle-plan-change [role=alert]{color:#922e25}
1542
- .nozle-plan-change .nzpc-actions{display:flex;gap:10px;flex-wrap:wrap}
1543
- .nozle-upgrade-modal select{box-sizing:border-box;width:100%;font:inherit;color:inherit;padding:10px;margin:8px 0;border:1px solid #dce2d9;border-radius:7px;background:var(--nozle-portal-background,#fff)}
1544
- .nozle-upgrade-modal dl{display:block;margin:16px 0;padding:0}
1545
- .nozle-upgrade-modal dl div{display:flex;justify-content:space-between;gap:16px;margin:12px 0}
1546
- .nozle-upgrade-modal dd{margin:0;text-align:right}
1547
- `;
1548
- var paymentPending = (state) => ["awaiting_payment", "processing", "needs_review"].includes(
1549
- state.checkoutStatus
1550
- );
1551
- function paymentMessage(state) {
1552
- if (state.checkoutStatus === "needs_review")
1553
- return "Payment needs review. Contact support before trying another payment.";
1554
- return state.checkoutStatus === "awaiting_payment" ? "Complete payment to change your plan." : "Your change is processing. Your current plan remains active until confirmation.";
2007
+
2008
+ // src/components/pricing/plan-features.ts
2009
+ function number(value) {
2010
+ const parsed = Number(value);
2011
+ return Number.isFinite(parsed) ? parsed.toLocaleString("en-US", { maximumFractionDigits: 6 }) : String(value);
1555
2012
  }
1556
- function done(attempt, state) {
1557
- if (attempt.operation === "withdraw")
1558
- return !state.pendingChange && state.status === "active" && state.currentPlan.code === attempt.fromPlan;
1559
- return state.currentPlan.code === attempt.target && state.status === "active" || state.pendingChange?.plan.code === attempt.target;
2013
+ function money(amount, currency) {
2014
+ const parsed = Number(amount);
2015
+ if (!Number.isFinite(parsed)) return `${amount} ${currency}`;
2016
+ try {
2017
+ return new Intl.NumberFormat("en-US", { style: "currency", currency, maximumFractionDigits: 6 }).format(parsed);
2018
+ } catch {
2019
+ return `${currency} ${number(amount)}`;
2020
+ }
1560
2021
  }
1561
- function PlanChangeControl(props) {
1562
- return /* @__PURE__ */ jsxRuntime.jsx(PlanChangeFlow, { ...props }, props.subscriptionId);
2022
+ function words(code) {
2023
+ return code.replace(/_/g, " ");
1563
2024
  }
1564
- function PlanChangeFlow({
1565
- subscriptionId,
1566
- refreshKey,
1567
- actions,
1568
- onChanged,
1569
- onError,
1570
- returnUrl,
1571
- locale = "en-US",
1572
- timezone = "UTC",
1573
- nonce
1574
- }) {
1575
- const owner = React.useMemo(
1576
- () => ({ controller: new AbortController() }),
1577
- [subscriptionId, actions]
1578
- );
1579
- const latest = React.useRef(owner);
1580
- latest.current = owner;
1581
- const [loaded, setLoaded] = React.useState();
1582
- const [busy, setBusy] = React.useState(false);
1583
- const [failure, setFailure] = React.useState();
1584
- const [notice, setNotice] = React.useState("");
1585
- const [mode, setMode] = React.useState(
2025
+ function entitlementRows(entitlement) {
2026
+ if (entitlement.feature_type === "config") {
2027
+ return (entitlement.privileges ?? []).flatMap((privilege) => {
2028
+ if (privilege.value === null || privilege.value === false) return [];
2029
+ if (privilege.value === true) return [`${entitlement.name}: ${privilege.name}`];
2030
+ return [`${entitlement.name}: ${privilege.value}`];
2031
+ });
2032
+ }
2033
+ if (entitlement.feature_type === "metered" && entitlement.usage_limit != null) {
2034
+ return [`${number(entitlement.usage_limit)} ${entitlement.name}`];
2035
+ }
2036
+ return [entitlement.name];
2037
+ }
2038
+ function creditGrantRow(rule) {
2039
+ const credits = `${number(rule.amount)} ${words(rule.credit_system_code)}`;
2040
+ if (rule.interval === "lifetime") return `${credits} (one time)`;
2041
+ const count = rule.interval_count ?? 1;
2042
+ return count === 1 ? `${credits} every ${rule.interval}` : `${credits} every ${count} ${rule.interval}s`;
2043
+ }
2044
+ function isZero(amount) {
2045
+ return amount === void 0 || Number(amount) === 0;
2046
+ }
2047
+ function rangeLabel(range, unit) {
2048
+ const from = number(range.from_value);
2049
+ return range.to_value === null ? `${from}+ ${unit}` : `${from} to ${number(range.to_value)} ${unit}`;
2050
+ }
2051
+ function rangeRows(ranges, unit, currency) {
2052
+ return ranges.map((range) => {
2053
+ const parts = [];
2054
+ if (!isZero(range.flat_amount)) parts.push(`${money(range.flat_amount, currency)} flat`);
2055
+ if (!isZero(range.per_unit_amount)) parts.push(`${money(range.per_unit_amount, currency)} each`);
2056
+ if (!isZero(range.rate)) parts.push(`${number(range.rate)}%`);
2057
+ return `${rangeLabel(range, unit)}: ${parts.length ? parts.join(" + ") : "free"}`;
2058
+ });
2059
+ }
2060
+ function chargeRows(charge, currency) {
2061
+ const properties = charge.properties ?? {};
2062
+ const unit = charge.feature_code ? words(charge.feature_code) : "unit";
2063
+ switch (charge.charge_model) {
2064
+ case "standard":
2065
+ return properties.amount === void 0 ? [] : [`${money(properties.amount, currency)} per ${unit}`];
2066
+ case "package": {
2067
+ if (properties.amount === void 0 || !properties.package_size) return [];
2068
+ const free = properties.free_units ? `, first ${number(properties.free_units)} free` : "";
2069
+ return [`${money(properties.amount, currency)} per ${number(properties.package_size)} ${unit}${free}`];
2070
+ }
2071
+ case "graduated":
2072
+ return rangeRows(properties.graduated_ranges ?? [], unit, currency);
2073
+ case "volume":
2074
+ return rangeRows(properties.volume_ranges ?? [], unit, currency);
2075
+ case "graduated_percentage":
2076
+ return rangeRows(properties.graduated_percentage_ranges ?? [], unit, currency);
2077
+ case "percentage": {
2078
+ if (properties.rate === void 0) return [];
2079
+ const fixed = isZero(properties.fixed_amount) ? "" : ` + ${money(properties.fixed_amount, currency)} per ${unit}`;
2080
+ return [`${number(properties.rate)}% of each ${unit}${fixed}`];
2081
+ }
2082
+ default:
2083
+ return [];
2084
+ }
2085
+ }
2086
+ function planFeatures(plan) {
2087
+ const rows = (plan.entitlements ?? []).flatMap(entitlementRows);
2088
+ rows.push(...(plan.credit_grant_rules ?? []).map(creditGrantRow));
2089
+ for (const charge of plan.charges ?? []) rows.push(...chargeRows(charge, plan.amount_currency));
2090
+ return rows;
2091
+ }
2092
+ var INTERVAL_LABELS = {
2093
+ weekly: "week",
2094
+ monthly: "month",
2095
+ quarterly: "quarter",
2096
+ semiannual: "6 months",
2097
+ yearly: "year"
2098
+ };
2099
+ function intervalLabel(interval) {
2100
+ return INTERVAL_LABELS[interval] ?? interval;
2101
+ }
2102
+ function formatPlanPrice(amountCents, currency) {
2103
+ const amount = amountCents / 100;
2104
+ try {
2105
+ return new Intl.NumberFormat("en-US", {
2106
+ style: "currency",
2107
+ currency,
2108
+ minimumFractionDigits: Number.isInteger(amount) ? 0 : 2,
2109
+ maximumFractionDigits: 2
2110
+ }).format(amount);
2111
+ } catch {
2112
+ return `${currency} ${amount}`;
2113
+ }
2114
+ }
2115
+ function entitlementValue(entitlement) {
2116
+ if (entitlement.feature_type === "config") {
2117
+ const values = (entitlement.privileges ?? []).filter((privilege) => privilege.value !== null && privilege.value !== false).map((privilege) => privilege.value === true ? privilege.name : String(privilege.value));
2118
+ return values.length ? values.join(", ") : false;
2119
+ }
2120
+ if (entitlement.feature_type === "metered" && entitlement.usage_limit != null) {
2121
+ return number(entitlement.usage_limit);
2122
+ }
2123
+ return true;
2124
+ }
2125
+ function comparisonRows(plans) {
2126
+ const rows = /* @__PURE__ */ new Map();
2127
+ const row = (key, label) => {
2128
+ let existing = rows.get(key);
2129
+ if (!existing) {
2130
+ existing = { key, label, values: {} };
2131
+ rows.set(key, existing);
2132
+ }
2133
+ return existing;
2134
+ };
2135
+ for (const plan of plans) {
2136
+ for (const entitlement of plan.entitlements ?? []) {
2137
+ row(`entitlement:${entitlement.code}`, entitlement.name).values[plan.code] = entitlementValue(entitlement);
2138
+ }
2139
+ for (const rule of plan.credit_grant_rules ?? []) {
2140
+ row(`credit:${rule.credit_system_code}`, words(rule.credit_system_code)).values[plan.code] = creditGrantRow(rule);
2141
+ }
2142
+ }
2143
+ const all = [...rows.values()];
2144
+ for (const item of all) {
2145
+ for (const plan of plans) item.values[plan.code] ??= false;
2146
+ }
2147
+ return all;
2148
+ }
2149
+ function sortPlansByPrice(plans) {
2150
+ const currencyRank = /* @__PURE__ */ new Map();
2151
+ for (const plan of plans) {
2152
+ if (!currencyRank.has(plan.amount_currency)) currencyRank.set(plan.amount_currency, currencyRank.size);
2153
+ }
2154
+ return plans.map((plan, index) => ({ plan, index })).sort(
2155
+ (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
2156
+ ).map(({ plan }) => plan);
2157
+ }
2158
+ var styles3 = `
2159
+ .nozle-plan-change{font:${T.fontBody};color:${T.foreground};margin-top:12px}
2160
+ .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}
2161
+ .nozle-plan-change button:disabled{opacity:.55;cursor:wait}
2162
+ .nozle-plan-change :focus-visible{outline:2px solid ${T.primary};outline-offset:3px}
2163
+ .nozle-plan-change [role=alert]{color:${T.dangerText}}
2164
+ .nozle-plan-change .nzpc-actions{display:flex;gap:10px;flex-wrap:wrap}
2165
+ .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}}
2166
+ .nozle-upgrade-modal dl{display:block;margin:16px 0;padding:0}
2167
+ .nozle-upgrade-modal dl div{display:flex;justify-content:space-between;gap:16px;margin:12px 0}
2168
+ .nozle-upgrade-modal dt{font:${T.fontMeta};color:${T.mutedForeground}}
2169
+ .nozle-upgrade-modal dd{margin:0;text-align:right}
2170
+ .nozle-upgrade-modal label{font:${T.fontBody};font-weight:500}
2171
+ .nozle-plan-change .nzpc-plans{display:grid;gap:10px}
2172
+ .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}}
2173
+ .nozle-plan-change label.nzpc-plan[data-selected]{border-color:${T.primary};background:${T.primarySoft}}
2174
+ .nozle-plan-change label.nzpc-plan input{flex:none;width:auto;margin:4px 0 0;padding:0;accent-color:${T.primary}}
2175
+ .nozle-plan-change .nzpc-plan-body{display:grid;gap:4px;min-width:0;flex:1}
2176
+ .nozle-plan-change .nzpc-plan-head{display:flex;justify-content:space-between;align-items:baseline;gap:8px}
2177
+ .nozle-plan-change .nzpc-plan-name{font:${T.fontHeading}}
2178
+ .nozle-plan-change .nzpc-plan-op{font:${T.labelFont};letter-spacing:${T.labelSpacing};text-transform:${T.labelTransform};color:${T.mutedForeground}}
2179
+ .nozle-plan-change .nzpc-plan-price{font-variant-numeric:tabular-nums}
2180
+ .nozle-plan-change .nzpc-plan-muted{font:${T.fontMeta};color:${T.mutedForeground};margin:0}
2181
+ .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}}
2182
+ `;
2183
+ var paymentPending = (state) => ["awaiting_payment", "processing", "needs_review"].includes(
2184
+ state.checkoutStatus
2185
+ );
2186
+ function paymentMessage(state) {
2187
+ if (state.checkoutStatus === "needs_review")
2188
+ return "Payment needs review. Contact support before trying another payment.";
2189
+ return state.checkoutStatus === "awaiting_payment" ? "Complete payment to change your plan." : "Your change is processing. Your current plan remains active until confirmation.";
2190
+ }
2191
+ function done(attempt, state) {
2192
+ if (attempt.operation === "withdraw")
2193
+ return !state.pendingChange && state.status === "active" && state.currentPlan.code === attempt.fromPlan;
2194
+ return state.currentPlan.code === attempt.target && state.status === "active" || state.pendingChange?.plan.code === attempt.target;
2195
+ }
2196
+ function PlanChangeControl(props) {
2197
+ return /* @__PURE__ */ jsxRuntime.jsx(PlanChangeFlow, { ...props }, props.subscriptionId);
2198
+ }
2199
+ function PlanChangeFlow({
2200
+ subscriptionId,
2201
+ refreshKey,
2202
+ actions,
2203
+ onChanged,
2204
+ onError,
2205
+ returnUrl,
2206
+ locale = "en-US",
2207
+ timezone = "UTC",
2208
+ nonce,
2209
+ appearance
2210
+ }) {
2211
+ const { root } = useAppearance(appearance);
2212
+ const owner = React2.useMemo(
2213
+ () => ({ controller: new AbortController() }),
2214
+ [subscriptionId, actions]
2215
+ );
2216
+ const latest = React2.useRef(owner);
2217
+ latest.current = owner;
2218
+ const [loaded, setLoaded] = React2.useState();
2219
+ const [busy, setBusy] = React2.useState(false);
2220
+ const [failure, setFailure] = React2.useState();
2221
+ const [notice, setNotice] = React2.useState("");
2222
+ const [mode, setMode] = React2.useState(
1586
2223
  null
1587
2224
  );
1588
- const [target, setTarget] = React.useState("");
1589
- const [quote, setQuote] = React.useState();
1590
- const [checkout, setCheckout] = React.useState(null);
1591
- const [withdrawTarget, setWithdrawTarget] = React.useState();
1592
- const pending = React.useRef(void 0);
1593
- const previousRefresh = React.useRef(refreshKey);
1594
- const inFlight = React.useRef(null);
2225
+ const [target, setTarget] = React2.useState("");
2226
+ const billing = useOptionalBillingContext();
2227
+ const catalog = reactQuery.useQuery(
2228
+ { ...catalogQuery(billing?.client), enabled: Boolean(billing) },
2229
+ billing?.queryClient ?? idleQueryClient
2230
+ );
2231
+ const [quote, setQuote] = React2.useState();
2232
+ const [checkout, setCheckout] = React2.useState(null);
2233
+ const [withdrawTarget, setWithdrawTarget] = React2.useState();
2234
+ const pending = React2.useRef(void 0);
2235
+ const previousRefresh = React2.useRef(refreshKey);
2236
+ const inFlight = React2.useRef(null);
1595
2237
  const state = loaded?.owner === owner ? loaded.value : void 0;
1596
- const errorCallback = React.useRef(onError), changedCallback = React.useRef(onChanged);
2238
+ const errorCallback = React2.useRef(onError), changedCallback = React2.useRef(onChanged);
1597
2239
  errorCallback.current = onError;
1598
2240
  changedCallback.current = onChanged;
1599
2241
  async function run(task) {
@@ -1683,7 +2325,7 @@ function PlanChangeFlow({
1683
2325
  }
1684
2326
  return value;
1685
2327
  }
1686
- React.useEffect(() => {
2328
+ React2.useEffect(() => {
1687
2329
  inFlight.current?.abort();
1688
2330
  inFlight.current = null;
1689
2331
  if (owner.controller.signal.aborted)
@@ -1701,7 +2343,7 @@ function PlanChangeFlow({
1701
2343
  owner.controller.abort();
1702
2344
  };
1703
2345
  }, [owner]);
1704
- React.useEffect(() => {
2346
+ React2.useEffect(() => {
1705
2347
  if (previousRefresh.current === refreshKey) return;
1706
2348
  previousRefresh.current = refreshKey;
1707
2349
  let stopped = false;
@@ -1722,7 +2364,7 @@ function PlanChangeFlow({
1722
2364
  clearTimeout(timer);
1723
2365
  };
1724
2366
  }, [refreshKey, owner]);
1725
- React.useEffect(() => {
2367
+ React2.useEffect(() => {
1726
2368
  if (!state || state.checkoutStatus === "needs_review" || !paymentPending(state) && !pending.current?.accepted)
1727
2369
  return;
1728
2370
  const timer = setInterval(() => {
@@ -1853,7 +2495,7 @@ function PlanChangeFlow({
1853
2495
  await reconcile(signal);
1854
2496
  });
1855
2497
  const selected = state?.eligiblePlans.find((plan) => plan.code === target);
1856
- const money2 = (amount) => formatMinorAmount(
2498
+ const money3 = (amount) => formatMinorAmount(
1857
2499
  amount,
1858
2500
  quote?.currency ?? state?.currentPlan.currency ?? "USD",
1859
2501
  locale
@@ -1861,7 +2503,7 @@ function PlanChangeFlow({
1861
2503
  const blocked = Boolean(
1862
2504
  state?.endingAt || state?.pendingChange || state && paymentPending(state) || state?.blockedReason || pending.current?.accepted
1863
2505
  );
1864
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nozle-plan-change", children: [
2506
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ...root("planChangeControl", void 0, "nozle-plan-change"), children: [
1865
2507
  /* @__PURE__ */ jsxRuntime.jsx("style", { nonce, children: styles3 }),
1866
2508
  !state ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1867
2509
  /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", children: "Loading plan changes\u2026" }),
@@ -1959,6 +2601,7 @@ function PlanChangeFlow({
1959
2601
  /* @__PURE__ */ jsxRuntime.jsxs(
1960
2602
  UpgradeModal,
1961
2603
  {
2604
+ appearance,
1962
2605
  isOpen: mode !== null,
1963
2606
  planCode: target,
1964
2607
  title: mode === "withdraw" ? "Keep your current plan?" : mode === "choose" ? "Change plan" : `Confirm ${quote?.operation ?? "plan change"}`,
@@ -1967,42 +2610,55 @@ function PlanChangeFlow({
1967
2610
  confirmDisabled: mode === "choose" && (!target || blocked) || mode === "confirm" && !quote,
1968
2611
  confirmLabel: pending.current?.attempted ? "Check status / retry" : mode === "choose" ? "Review change" : mode === "withdraw" ? "Confirm keep current plan" : quote?.operation === "downgrade" ? "Confirm downgrade" : "Confirm upgrade",
1969
2612
  onCancel: () => setMode(null),
2613
+ onBack: mode === "confirm" ? () => {
2614
+ setMode("choose");
2615
+ setQuote(void 0);
2616
+ setFailure(void 0);
2617
+ } : void 0,
1970
2618
  onSubmit: confirm,
1971
2619
  children: [
1972
2620
  failure && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", children: failure }),
1973
- mode === "choose" && /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
1974
- "Choose a plan",
1975
- /* @__PURE__ */ jsxRuntime.jsxs(
1976
- "select",
2621
+ mode === "choose" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nzpc-plans", role: "radiogroup", "aria-label": "Choose a plan", children: state.eligiblePlans.map((plan) => {
2622
+ const details = catalog.data?.plans.find((entry) => entry.code === plan.code);
2623
+ const features = details ? planFeatures(details) : [];
2624
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2625
+ "label",
1977
2626
  {
1978
- value: target,
1979
- onChange: (event) => {
1980
- setTarget(event.target.value);
1981
- setQuote(void 0);
1982
- },
1983
- disabled: busy,
2627
+ className: "nzpc-plan",
2628
+ "data-selected": target === plan.code ? "" : void 0,
1984
2629
  children: [
1985
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Select a plan" }),
1986
- state.eligiblePlans.map((plan) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: plan.code, children: [
1987
- plan.name,
1988
- " \u2014",
1989
- " ",
1990
- formatMinorAmount(
1991
- plan.amountCents,
1992
- plan.currency,
1993
- locale
1994
- ),
1995
- " ",
1996
- "/ ",
1997
- plan.interval,
1998
- " (",
1999
- plan.operation,
2000
- ")"
2001
- ] }, plan.code))
2630
+ /* @__PURE__ */ jsxRuntime.jsx(
2631
+ "input",
2632
+ {
2633
+ type: "radio",
2634
+ name: `nozle-plan-${subscriptionId}`,
2635
+ value: plan.code,
2636
+ checked: target === plan.code,
2637
+ disabled: busy,
2638
+ onChange: () => {
2639
+ setTarget(plan.code);
2640
+ setQuote(void 0);
2641
+ }
2642
+ }
2643
+ ),
2644
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "nzpc-plan-body", children: [
2645
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "nzpc-plan-head", children: [
2646
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "nzpc-plan-name", children: plan.name }),
2647
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "nzpc-plan-op", children: plan.operation === "upgrade" ? "Upgrade" : "Downgrade" })
2648
+ ] }),
2649
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "nzpc-plan-price", children: [
2650
+ formatMinorAmount(plan.amountCents, plan.currency, locale),
2651
+ " / ",
2652
+ plan.interval
2653
+ ] }),
2654
+ details?.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "nzpc-plan-muted", children: details.description }),
2655
+ features.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("ul", { children: features.map((feature) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: feature }, feature)) })
2656
+ ] })
2002
2657
  ]
2003
- }
2004
- )
2005
- ] }),
2658
+ },
2659
+ plan.code
2660
+ );
2661
+ }) }),
2006
2662
  mode === "withdraw" && /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
2007
2663
  "Remove the scheduled change to ",
2008
2664
  withdrawTarget?.name,
@@ -2031,19 +2687,19 @@ function PlanChangeFlow({
2031
2687
  /* @__PURE__ */ jsxRuntime.jsxs("dl", { children: [
2032
2688
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2033
2689
  /* @__PURE__ */ jsxRuntime.jsx("dt", { children: "New plan charge" }),
2034
- /* @__PURE__ */ jsxRuntime.jsx("dd", { children: money2(quote.debitAmountCents) })
2690
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { children: money3(quote.debitAmountCents) })
2035
2691
  ] }),
2036
2692
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2037
2693
  /* @__PURE__ */ jsxRuntime.jsx("dt", { children: "Credits applied" }),
2038
- /* @__PURE__ */ jsxRuntime.jsx("dd", { children: money2(quote.creditAmountCents) })
2694
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { children: money3(quote.creditAmountCents) })
2039
2695
  ] }),
2040
2696
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2041
2697
  /* @__PURE__ */ jsxRuntime.jsx("dt", { children: "Due now" }),
2042
- /* @__PURE__ */ jsxRuntime.jsx("dd", { children: money2(quote.amountDueNowCents) })
2698
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { children: money3(quote.amountDueNowCents) })
2043
2699
  ] }),
2044
2700
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2045
2701
  /* @__PURE__ */ jsxRuntime.jsx("dt", { children: "Due at effective date" }),
2046
- /* @__PURE__ */ jsxRuntime.jsx("dd", { children: money2(quote.amountDueAtEffectiveCents) })
2702
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { children: money3(quote.amountDueAtEffectiveCents) })
2047
2703
  ] })
2048
2704
  ] }),
2049
2705
  /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
@@ -2062,6 +2718,19 @@ function PlanChangeFlow({
2062
2718
  ] })
2063
2719
  ] });
2064
2720
  }
2721
+ var PortalAppearance = React2.createContext({
2722
+ el: (key) => ({ className: "nozle-" + key })
2723
+ });
2724
+ function Part({
2725
+ as: Tag = "div",
2726
+ part,
2727
+ className,
2728
+ style,
2729
+ ...rest
2730
+ }) {
2731
+ const themed = React2.useContext(PortalAppearance).el(part, style, className);
2732
+ return /* @__PURE__ */ jsxRuntime.jsx(Tag, { ...rest, ...themed });
2733
+ }
2065
2734
  function asError(error) {
2066
2735
  return error instanceof BillingPortalError ? error : new BillingPortalError("request");
2067
2736
  }
@@ -2073,13 +2742,15 @@ function BillingPortal({
2073
2742
  className = "",
2074
2743
  style,
2075
2744
  nonce,
2076
- onError
2745
+ onError,
2746
+ appearance
2077
2747
  }) {
2078
- const [attempt, setAttempt] = React.useState(0);
2079
- const [state, setState] = React.useState();
2080
- const errorCallback = React.useRef(onError);
2748
+ const { root, el } = useAppearance(appearance);
2749
+ const [attempt, setAttempt] = React2.useState(0);
2750
+ const [state, setState] = React2.useState();
2751
+ const errorCallback = React2.useRef(onError);
2081
2752
  errorCallback.current = onError;
2082
- React.useEffect(() => {
2753
+ React2.useEffect(() => {
2083
2754
  const controller = new AbortController();
2084
2755
  let active = true;
2085
2756
  const timer = setTimeout(() => {
@@ -2111,11 +2782,10 @@ function BillingPortal({
2111
2782
  }, [createSession, attempt]);
2112
2783
  const current = state?.source === createSession && state.attempt === attempt ? state : void 0;
2113
2784
  const reconnect = () => setAttempt((value) => value + 1);
2114
- return /* @__PURE__ */ jsxRuntime.jsxs(
2785
+ return /* @__PURE__ */ jsxRuntime.jsx(PortalAppearance.Provider, { value: { el, appearance }, children: /* @__PURE__ */ jsxRuntime.jsxs(
2115
2786
  "div",
2116
2787
  {
2117
- className: `nozle-billing-portal ${className}`,
2118
- style,
2788
+ ...root("billingPortal", style, `nozle-billing-portal ${className}`),
2119
2789
  "aria-label": "Nozle billing portal",
2120
2790
  children: [
2121
2791
  /* @__PURE__ */ jsxRuntime.jsx("style", { nonce, children: portalStyles }),
@@ -2134,15 +2804,15 @@ function BillingPortal({
2134
2804
  ) : current?.error ? /* @__PURE__ */ jsxRuntime.jsx(Failure, { error: current.error, retry: reconnect }) : /* @__PURE__ */ jsxRuntime.jsx(Loading, { label: "Starting billing session\u2026" })
2135
2805
  ]
2136
2806
  }
2137
- );
2807
+ ) });
2138
2808
  }
2139
- function useQuery(client, query, variables, onError, retainDuringRefresh = false) {
2140
- const [attempt, setAttempt] = React.useState(0);
2809
+ function useQuery4(client, query, variables, onError, retainDuringRefresh = false) {
2810
+ const [attempt, setAttempt] = React2.useState(0);
2141
2811
  const key = JSON.stringify(variables);
2142
- const [state, setState] = React.useState();
2143
- const callback = React.useRef(onError);
2812
+ const [state, setState] = React2.useState();
2813
+ const callback = React2.useRef(onError);
2144
2814
  callback.current = onError;
2145
- React.useEffect(() => {
2815
+ React2.useEffect(() => {
2146
2816
  const controller = new AbortController();
2147
2817
  client.request(query, JSON.parse(key), controller.signal).then((data) => {
2148
2818
  if (!controller.signal.aborted)
@@ -2164,10 +2834,10 @@ function useQuery(client, query, variables, onError, retainDuringRefresh = false
2164
2834
  };
2165
2835
  }
2166
2836
  function useAction(onError) {
2167
- const [pending, setPending] = React.useState(false);
2168
- const [error, setError] = React.useState();
2169
- const controller = React.useRef(null);
2170
- React.useEffect(() => () => controller.current?.abort(), []);
2837
+ const [pending, setPending] = React2.useState(false);
2838
+ const [error, setError] = React2.useState();
2839
+ const controller = React2.useRef(null);
2840
+ React2.useEffect(() => () => controller.current?.abort(), []);
2171
2841
  return {
2172
2842
  pending,
2173
2843
  error,
@@ -2201,12 +2871,12 @@ function Failure({
2201
2871
  reconnect
2202
2872
  }) {
2203
2873
  const action = error.code === "unauthorized" ? reconnect ?? retry : retry;
2204
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "alert", className: "nzp-error", children: [
2874
+ return /* @__PURE__ */ jsxRuntime.jsxs(Part, { role: "alert", className: "nzp-error", part: "billingPortal__error", children: [
2205
2875
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: error.message }),
2206
2876
  action && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: action, children: error.code === "unauthorized" ? "Reconnect" : "Try again" })
2207
2877
  ] });
2208
2878
  }
2209
- function money(cents, currency, locale) {
2879
+ function money2(cents, currency, locale) {
2210
2880
  const formatter = new Intl.NumberFormat(locale, {
2211
2881
  style: "currency",
2212
2882
  currency
@@ -2233,20 +2903,20 @@ var intervals = {
2233
2903
  };
2234
2904
  function Portal(props) {
2235
2905
  const { client, locale, reconnect, onError } = props;
2236
- const { data, error, retry } = useQuery(
2906
+ const { data, error, retry } = useQuery4(
2237
2907
  client,
2238
2908
  identityQuery,
2239
2909
  {},
2240
2910
  onError
2241
2911
  );
2242
- const [view, setView] = React.useState({ kind: "overview" });
2243
- const [notice, setNotice] = React.useState("");
2912
+ const [view, setView] = React2.useState({ kind: "overview" });
2913
+ const [notice, setNotice] = React2.useState("");
2244
2914
  const back = () => setView({ kind: "overview" });
2245
2915
  if (error)
2246
2916
  return /* @__PURE__ */ jsxRuntime.jsx(Failure, { error, retry, reconnect });
2247
2917
  if (!data) return /* @__PURE__ */ jsxRuntime.jsx(Loading, {});
2248
2918
  const customer = data.customerPortalUser;
2249
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2919
+ return /* @__PURE__ */ jsxRuntime.jsxs(NozleBranding, { show: data.customerPortalOrganization.nozleBranding === true, children: [
2250
2920
  /* @__PURE__ */ jsxRuntime.jsxs("header", { children: [
2251
2921
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nzp-row", children: [
2252
2922
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "nzp-muted", children: data.customerPortalOrganization.name }),
@@ -2255,7 +2925,7 @@ function Portal(props) {
2255
2925
  /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Billing portal" }),
2256
2926
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "nzp-muted", children: customer.name || [customer.firstname, customer.lastname].filter(Boolean).join(" ") || customer.email || "Your account" })
2257
2927
  ] }),
2258
- notice && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", children: notice }),
2928
+ notice && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "nzp-notice", role: "status", children: notice }),
2259
2929
  view.kind !== "overview" && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: back, children: "\u2190 Back to overview" }),
2260
2930
  view.kind === "overview" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2261
2931
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -2273,7 +2943,7 @@ function Portal(props) {
2273
2943
  onWallet: (wallet) => setView({ kind: "wallet", wallet })
2274
2944
  }
2275
2945
  ),
2276
- /* @__PURE__ */ jsxRuntime.jsxs("section", { "aria-label": "Billing information", children: [
2946
+ /* @__PURE__ */ jsxRuntime.jsxs(Part, { as: "section", part: "billingPortal__section", "aria-label": "Billing information", children: [
2277
2947
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nzp-row", children: [
2278
2948
  /* @__PURE__ */ jsxRuntime.jsx("h3", { children: "Billing information" }),
2279
2949
  /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => setView({ kind: "edit" }), children: "Edit billing information" })
@@ -2343,8 +3013,7 @@ function Portal(props) {
2343
3013
  back();
2344
3014
  }
2345
3015
  }
2346
- ),
2347
- /* @__PURE__ */ jsxRuntime.jsx("footer", { children: "Powered by Nozle" })
3016
+ )
2348
3017
  ] });
2349
3018
  }
2350
3019
  function Pager({
@@ -2391,17 +3060,20 @@ function Subscriptions({
2391
3060
  planChangeActions,
2392
3061
  nonce
2393
3062
  }) {
2394
- const [page, setPage] = React.useState(1);
2395
- const [updated, setUpdated] = React.useState({});
2396
- const { data, error, retry } = useQuery(client, subscriptionsQuery, { page }, onError, true);
3063
+ const portalAppearance = React2.useContext(PortalAppearance).appearance;
3064
+ const [page, setPage] = React2.useState(1);
3065
+ const [updated, setUpdated] = React2.useState({});
3066
+ const { data, error, retry } = useQuery4(client, subscriptionsQuery, { page }, onError, true);
2397
3067
  const result = data?.customerPortalSubscriptions;
2398
- React.useEffect(() => setUpdated({}), [result]);
2399
- return /* @__PURE__ */ jsxRuntime.jsxs("section", { "aria-label": "Subscriptions", children: [
3068
+ React2.useEffect(() => setUpdated({}), [result]);
3069
+ return /* @__PURE__ */ jsxRuntime.jsxs(Part, { as: "section", part: "billingPortal__section", "aria-label": "Subscriptions", children: [
2400
3070
  /* @__PURE__ */ jsxRuntime.jsx("h3", { children: "Subscriptions" }),
2401
3071
  error ? /* @__PURE__ */ jsxRuntime.jsx(Failure, { error, retry, reconnect }) : !result ? /* @__PURE__ */ jsxRuntime.jsx(Loading, {}) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2402
3072
  result.collection.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "nzp-empty", children: "No active subscriptions." }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nzp-stack", children: result.collection.map((item) => updated[item.id] ?? item).map((subscription) => /* @__PURE__ */ jsxRuntime.jsxs(
2403
- "article",
3073
+ Part,
2404
3074
  {
3075
+ as: "article",
3076
+ part: "billingPortal__card",
2405
3077
  className: "nzp-card",
2406
3078
  "data-subscription-id": subscription.externalId,
2407
3079
  children: [
@@ -2410,7 +3082,7 @@ function Subscriptions({
2410
3082
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "nzp-badge", children: subscription.endingAt ? "Scheduled to end" : "Active" })
2411
3083
  ] }),
2412
3084
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nzp-price", children: [
2413
- money(
3085
+ money2(
2414
3086
  subscription.plan.amountCents,
2415
3087
  subscription.plan.amountCurrency,
2416
3088
  locale
@@ -2422,25 +3094,15 @@ function Subscriptions({
2422
3094
  intervals[subscription.plan.interval] || subscription.plan.interval
2423
3095
  ] })
2424
3096
  ] }),
2425
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nzp-row", children: [
2426
- /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "nzp-muted", children: [
2427
- "Base price, excluding tax and usage",
2428
- /* @__PURE__ */ jsxRuntime.jsx("br", {}),
2429
- "Current period ends",
2430
- " ",
2431
- date(
2432
- subscription.currentBillingPeriodEndingAt,
2433
- locale,
2434
- timezone
2435
- )
2436
- ] }),
2437
- /* @__PURE__ */ jsxRuntime.jsx(
2438
- "button",
2439
- {
2440
- type: "button",
2441
- onClick: () => onUsage(subscription),
2442
- children: "View usage"
2443
- }
3097
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "nzp-muted", children: [
3098
+ "Base price, excluding tax and usage",
3099
+ /* @__PURE__ */ jsxRuntime.jsx("br", {}),
3100
+ "Current period ends",
3101
+ " ",
3102
+ date(
3103
+ subscription.currentBillingPeriodEndingAt,
3104
+ locale,
3105
+ timezone
2444
3106
  )
2445
3107
  ] }),
2446
3108
  subscription.endingAt && /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
@@ -2453,48 +3115,60 @@ function Subscriptions({
2453
3115
  " ",
2454
3116
  subscription.nextSubscription.plan.name
2455
3117
  ] }),
2456
- cancellationActions && subscription.externalId && /* @__PURE__ */ jsxRuntime.jsx(
2457
- CancellationControl,
2458
- {
2459
- subscription: cancellationState(subscription),
2460
- actions: cancellationActions,
2461
- locale,
2462
- timezone,
2463
- nonce,
2464
- onError,
2465
- onChanged: retry,
2466
- refresh: async (signal) => {
2467
- const result2 = await client.request(
2468
- subscriptionQuery,
2469
- { id: subscription.id },
2470
- signal
2471
- );
2472
- if (!signal.aborted && result2.customerPortalSubscription) {
2473
- const fresh = result2.customerPortalSubscription;
2474
- setUpdated((previous) => ({
2475
- ...previous,
2476
- [fresh.id]: fresh
2477
- }));
3118
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nzp-card-actions", children: [
3119
+ /* @__PURE__ */ jsxRuntime.jsx(
3120
+ "button",
3121
+ {
3122
+ type: "button",
3123
+ onClick: () => onUsage(subscription),
3124
+ children: "View usage"
3125
+ }
3126
+ ),
3127
+ planChangeActions && subscription.externalId && /* @__PURE__ */ jsxRuntime.jsx(
3128
+ PlanChangeControl,
3129
+ {
3130
+ subscriptionId: subscription.externalId,
3131
+ refreshKey: `${subscription.id}:${subscription.endingAt ?? ""}:${subscription.nextSubscription?.id ?? ""}:${subscription.plan.code}`,
3132
+ actions: planChangeActions,
3133
+ appearance: portalAppearance,
3134
+ locale,
3135
+ timezone,
3136
+ nonce,
3137
+ onError,
3138
+ onChanged: retry
3139
+ }
3140
+ ),
3141
+ cancellationActions && subscription.externalId && /* @__PURE__ */ jsxRuntime.jsx(
3142
+ CancellationControl,
3143
+ {
3144
+ subscription: cancellationState(subscription),
3145
+ actions: cancellationActions,
3146
+ appearance: portalAppearance,
3147
+ locale,
3148
+ timezone,
3149
+ nonce,
3150
+ onError,
3151
+ onChanged: retry,
3152
+ refresh: async (signal) => {
3153
+ const result2 = await client.request(
3154
+ subscriptionQuery,
3155
+ { id: subscription.id },
3156
+ signal
3157
+ );
3158
+ if (!signal.aborted && result2.customerPortalSubscription) {
3159
+ const fresh = result2.customerPortalSubscription;
3160
+ setUpdated((previous) => ({
3161
+ ...previous,
3162
+ [fresh.id]: fresh
3163
+ }));
3164
+ }
3165
+ return result2.customerPortalSubscription ? cancellationState(
3166
+ result2.customerPortalSubscription
3167
+ ) : null;
2478
3168
  }
2479
- return result2.customerPortalSubscription ? cancellationState(
2480
- result2.customerPortalSubscription
2481
- ) : null;
2482
3169
  }
2483
- }
2484
- ),
2485
- planChangeActions && subscription.externalId && /* @__PURE__ */ jsxRuntime.jsx(
2486
- PlanChangeControl,
2487
- {
2488
- subscriptionId: subscription.externalId,
2489
- refreshKey: `${subscription.id}:${subscription.endingAt ?? ""}:${subscription.nextSubscription?.id ?? ""}:${subscription.plan.code}`,
2490
- actions: planChangeActions,
2491
- locale,
2492
- timezone,
2493
- nonce,
2494
- onError,
2495
- onChanged: retry
2496
- }
2497
- )
3170
+ )
3171
+ ] })
2498
3172
  ]
2499
3173
  },
2500
3174
  subscription.externalId || subscription.id
@@ -2528,9 +3202,9 @@ function UsageDetails({
2528
3202
  subscription,
2529
3203
  timezone
2530
3204
  }) {
2531
- const { data, error, retry } = useQuery(client, usageQuery, { subscriptionId: subscription.id }, onError);
3205
+ const { data, error, retry } = useQuery4(client, usageQuery, { subscriptionId: subscription.id }, onError);
2532
3206
  const usage = data?.customerPortalCustomerUsage;
2533
- return /* @__PURE__ */ jsxRuntime.jsxs("section", { "aria-label": "Subscription usage", children: [
3207
+ return /* @__PURE__ */ jsxRuntime.jsxs(Part, { as: "section", part: "billingPortal__section", "aria-label": "Subscription usage", children: [
2534
3208
  /* @__PURE__ */ jsxRuntime.jsxs("h3", { children: [
2535
3209
  planName(subscription),
2536
3210
  " \xB7 Usage"
@@ -2542,18 +3216,18 @@ function UsageDetails({
2542
3216
  " ",
2543
3217
  date(usage.toDatetime, locale, timezone)
2544
3218
  ] }),
2545
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "nzp-price", children: money(usage.amountCents, usage.currency, locale) }),
3219
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "nzp-price", children: money2(usage.amountCents, usage.currency, locale) }),
2546
3220
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "nzp-muted", children: "Current usage, excluding tax" }),
2547
- usage.chargesUsage.length ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nzp-table", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { children: [
3221
+ usage.chargesUsage.length ? /* @__PURE__ */ jsxRuntime.jsx(Part, { className: "nzp-table", part: "billingPortal__table", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { children: [
2548
3222
  /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
2549
3223
  /* @__PURE__ */ jsxRuntime.jsx("th", { children: "Feature" }),
2550
- /* @__PURE__ */ jsxRuntime.jsx("th", { children: "Units" }),
2551
- /* @__PURE__ */ jsxRuntime.jsx("th", { children: "Amount" })
3224
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "nzp-num", children: "Units" }),
3225
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "nzp-num", children: "Amount" })
2552
3226
  ] }) }),
2553
3227
  /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: usage.chargesUsage.map((charge, index) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
2554
3228
  /* @__PURE__ */ jsxRuntime.jsx("td", { children: charge.charge?.invoiceDisplayName || charge.feature?.name || charge.feature?.code || "Usage" }),
2555
- /* @__PURE__ */ jsxRuntime.jsx("td", { children: charge.units }),
2556
- /* @__PURE__ */ jsxRuntime.jsx("td", { children: money(charge.amountCents, usage.currency, locale) })
3229
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "nzp-num", children: charge.units }),
3230
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "nzp-num", children: money2(charge.amountCents, usage.currency, locale) })
2557
3231
  ] }, `${charge.id}-${index}`)) })
2558
3232
  ] }) }) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "nzp-empty", children: "No usage recorded for this billing period." })
2559
3233
  ] })
@@ -2561,27 +3235,33 @@ function UsageDetails({
2561
3235
  }
2562
3236
  function Invoices(props) {
2563
3237
  const { client, locale, reconnect, onError } = props;
2564
- const [search, setSearch] = React.useState("");
2565
- const [filter, setFilter] = React.useState({ page: 1, searchTerm: "" });
2566
- const { data, error, retry } = useQuery(client, invoicesQuery, filter, onError);
3238
+ const [search, setSearch] = React2.useState("");
3239
+ const [filter, setFilter] = React2.useState({ page: 1, searchTerm: "" });
3240
+ const { data, error, retry } = useQuery4(client, invoicesQuery, filter, onError);
2567
3241
  const result = data?.customerPortalInvoices;
2568
- return /* @__PURE__ */ jsxRuntime.jsxs("section", { "aria-label": "Invoice history", children: [
3242
+ return /* @__PURE__ */ jsxRuntime.jsxs(Part, { as: "section", part: "billingPortal__section", "aria-label": "Invoice history", children: [
2569
3243
  /* @__PURE__ */ jsxRuntime.jsx("h3", { children: "Invoice history" }),
2570
3244
  /* @__PURE__ */ jsxRuntime.jsxs(
2571
3245
  "form",
2572
3246
  {
2573
3247
  className: "nzp-actions",
3248
+ role: "search",
2574
3249
  onSubmit: (event) => {
2575
3250
  event.preventDefault();
2576
3251
  setFilter({ page: 1, searchTerm: search.trim() });
2577
3252
  },
2578
3253
  children: [
2579
- /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
2580
- "Search invoices",
3254
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "nzp-search", children: [
3255
+ /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.25", strokeLinecap: "round", "aria-hidden": "true", children: [
3256
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "11", cy: "11", r: "7" }),
3257
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m20 20-3.5-3.5" })
3258
+ ] }),
2581
3259
  /* @__PURE__ */ jsxRuntime.jsx(
2582
3260
  "input",
2583
3261
  {
2584
3262
  type: "search",
3263
+ "aria-label": "Search invoices",
3264
+ placeholder: "Search invoices",
2585
3265
  value: search,
2586
3266
  onChange: (event) => setSearch(event.target.value)
2587
3267
  }
@@ -2592,18 +3272,18 @@ function Invoices(props) {
2592
3272
  }
2593
3273
  ),
2594
3274
  error ? /* @__PURE__ */ jsxRuntime.jsx(Failure, { error, retry, reconnect }) : !result ? /* @__PURE__ */ jsxRuntime.jsx(Loading, {}) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2595
- !result.collection.length ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "nzp-empty", children: filter.searchTerm ? "No invoices match your search." : "No invoices yet." }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nzp-table", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { children: [
3275
+ !result.collection.length ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "nzp-empty", children: filter.searchTerm ? "No invoices match your search." : "No invoices yet." }) : /* @__PURE__ */ jsxRuntime.jsx(Part, { className: "nzp-table", part: "billingPortal__table", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { children: [
2596
3276
  /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
2597
3277
  /* @__PURE__ */ jsxRuntime.jsx("th", { children: "Invoice" }),
2598
3278
  /* @__PURE__ */ jsxRuntime.jsx("th", { children: "Date" }),
2599
- /* @__PURE__ */ jsxRuntime.jsx("th", { children: "Amount" }),
3279
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "nzp-num", children: "Amount" }),
2600
3280
  /* @__PURE__ */ jsxRuntime.jsx("th", { children: "Status" }),
2601
3281
  /* @__PURE__ */ jsxRuntime.jsx("th", { children: "PDF" })
2602
3282
  ] }) }),
2603
3283
  /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: result.collection.map((invoice) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
2604
3284
  /* @__PURE__ */ jsxRuntime.jsx("td", { children: invoice.number }),
2605
3285
  /* @__PURE__ */ jsxRuntime.jsx("td", { children: date(invoice.issuingDate, locale) }),
2606
- /* @__PURE__ */ jsxRuntime.jsx("td", { children: money(
3286
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "nzp-num", children: money2(
2607
3287
  invoice.totalAmountCents,
2608
3288
  invoice.currency,
2609
3289
  locale
@@ -2629,8 +3309,8 @@ function InvoiceDownload({
2629
3309
  onError,
2630
3310
  reconnect
2631
3311
  }) {
2632
- const [url, setUrl] = React.useState("");
2633
- const [generating, setGenerating] = React.useState(false);
3312
+ const [url, setUrl] = React2.useState("");
3313
+ const [generating, setGenerating] = React2.useState(false);
2634
3314
  const action = useAction(onError);
2635
3315
  const download = () => action.run(async (signal) => {
2636
3316
  const data = await client.request(downloadInvoiceMutation, { input: { id: invoice.id } }, signal);
@@ -2688,7 +3368,7 @@ function CustomerForm({
2688
3368
  onSaved,
2689
3369
  onCancel
2690
3370
  }) {
2691
- const [values, setValues] = React.useState(
3371
+ const [values, setValues] = React2.useState(
2692
3372
  () => Object.fromEntries(
2693
3373
  customerFields.map(([key]) => [key, customer[key] ?? ""])
2694
3374
  )
@@ -2707,7 +3387,7 @@ function CustomerForm({
2707
3387
  if (!signal.aborted) onSaved();
2708
3388
  });
2709
3389
  };
2710
- return /* @__PURE__ */ jsxRuntime.jsxs("section", { "aria-label": "Edit billing information", children: [
3390
+ return /* @__PURE__ */ jsxRuntime.jsxs(Part, { as: "section", part: "billingPortal__section", "aria-label": "Edit billing information", children: [
2711
3391
  /* @__PURE__ */ jsxRuntime.jsx("h3", { children: "Edit billing information" }),
2712
3392
  /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: submit, children: [
2713
3393
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nzp-fields", children: customerFields.map(([key, label]) => /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
@@ -2748,16 +3428,16 @@ function Wallets({
2748
3428
  onError,
2749
3429
  onWallet
2750
3430
  }) {
2751
- const [page, setPage] = React.useState(1);
2752
- const { data, error, retry } = useQuery(client, walletsQuery, { page }, onError);
3431
+ const [page, setPage] = React2.useState(1);
3432
+ const { data, error, retry } = useQuery4(client, walletsQuery, { page }, onError);
2753
3433
  const result = data?.customerPortalWallets;
2754
3434
  if (result && !result.collection.length) return null;
2755
- return /* @__PURE__ */ jsxRuntime.jsxs("section", { "aria-label": "Wallets", children: [
3435
+ return /* @__PURE__ */ jsxRuntime.jsxs(Part, { as: "section", part: "billingPortal__section", "aria-label": "Wallets", children: [
2756
3436
  /* @__PURE__ */ jsxRuntime.jsx("h3", { children: "Wallets" }),
2757
3437
  error ? /* @__PURE__ */ jsxRuntime.jsx(Failure, { error, retry, reconnect }) : !result ? /* @__PURE__ */ jsxRuntime.jsx(Loading, {}) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2758
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nzp-stack", children: result.collection.map((wallet) => /* @__PURE__ */ jsxRuntime.jsxs("article", { className: "nzp-card", children: [
3438
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nzp-stack", children: result.collection.map((wallet) => /* @__PURE__ */ jsxRuntime.jsxs(Part, { as: "article", part: "billingPortal__card", className: "nzp-card", children: [
2759
3439
  /* @__PURE__ */ jsxRuntime.jsx("h3", { children: wallet.name || "Prepaid credits" }),
2760
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nzp-price", children: money(wallet.balanceCents, wallet.currency, locale) }),
3440
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nzp-price", children: money2(wallet.balanceCents, wallet.currency, locale) }),
2761
3441
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nzp-row", children: [
2762
3442
  /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "nzp-muted", children: [
2763
3443
  wallet.creditsBalance,
@@ -2788,7 +3468,7 @@ function WalletTopUp({
2788
3468
  onError,
2789
3469
  onCreated
2790
3470
  }) {
2791
- const [credits, setCredits] = React.useState("");
3471
+ const [credits, setCredits] = React2.useState("");
2792
3472
  const action = useAction(onError);
2793
3473
  const digits = new Intl.NumberFormat(locale, {
2794
3474
  style: "currency",
@@ -2802,7 +3482,7 @@ function WalletTopUp({
2802
3482
  amountCents,
2803
3483
  String(wallet.paidTopUpMaxAmountCents)
2804
3484
  ) <= 0);
2805
- return /* @__PURE__ */ jsxRuntime.jsxs("section", { "aria-label": "Add credits", children: [
3485
+ return /* @__PURE__ */ jsxRuntime.jsxs(Part, { as: "section", part: "billingPortal__section", "aria-label": "Add credits", children: [
2806
3486
  /* @__PURE__ */ jsxRuntime.jsxs("h3", { children: [
2807
3487
  "Add credits \xB7 ",
2808
3488
  wallet.name || "Wallet"
@@ -2839,19 +3519,19 @@ function WalletTopUp({
2839
3519
  )
2840
3520
  ] }) }),
2841
3521
  /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
2842
- amountCents !== null ? money(amountCents, wallet.currency, locale) : "\u2014",
3522
+ amountCents !== null ? money2(amountCents, wallet.currency, locale) : "\u2014",
2843
3523
  " ",
2844
3524
  "excluding tax"
2845
3525
  ] }),
2846
3526
  wallet.paidTopUpMinAmountCents != null && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "nzp-muted", children: [
2847
3527
  "Minimum",
2848
3528
  " ",
2849
- money(wallet.paidTopUpMinAmountCents, wallet.currency, locale)
3529
+ money2(wallet.paidTopUpMinAmountCents, wallet.currency, locale)
2850
3530
  ] }),
2851
3531
  wallet.paidTopUpMaxAmountCents != null && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "nzp-muted", children: [
2852
3532
  "Maximum",
2853
3533
  " ",
2854
- money(wallet.paidTopUpMaxAmountCents, wallet.currency, locale)
3534
+ money2(wallet.paidTopUpMaxAmountCents, wallet.currency, locale)
2855
3535
  ] }),
2856
3536
  action.error && /* @__PURE__ */ jsxRuntime.jsx(Failure, { error: action.error, reconnect }),
2857
3537
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nzp-actions", children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -2868,45 +3548,119 @@ function WalletTopUp({
2868
3548
  )
2869
3549
  ] });
2870
3550
  }
2871
- function usePlans() {
2872
- const { client } = useBillingContext();
2873
- const [plans, setPlans] = React.useState([]);
2874
- const [isLoading, setIsLoading] = React.useState(true);
2875
- const [error, setError] = React.useState(null);
2876
- React.useEffect(() => {
2877
- let cancelled = false;
2878
- setIsLoading(true);
2879
- setError(null);
2880
- setPlans([]);
2881
- if (!client) {
2882
- setIsLoading(false);
2883
- return;
2884
- }
2885
- void client.catalogFetch("/api/v1/plans").then(async (response) => {
2886
- if (!response.ok) throw new Error(`HTTP ${response.status}`);
2887
- const data = await response.json();
2888
- if (!cancelled) setPlans(data.plans ?? []);
2889
- }).catch((cause) => {
2890
- if (!cancelled) {
2891
- setError(
2892
- cause instanceof Error ? cause : new Error("Failed to load plans")
2893
- );
3551
+ function useBilling() {
3552
+ const { billingFetch, billingEndpoint, queryClient } = useBillingContext();
3553
+ if (!billingFetch || !billingEndpoint) {
3554
+ throw new Error("Billing hooks need BillingProvider billingEndpoint, a backend route served by createBillingHandler");
3555
+ }
3556
+ return { fetchRoute: billingFetch, queryClient, endpoint: billingEndpoint };
3557
+ }
3558
+ function useSubscription() {
3559
+ const { fetchRoute, queryClient, endpoint } = useBilling();
3560
+ return reactQuery.useQuery(
3561
+ { queryKey: ["nozle", endpoint, "subscription"], queryFn: () => fetchRoute("subscription") },
3562
+ queryClient
3563
+ );
3564
+ }
3565
+ function useEntitlement(featureCode, options = {}) {
3566
+ const { fetchRoute, queryClient, endpoint } = useBilling();
3567
+ return reactQuery.useQuery(
3568
+ {
3569
+ queryKey: ["nozle", endpoint, "entitlement", featureCode],
3570
+ queryFn: () => fetchRoute("entitlement", { feature: featureCode }),
3571
+ enabled: Boolean(featureCode) && options.enabled !== false,
3572
+ staleTime: 1e4
3573
+ },
3574
+ queryClient
3575
+ );
3576
+ }
3577
+ function useCreditBalance(creditSystemCode) {
3578
+ const { fetchRoute, queryClient, endpoint } = useBilling();
3579
+ return reactQuery.useQuery(
3580
+ {
3581
+ queryKey: ["nozle", endpoint, "credits"],
3582
+ queryFn: () => fetchRoute("credits"),
3583
+ select: (data) => {
3584
+ const balances = data.balances ?? [];
3585
+ if (creditSystemCode) return balances.find((b) => b.credit_system === creditSystemCode) ?? null;
3586
+ return balances.find((b) => Number(b.available) > 0) ?? balances[0] ?? null;
2894
3587
  }
2895
- }).finally(() => {
2896
- if (!cancelled) setIsLoading(false);
2897
- });
2898
- return () => {
2899
- cancelled = true;
2900
- };
2901
- }, [client]);
2902
- return { plans, isLoading, error };
3588
+ },
3589
+ queryClient
3590
+ );
3591
+ }
3592
+ function useCreditHistory(options = {}) {
3593
+ const { fetchRoute, queryClient, endpoint } = useBilling();
3594
+ const query = reactQuery.useInfiniteQuery(
3595
+ {
3596
+ queryKey: ["nozle", endpoint, "credit-history", options.creditSystemCode, options.limit],
3597
+ queryFn: ({ pageParam }) => fetchRoute("credit-history", {
3598
+ credit_system: options.creditSystemCode,
3599
+ limit: options.limit,
3600
+ cursor: pageParam
3601
+ }),
3602
+ initialPageParam: void 0,
3603
+ getNextPageParam: (page) => page.next_cursor ?? void 0
3604
+ },
3605
+ queryClient
3606
+ );
3607
+ return { ...query, operations: query.data?.pages.flatMap((page) => page.operations) ?? [] };
3608
+ }
3609
+ function useUsage(options = {}) {
3610
+ const { fetchRoute, queryClient, endpoint } = useBilling();
3611
+ const period = options.period ?? "current";
3612
+ return reactQuery.useQuery(
3613
+ {
3614
+ queryKey: ["nozle", endpoint, "usage", period, options.periods],
3615
+ queryFn: () => fetchRoute("usage", {
3616
+ period,
3617
+ periods: options.periods
3618
+ }),
3619
+ select: (data) => period === "past" ? data.periods ?? [] : data.usage ?? null
3620
+ },
3621
+ queryClient
3622
+ );
3623
+ }
3624
+ function useInvoices(options = {}) {
3625
+ const { fetchRoute, queryClient, endpoint } = useBilling();
3626
+ const page = options.page ?? 1;
3627
+ return reactQuery.useQuery(
3628
+ {
3629
+ queryKey: ["nozle", endpoint, "invoices", page],
3630
+ queryFn: () => fetchRoute("invoices", { page })
3631
+ },
3632
+ queryClient
3633
+ );
3634
+ }
3635
+ function useInvoiceDownload() {
3636
+ const { fetchRoute } = useBilling();
3637
+ return React2.useCallback(
3638
+ async (invoiceId) => (await fetchRoute("invoice-download", { id: invoiceId })).url,
3639
+ [fetchRoute]
3640
+ );
3641
+ }
3642
+ function useUpgradePreview(planCode) {
3643
+ const { fetchRoute, queryClient, endpoint } = useBilling();
3644
+ return reactQuery.useQuery(
3645
+ {
3646
+ queryKey: ["nozle", endpoint, "preview", planCode],
3647
+ queryFn: () => fetchRoute("preview", { plan: planCode }),
3648
+ enabled: Boolean(planCode),
3649
+ staleTime: 0
3650
+ },
3651
+ queryClient
3652
+ );
3653
+ }
3654
+ function useBillingRefresh() {
3655
+ const { queryClient } = useBillingContext();
3656
+ return React2.useCallback(() => queryClient.invalidateQueries({ queryKey: ["nozle"] }), [queryClient]);
2903
3657
  }
2904
3658
  function useCheckout2() {
2905
3659
  const { createCheckout } = useBillingContext();
2906
- const [isLoading, setIsLoading] = React.useState(false);
2907
- const [error, setError] = React.useState(null);
2908
- const [checkout, setCheckout] = React.useState(null);
2909
- const fetchClientSecret = React.useCallback(
3660
+ const [isLoading, setIsLoading] = React2.useState(false);
3661
+ const [error, setError] = React2.useState(null);
3662
+ const [checkout, setCheckout] = React2.useState(null);
3663
+ const fetchClientSecret = React2.useCallback(
2910
3664
  async (planCode, returnUrl) => {
2911
3665
  setIsLoading(true);
2912
3666
  setError(null);
@@ -2947,33 +3701,124 @@ function useCheckout2() {
2947
3701
  );
2948
3702
  return { fetchClientSecret, checkout, isLoading, error };
2949
3703
  }
3704
+ function PanelPlanCard({
3705
+ frame,
3706
+ el,
3707
+ name,
3708
+ description,
3709
+ price,
3710
+ interval,
3711
+ features,
3712
+ isCurrent,
3713
+ isHighlighted,
3714
+ filled: filledStyle,
3715
+ highlightLabel,
3716
+ children
3717
+ }) {
3718
+ const filled = isHighlighted && filledStyle;
3719
+ const text2 = filled ? T.highlightForeground : T.foreground;
3720
+ const muted5 = filled ? `color-mix(in srgb, ${T.highlightForeground} 75%, ${T.highlight})` : T.mutedForeground;
3721
+ const edge = isHighlighted ? T.highlight : isCurrent ? T.foreground : T.border;
3722
+ const label = isCurrent ? "Active" : isHighlighted ? highlightLabel : null;
3723
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3724
+ "div",
3725
+ {
3726
+ ...frame({
3727
+ display: "flex",
3728
+ flexDirection: "column",
3729
+ overflow: "hidden",
3730
+ ...text.body,
3731
+ background: filled ? T.highlight : T.card,
3732
+ color: text2,
3733
+ border: border(edge),
3734
+ borderRadius: T.radius,
3735
+ boxShadow: T.shadow
3736
+ }),
3737
+ children: [
3738
+ /* @__PURE__ */ jsxRuntime.jsxs(
3739
+ "div",
3740
+ {
3741
+ ...el("planCard__header", {
3742
+ display: "flex",
3743
+ alignItems: "center",
3744
+ justifyContent: "space-between",
3745
+ gap: "0.75rem",
3746
+ padding: "0.875rem 1.25rem",
3747
+ borderBottom: border(filled ? `color-mix(in srgb, ${T.highlightForeground} 30%, ${T.highlight})` : T.border)
3748
+ }),
3749
+ children: [
3750
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { ...el("planCard__name", { ...text.heading, color: text2, margin: 0 }), children: name }),
3751
+ label && /* @__PURE__ */ jsxRuntime.jsx(
3752
+ "span",
3753
+ {
3754
+ ...el("planCard__badge", {
3755
+ ...text.label,
3756
+ whiteSpace: "nowrap",
3757
+ // The current plan's label takes the colour of its button.
3758
+ color: filled ? text2 : isCurrent ? T.mutedForeground : T.highlight
3759
+ }),
3760
+ children: label
3761
+ }
3762
+ )
3763
+ ]
3764
+ }
3765
+ ),
3766
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "1rem", padding: "1.25rem", flex: 1 }, children: [
3767
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3768
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { ...el("planCard__price", { ...text.metric, color: text2, margin: 0 }), children: [
3769
+ price,
3770
+ interval && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { ...text.meta, color: muted5 }, children: [
3771
+ " / ",
3772
+ interval
3773
+ ] })
3774
+ ] }),
3775
+ description && /* @__PURE__ */ jsxRuntime.jsx("p", { ...el("planCard__description", { ...text.secondary, color: muted5, margin: "0.25rem 0 0" }), children: description })
3776
+ ] }),
3777
+ /* @__PURE__ */ jsxRuntime.jsx(
3778
+ "ul",
3779
+ {
3780
+ ...el("planCard__features", {
3781
+ listStyle: "none",
3782
+ margin: 0,
3783
+ padding: 0,
3784
+ display: "flex",
3785
+ flexDirection: "column",
3786
+ gap: "0.375rem",
3787
+ flex: 1,
3788
+ ...text.secondary,
3789
+ color: muted5
3790
+ }),
3791
+ children: features.map((feature) => /* @__PURE__ */ jsxRuntime.jsx("li", { ...el("planCard__feature"), children: feature }, feature))
3792
+ }
3793
+ ),
3794
+ children
3795
+ ] })
3796
+ ]
3797
+ }
3798
+ );
3799
+ }
3800
+ function featureLines(override, plan, index) {
3801
+ const given = Array.isArray(override) ? override[index] : override?.[plan.code];
3802
+ return given ?? planFeatures(plan);
3803
+ }
2950
3804
  var SYMBOLS = { USD: "$", EUR: "\u20AC", GBP: "\xA3", JPY: "\xA5" };
2951
3805
  function fmtPrice(cents, cur) {
2952
3806
  const s = SYMBOLS[cur] ?? cur + " ";
2953
3807
  const val = cents % 100 === 0 ? String(cents / 100) : (cents / 100).toFixed(2);
2954
3808
  return `${s}${val}`;
2955
3809
  }
2956
- function isEnterprise(p) {
2957
- return /enterprise|custom/i.test(p.name + p.code);
2958
- }
2959
- function v(name, fb) {
2960
- return `var(${name}, ${fb})`;
2961
- }
2962
3810
  var C = {
2963
- bg: v("--nozle-pricing-bg", v("--nozle-background", "#ffffff")),
2964
- cardBg: v("--nozle-pricing-card-bg", v("--nozle-card", "#ffffff")),
2965
- hl: v("--nozle-pricing-highlight", v("--nozle-primary", "#6366f1")),
2966
- border: v("--nozle-pricing-border", v("--nozle-border", "#e5e7eb")),
2967
- radius: v("--nozle-pricing-radius", v("--nozle-radius", "12px")),
2968
- text: v("--nozle-foreground", "#111827"),
2969
- muted: v("--nozle-muted-foreground", "#6b7280"),
2970
- mutedBg: v("--nozle-muted", "#f3f4f6"),
2971
- hlFg: v("--nozle-primary-foreground", "#ffffff")
2972
- };
3811
+ cardBg: T.card,
3812
+ hl: T.primary,
3813
+ border: T.border,
3814
+ radius: T.radius,
3815
+ text: T.foreground,
3816
+ muted: T.mutedForeground,
3817
+ mutedBg: T.muted};
2973
3818
  function Check() {
2974
3819
  return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", style: { flexShrink: 0 }, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3.5 8.5L6.5 11.5L12.5 4.5", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round" }) });
2975
3820
  }
2976
- function Toggle({ isAnnual, onChange }) {
3821
+ function Toggle({ isAnnual, onChange, el }) {
2977
3822
  const pill = {
2978
3823
  display: "inline-flex",
2979
3824
  borderRadius: "9999px",
@@ -2986,24 +3831,15 @@ function Toggle({ isAnnual, onChange }) {
2986
3831
  borderRadius: "9999px",
2987
3832
  border: "none",
2988
3833
  background: active ? C.cardBg : "transparent",
3834
+ ...text.action,
2989
3835
  color: active ? C.text : C.muted,
2990
- fontWeight: active ? 600 : 400,
2991
- fontSize: "0.875rem",
2992
3836
  cursor: "pointer",
2993
- boxShadow: active ? "0 1px 3px rgba(0,0,0,0.08)" : "none",
3837
+ boxShadow: active ? T.shadow : "none",
2994
3838
  transition: "all 0.15s ease"
2995
3839
  });
2996
- return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", justifyContent: "center", marginBottom: "2.5rem" }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: pill, children: [
2997
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => onChange(false), style: tab(!isAnnual), "aria-pressed": !isAnnual, children: "Monthly" }),
2998
- /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: () => onChange(true), style: tab(isAnnual), "aria-pressed": isAnnual, children: [
2999
- "Annual",
3000
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
3001
- marginLeft: "6px",
3002
- fontSize: "0.7rem",
3003
- fontWeight: 600,
3004
- color: isAnnual ? C.hl : C.muted
3005
- }, children: "Save 20%" })
3006
- ] })
3840
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", justifyContent: "center", marginBottom: "2.5rem" }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { ...el("pricingTable__toggle", pill), children: [
3841
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => onChange(false), ...el("pricingTable__toggleButton", tab(!isAnnual)), "aria-pressed": !isAnnual, children: "Monthly" }),
3842
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => onChange(true), ...el("pricingTable__toggleButton", tab(isAnnual)), "aria-pressed": isAnnual, children: "Annual" })
3007
3843
  ] }) });
3008
3844
  }
3009
3845
  function Card({
@@ -3011,13 +3847,22 @@ function Card({
3011
3847
  features,
3012
3848
  isCurrent,
3013
3849
  isHighlighted,
3850
+ filled: filledStyle,
3851
+ highlightLabel,
3014
3852
  isEnt,
3015
3853
  enterpriseEmail,
3016
- onSelect
3854
+ onSelect,
3855
+ el,
3856
+ panel,
3857
+ isUpgrade
3017
3858
  }) {
3018
- const [hover, setHover] = React.useState(false);
3019
- const shadow = isHighlighted ? "0 8px 28px rgba(99,102,241,0.12), 0 2px 8px rgba(99,102,241,0.06)" : hover ? "0 8px 24px rgba(0,0,0,0.06), 0 2px 8px rgba(0,0,0,0.03)" : "0 1px 3px rgba(0,0,0,0.04), 0 1px 2px rgba(0,0,0,0.03)";
3020
- const interval = plan.interval === "yearly" ? "/year" : "/month";
3859
+ const [hover, setHover] = React2.useState(false);
3860
+ const filled = isHighlighted && filledStyle;
3861
+ const text2 = filled ? T.highlightForeground : C.text;
3862
+ const muted5 = filled ? `color-mix(in srgb, ${T.highlightForeground} 75%, ${T.highlight})` : C.muted;
3863
+ const accent = filled ? T.highlightForeground : C.hl;
3864
+ const badgeLabel = isCurrent ? "Current Plan" : highlightLabel;
3865
+ const interval = `/${intervalLabel(plan.interval)}`;
3021
3866
  const handleClick = () => {
3022
3867
  if (isCurrent) return;
3023
3868
  if (isEnt && enterpriseEmail) {
@@ -3026,75 +3871,97 @@ function Card({
3026
3871
  }
3027
3872
  onSelect?.();
3028
3873
  };
3874
+ const emphasised = isUpgrade ?? isHighlighted;
3875
+ const cta = onSelect && /* @__PURE__ */ jsxRuntime.jsx(
3876
+ CTAButton,
3877
+ {
3878
+ onClick: handleClick,
3879
+ disabled: isCurrent,
3880
+ variant: isCurrent ? "current" : isEnt ? "outline" : !emphasised ? "secondary" : filled ? "inverse" : isHighlighted ? "highlight" : "primary",
3881
+ el,
3882
+ children: isCurrent ? "Current Plan" : isEnt ? "Contact Sales" : `Get ${plan.name}`
3883
+ }
3884
+ );
3885
+ if (panel) {
3886
+ return /* @__PURE__ */ jsxRuntime.jsx(
3887
+ PanelPlanCard,
3888
+ {
3889
+ frame: (style) => el("planCard", style),
3890
+ el,
3891
+ name: plan.name,
3892
+ description: plan.description,
3893
+ price: isEnt ? "Custom" : fmtPrice(plan.amount_cents, plan.amount_currency),
3894
+ interval: isEnt ? void 0 : intervalLabel(plan.interval),
3895
+ features: features ?? [],
3896
+ isCurrent,
3897
+ isHighlighted,
3898
+ filled: filledStyle,
3899
+ highlightLabel,
3900
+ children: cta
3901
+ }
3902
+ );
3903
+ }
3029
3904
  return /* @__PURE__ */ jsxRuntime.jsxs(
3030
3905
  "div",
3031
3906
  {
3032
3907
  onMouseEnter: () => setHover(true),
3033
3908
  onMouseLeave: () => setHover(false),
3034
- style: {
3909
+ ...el("planCard", {
3035
3910
  position: "relative",
3036
3911
  display: "flex",
3037
3912
  flexDirection: "column",
3038
- background: C.cardBg,
3039
- border: `1px solid ${isHighlighted ? C.hl : C.border}`,
3040
- borderTopWidth: isHighlighted ? "3px" : "1px",
3913
+ background: filled ? T.highlight : C.cardBg,
3914
+ color: text2,
3915
+ border: border(isHighlighted ? T.highlight : C.border),
3916
+ borderTopWidth: isHighlighted && !filled ? `calc(${T.borderWidth} + 2px)` : T.borderWidth,
3041
3917
  borderRadius: C.radius,
3042
3918
  padding: "2rem",
3043
3919
  transition: "box-shadow 0.2s ease, transform 0.2s ease",
3044
- boxShadow: shadow,
3920
+ boxShadow: T.shadow,
3045
3921
  transform: hover && !isCurrent ? "translateY(-2px)" : "none"
3046
- },
3922
+ }),
3047
3923
  children: [
3048
- isHighlighted && /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
3924
+ isHighlighted && badgeLabel && /* @__PURE__ */ jsxRuntime.jsx("div", { ...el("planCard__badge", {
3049
3925
  position: "absolute",
3050
3926
  top: "-1px",
3051
3927
  left: "50%",
3052
3928
  transform: "translate(-50%, -50%)",
3053
- background: C.hl,
3054
- color: C.hlFg,
3055
- fontSize: "0.7rem",
3056
- fontWeight: 700,
3929
+ background: filled ? T.highlightForeground : T.highlight,
3930
+ border: filled ? border(T.highlight) : void 0,
3931
+ ...text.label,
3932
+ color: filled ? T.highlight : T.highlightForeground,
3057
3933
  padding: "4px 12px",
3058
3934
  borderRadius: "9999px",
3059
- letterSpacing: "0.025em",
3060
- textTransform: "uppercase",
3061
3935
  whiteSpace: "nowrap"
3062
- }, children: isCurrent ? "Current Plan" : "Most Popular" }),
3063
- isCurrent && !isHighlighted && /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
3936
+ }), children: badgeLabel }),
3937
+ isCurrent && !isHighlighted && /* @__PURE__ */ jsxRuntime.jsx("div", { ...el("planCard__badge", {
3064
3938
  position: "absolute",
3065
3939
  top: "0",
3066
3940
  left: "50%",
3067
3941
  transform: "translate(-50%, -50%)",
3068
3942
  background: C.mutedBg,
3069
- color: C.muted,
3070
- fontSize: "0.7rem",
3071
- fontWeight: 700,
3943
+ ...text.label,
3072
3944
  padding: "4px 12px",
3073
3945
  borderRadius: "9999px",
3074
- letterSpacing: "0.025em",
3075
- textTransform: "uppercase",
3076
3946
  whiteSpace: "nowrap"
3077
- }, children: "Current Plan" }),
3078
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: "1.25rem", marginTop: isHighlighted || isCurrent ? "0.5rem" : 0 }, children: [
3079
- /* @__PURE__ */ jsxRuntime.jsx("h3", { style: {
3080
- fontSize: "1.25rem",
3081
- fontWeight: 700,
3082
- color: C.text,
3083
- margin: 0,
3084
- lineHeight: 1.3
3085
- }, children: plan.name }),
3086
- plan.description && /* @__PURE__ */ jsxRuntime.jsx("p", { style: {
3087
- fontSize: "0.875rem",
3088
- color: C.muted,
3089
- margin: "6px 0 0",
3090
- lineHeight: 1.5
3091
- }, children: plan.description })
3947
+ }), children: "Current Plan" }),
3948
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: "1.25rem", marginTop: isHighlighted && badgeLabel || isCurrent ? "0.5rem" : 0 }, children: [
3949
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { ...el("planCard__name", {
3950
+ ...text.heading,
3951
+ color: text2,
3952
+ margin: 0
3953
+ }), children: plan.name }),
3954
+ plan.description && /* @__PURE__ */ jsxRuntime.jsx("p", { ...el("planCard__description", {
3955
+ ...text.secondary,
3956
+ color: muted5,
3957
+ margin: "6px 0 0"
3958
+ }), children: plan.description })
3092
3959
  ] }),
3093
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { marginBottom: "1.5rem" }, children: isEnt ? /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "2.25rem", fontWeight: 700, color: C.text, lineHeight: 1 }, children: "Custom" }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "baseline", gap: "2px" }, children: [
3094
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "2.75rem", fontWeight: 700, color: C.text, lineHeight: 1, letterSpacing: "-0.025em" }, children: fmtPrice(plan.amount_cents, plan.amount_currency) }),
3095
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "0.9375rem", color: C.muted, marginLeft: "4px" }, children: interval })
3960
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ...el("planCard__price", { marginBottom: "1.5rem" }), children: isEnt ? /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...text.metric, color: text2 }, children: "Custom" }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "baseline", gap: "2px" }, children: [
3961
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...text.metric, color: text2 }, children: fmtPrice(plan.amount_cents, plan.amount_currency) }),
3962
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...text.meta, color: muted5, marginLeft: "4px" }, children: interval })
3096
3963
  ] }) }),
3097
- features && features.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("ul", { style: {
3964
+ features && features.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("ul", { ...el("planCard__features", {
3098
3965
  listStyle: "none",
3099
3966
  padding: 0,
3100
3967
  margin: "0 0 2rem",
@@ -3102,27 +3969,18 @@ function Card({
3102
3969
  flexDirection: "column",
3103
3970
  gap: "0.75rem",
3104
3971
  flex: 1
3105
- }, children: features.map((feat) => /* @__PURE__ */ jsxRuntime.jsxs("li", { style: {
3972
+ }), children: features.map((feat) => /* @__PURE__ */ jsxRuntime.jsxs("li", { ...el("planCard__feature", {
3106
3973
  display: "flex",
3107
3974
  alignItems: "flex-start",
3108
3975
  gap: "10px",
3109
- fontSize: "0.875rem",
3110
- color: C.text,
3111
- lineHeight: 1.5
3112
- }, children: [
3113
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: C.hl, marginTop: "2px" }, children: /* @__PURE__ */ jsxRuntime.jsx(Check, {}) }),
3976
+ ...text.body,
3977
+ color: text2
3978
+ }), children: [
3979
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: accent, marginTop: "2px" }, children: /* @__PURE__ */ jsxRuntime.jsx(Check, {}) }),
3114
3980
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: feat })
3115
3981
  ] }, feat)) }),
3116
3982
  (!features || features.length === 0) && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1 } }),
3117
- onSelect && /* @__PURE__ */ jsxRuntime.jsx(
3118
- CTAButton,
3119
- {
3120
- onClick: handleClick,
3121
- disabled: isCurrent,
3122
- variant: isCurrent ? "current" : isEnt ? "outline" : isHighlighted ? "primary" : "secondary",
3123
- children: isCurrent ? "Current Plan" : isEnt ? "Contact Sales" : `Get ${plan.name}`
3124
- }
3125
- )
3983
+ cta
3126
3984
  ]
3127
3985
  }
3128
3986
  );
@@ -3131,75 +3989,53 @@ function CTAButton({
3131
3989
  children,
3132
3990
  onClick,
3133
3991
  disabled,
3134
- variant
3992
+ variant,
3993
+ el
3135
3994
  }) {
3136
- const [hover, setHover] = React.useState(false);
3137
- const base = {
3138
- width: "100%",
3139
- padding: "12px 24px",
3140
- borderRadius: "10px",
3141
- fontSize: "0.9375rem",
3142
- fontWeight: 600,
3143
- cursor: disabled ? "default" : "pointer",
3144
- border: "none",
3145
- transition: "all 0.15s ease",
3146
- lineHeight: 1.4
3147
- };
3148
- const variants = {
3149
- primary: {
3150
- background: hover && !disabled ? "#4f46e5" : C.hl,
3151
- color: C.hlFg
3152
- },
3153
- secondary: {
3154
- background: hover && !disabled ? "#f0f0f3" : C.mutedBg,
3155
- color: C.text,
3156
- border: `1px solid ${C.border}`
3157
- },
3158
- outline: {
3159
- background: hover && !disabled ? "rgba(99,102,241,0.04)" : "transparent",
3160
- color: C.hl,
3161
- border: `1.5px solid ${C.hl}`
3162
- },
3163
- current: {
3164
- background: C.mutedBg,
3165
- color: C.muted
3166
- }
3167
- };
3995
+ const [hover, setHover] = React2.useState(false);
3996
+ 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);
3168
3997
  return /* @__PURE__ */ jsxRuntime.jsx(
3169
3998
  "button",
3170
3999
  {
4000
+ type: "button",
3171
4001
  onClick: disabled ? void 0 : onClick,
3172
4002
  disabled,
3173
4003
  onMouseEnter: () => setHover(true),
3174
4004
  onMouseLeave: () => setHover(false),
3175
- style: { ...base, ...variants[variant] },
4005
+ ...el("planCard__cta", {
4006
+ ...look,
4007
+ width: "100%",
4008
+ filter: hover && !disabled ? "brightness(0.95)" : void 0
4009
+ }),
3176
4010
  children
3177
4011
  }
3178
4012
  );
3179
4013
  }
3180
- function Skeleton() {
3181
- return /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
4014
+ function Skeleton({ rootProps }) {
4015
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ...rootProps, "aria-busy": "true", style: {
4016
+ ...rootProps.style,
3182
4017
  display: "grid",
3183
4018
  gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
3184
4019
  gap: "1.5rem"
3185
4020
  }, children: [0, 1, 2].map((i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
3186
4021
  background: C.cardBg,
3187
- border: `1px solid ${C.border}`,
4022
+ border: border(),
3188
4023
  borderRadius: C.radius,
3189
4024
  padding: "2rem",
3190
4025
  height: "380px"
3191
4026
  }, children: [
3192
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 20, width: "40%", borderRadius: 6, background: "#f0f0f0", marginBottom: 12 } }),
3193
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 40, width: "50%", borderRadius: 6, background: "#f0f0f0", marginBottom: 24 } }),
3194
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 14, width: "80%", borderRadius: 6, background: "#f0f0f0", marginBottom: 10 } }),
3195
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 14, width: "70%", borderRadius: 6, background: "#f0f0f0", marginBottom: 10 } }),
3196
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 14, width: "60%", borderRadius: 6, background: "#f0f0f0" } })
4027
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 20, width: "40%", borderRadius: 6, background: T.skeleton, marginBottom: 12 } }),
4028
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 40, width: "50%", borderRadius: 6, background: T.skeleton, marginBottom: 24 } }),
4029
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 14, width: "80%", borderRadius: 6, background: T.skeleton, marginBottom: 10 } }),
4030
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 14, width: "70%", borderRadius: 6, background: T.skeleton, marginBottom: 10 } }),
4031
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 14, width: "60%", borderRadius: 6, background: T.skeleton } })
3197
4032
  ] }, i)) });
3198
4033
  }
3199
4034
  function PricingTable({
3200
4035
  currentPlanCode,
3201
4036
  plans: propPlans,
3202
4037
  features,
4038
+ sort,
3203
4039
  onSelect,
3204
4040
  returnUrl,
3205
4041
  onStripeClientSecret,
@@ -3207,41 +4043,29 @@ function PricingTable({
3207
4043
  onCheckoutError,
3208
4044
  showToggle = true,
3209
4045
  highlightPlan,
4046
+ highlightStyle = "outline",
4047
+ highlightLabel = "Most Popular",
3210
4048
  className,
3211
- enterpriseEmail
4049
+ contactSalesPlans,
4050
+ enterpriseEmail,
4051
+ appearance
3212
4052
  }) {
4053
+ const { root, el, planCardLayout } = useAppearance(appearance);
4054
+ const isEnterprise = (p) => contactSalesPlans?.includes(p.code) ?? false;
3213
4055
  const billing = useOptionalBillingContext();
3214
4056
  const client = billing?.client;
3215
- const [plans, setPlans] = React.useState(propPlans ?? []);
3216
- const [currentPlan, setCurrentPlan] = React.useState(currentPlanCode ?? null);
3217
- const [isAnnual, setIsAnnual] = React.useState(false);
3218
- const [loading, setLoading] = React.useState(!propPlans);
3219
- const [processing, setProcessing] = React.useState(false);
3220
- const [checkoutError, setCheckoutError] = React.useState(null);
3221
- React.useEffect(() => {
3222
- if (propPlans) {
3223
- setPlans(propPlans);
3224
- return;
3225
- }
3226
- if (!client) {
3227
- setLoading(false);
3228
- setCheckoutError("BillingProvider with a publishableKey is required to load plans");
3229
- return;
3230
- }
3231
- let cancelled = false;
3232
- client.catalogFetch("/api/v1/plans").then(async (res) => {
3233
- if (!cancelled && res.ok) {
3234
- const data = await res.json();
3235
- setPlans(data.plans || []);
3236
- }
3237
- }).finally(() => {
3238
- if (!cancelled) setLoading(false);
3239
- });
3240
- return () => {
3241
- cancelled = true;
3242
- };
3243
- }, [client, propPlans]);
3244
- React.useEffect(() => {
4057
+ const catalog = reactQuery.useQuery(
4058
+ { ...catalogQuery(client), enabled: !propPlans && Boolean(client) },
4059
+ billing?.queryClient ?? idleQueryClient
4060
+ );
4061
+ const plans = propPlans ?? catalog.data?.plans ?? [];
4062
+ const loading = !propPlans && Boolean(client) && catalog.isPending;
4063
+ 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;
4064
+ const [currentPlan, setCurrentPlan] = React2.useState(currentPlanCode ?? null);
4065
+ const [isAnnual, setIsAnnual] = React2.useState(false);
4066
+ const [processing, setProcessing] = React2.useState(false);
4067
+ const [checkoutError, setCheckoutError] = React2.useState(null);
4068
+ React2.useEffect(() => {
3245
4069
  if (currentPlanCode !== void 0) setCurrentPlan(currentPlanCode);
3246
4070
  }, [currentPlanCode]);
3247
4071
  async function selectPlan(plan) {
@@ -3273,124 +4097,123 @@ function PricingTable({
3273
4097
  onCheckoutError?.(error);
3274
4098
  }
3275
4099
  }
3276
- const hasAnnual = plans.some((p) => p.interval === "yearly");
3277
- const filtered = showToggle && hasAnnual ? plans.filter((p) => p.interval === (isAnnual ? "yearly" : "monthly") || isEnterprise(p)) : plans;
3278
- if (loading) return /* @__PURE__ */ jsxRuntime.jsx(Skeleton, {});
3279
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style: {
3280
- fontFamily: "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
3281
- color: C.text,
3282
- background: C.bg
3283
- }, children: [
3284
- showToggle && hasAnnual && /* @__PURE__ */ jsxRuntime.jsx(Toggle, { isAnnual, onChange: setIsAnnual }),
4100
+ const ordered = sort ? [...plans].sort(sort) : [...sortPlansByPrice(plans.filter((p) => !isEnterprise(p))), ...plans.filter(isEnterprise)];
4101
+ const hasAnnual = ordered.some((p) => p.interval === "yearly");
4102
+ const current = plans.find((p) => p.code === currentPlan);
4103
+ const filtered = showToggle && hasAnnual ? ordered.filter((p) => p.interval === (isAnnual ? "yearly" : "monthly") || isEnterprise(p)) : ordered;
4104
+ const rootProps = root("pricingTable", text.body, className);
4105
+ if (loading) return /* @__PURE__ */ jsxRuntime.jsx(Skeleton, { rootProps });
4106
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ...rootProps, children: [
4107
+ showToggle && hasAnnual && /* @__PURE__ */ jsxRuntime.jsx(Toggle, { isAnnual, onChange: setIsAnnual, el }),
3285
4108
  processing && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", children: "Payment is processing. Confirmation may take a little while." }),
3286
- checkoutError && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", children: checkoutError }),
3287
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
4109
+ (checkoutError ?? loadError) && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", style: { color: T.dangerText }, children: checkoutError ?? loadError }),
4110
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ...el("pricingTable__grid", {
3288
4111
  display: "grid",
3289
4112
  gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
3290
4113
  gap: "1.5rem"
3291
- }, children: filtered.map((plan, i) => /* @__PURE__ */ jsxRuntime.jsx(
4114
+ }), children: filtered.map((plan, i) => /* @__PURE__ */ jsxRuntime.jsx(
3292
4115
  Card,
3293
4116
  {
3294
4117
  plan,
3295
- features: features?.[i],
4118
+ features: featureLines(features, plan, i),
3296
4119
  isCurrent: currentPlan === plan.code,
4120
+ isUpgrade: current && current.amount_currency === plan.amount_currency ? plan.amount_cents > current.amount_cents : void 0,
3297
4121
  isHighlighted: highlightPlan === plan.code,
4122
+ filled: highlightStyle === "filled",
4123
+ highlightLabel,
3298
4124
  isEnt: isEnterprise(plan),
3299
4125
  enterpriseEmail,
3300
- onSelect: () => void selectPlan(plan)
4126
+ onSelect: () => void selectPlan(plan),
4127
+ el,
4128
+ panel: planCardLayout === "panel"
3301
4129
  },
3302
4130
  plan.code
3303
- )) })
4131
+ )) }),
4132
+ /* @__PURE__ */ jsxRuntime.jsx(NozleBranding, {})
3304
4133
  ] });
3305
4134
  }
3306
4135
  function PlanCard({
3307
- id,
3308
- name,
3309
- monthlyPrice,
3310
- annualPrice,
3311
- features,
3312
- description,
3313
- isAnnual,
4136
+ plan,
4137
+ features: featureOverride,
3314
4138
  isCurrent,
3315
4139
  returnUrl,
3316
- onSelect
4140
+ onSelect,
4141
+ appearance
3317
4142
  }) {
3318
- const price = isAnnual ? annualPrice : monthlyPrice;
3319
- const interval = isAnnual ? "year" : "month";
3320
- return /* @__PURE__ */ jsxRuntime.jsxs(
3321
- "div",
4143
+ const { root, el, planCardLayout } = useAppearance(appearance);
4144
+ const { code: id, name, description } = plan;
4145
+ const price = formatPlanPrice(plan.amount_cents, plan.amount_currency);
4146
+ const interval = intervalLabel(plan.interval);
4147
+ const features = featureOverride ?? planFeatures(plan);
4148
+ const cta = onSelect && /* @__PURE__ */ jsxRuntime.jsx(
4149
+ "button",
4150
+ {
4151
+ type: "button",
4152
+ onClick: () => onSelect?.({
4153
+ planCode: id,
4154
+ returnUrl: returnUrl ?? window.location.href
4155
+ }),
4156
+ ...el("planCard__cta", {
4157
+ ...buttonStyle(isCurrent ? "secondary" : "primary"),
4158
+ ...isCurrent && { background: T.muted, color: T.mutedForeground, cursor: "default" },
4159
+ width: "100%"
4160
+ }),
4161
+ disabled: isCurrent,
4162
+ "aria-label": isCurrent ? `Current plan: ${name}` : `Select ${name}`,
4163
+ children: isCurrent ? "Current Plan" : "Get Started"
4164
+ }
4165
+ );
4166
+ if (planCardLayout === "panel") {
4167
+ return /* @__PURE__ */ jsxRuntime.jsx(
4168
+ PanelPlanCard,
4169
+ {
4170
+ frame: (style) => ({ "data-testid": `plan-card-${id}`, "aria-current": isCurrent ? "true" : void 0, ...root("planCard", style) }),
4171
+ el,
4172
+ name,
4173
+ description,
4174
+ price,
4175
+ interval,
4176
+ features,
4177
+ isCurrent: Boolean(isCurrent),
4178
+ isHighlighted: false,
4179
+ filled: false,
4180
+ children: cta
4181
+ }
4182
+ );
4183
+ }
4184
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4185
+ "div",
3322
4186
  {
3323
4187
  "data-testid": `plan-card-${id}`,
3324
4188
  "aria-current": isCurrent ? "true" : void 0,
3325
- style: {
3326
- background: "var(--nozle-background, var(--background))",
3327
- border: isCurrent ? "2px solid var(--nozle-primary, var(--primary))" : "1px solid var(--nozle-border, var(--border))",
3328
- borderRadius: "var(--nozle-radius, 0.5rem)",
4189
+ ...root("planCard", {
4190
+ ...text.body,
4191
+ background: T.card,
4192
+ border: border(isCurrent ? T.primary : T.border),
4193
+ outline: isCurrent ? `1px solid ${T.primary}` : void 0,
4194
+ borderRadius: T.radius,
4195
+ boxShadow: T.shadow,
3329
4196
  padding: "1.5rem",
3330
4197
  display: "flex",
3331
4198
  flexDirection: "column",
3332
4199
  gap: "1rem"
3333
- },
3334
- className: isCurrent ? "border-primary" : "",
4200
+ }),
3335
4201
  children: [
3336
4202
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3337
- /* @__PURE__ */ jsxRuntime.jsx(
3338
- "h3",
3339
- {
3340
- style: {
3341
- fontSize: "1.25rem",
3342
- fontWeight: 600,
3343
- color: "var(--nozle-foreground, var(--foreground))",
3344
- margin: 0
3345
- },
3346
- children: name
3347
- }
3348
- ),
3349
- description && /* @__PURE__ */ jsxRuntime.jsx(
3350
- "p",
3351
- {
3352
- style: {
3353
- color: "var(--nozle-muted-foreground, var(--muted-foreground))",
3354
- fontSize: "0.875rem",
3355
- margin: "0.25rem 0 0"
3356
- },
3357
- children: description
3358
- }
3359
- )
4203
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { ...el("planCard__name", { ...text.heading, margin: 0 }), children: name }),
4204
+ description && /* @__PURE__ */ jsxRuntime.jsx("p", { ...el("planCard__description", { ...text.secondary, margin: "0.25rem 0 0" }), children: description })
3360
4205
  ] }),
3361
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3362
- /* @__PURE__ */ jsxRuntime.jsxs(
3363
- "span",
3364
- {
3365
- style: {
3366
- fontSize: "2rem",
3367
- fontWeight: 700,
3368
- color: "var(--nozle-foreground, var(--foreground))"
3369
- },
3370
- children: [
3371
- "$",
3372
- price
3373
- ]
3374
- }
3375
- ),
3376
- /* @__PURE__ */ jsxRuntime.jsxs(
3377
- "span",
3378
- {
3379
- style: {
3380
- color: "var(--nozle-muted-foreground, var(--muted-foreground))",
3381
- marginLeft: "0.25rem"
3382
- },
3383
- children: [
3384
- "/",
3385
- interval
3386
- ]
3387
- }
3388
- )
4206
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { ...el("planCard__price"), children: [
4207
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: text.metric, children: price }),
4208
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { ...text.meta, marginLeft: "0.25rem" }, children: [
4209
+ "/",
4210
+ interval
4211
+ ] })
3389
4212
  ] }),
3390
4213
  /* @__PURE__ */ jsxRuntime.jsx(
3391
4214
  "ul",
3392
4215
  {
3393
- style: {
4216
+ ...el("planCard__features", {
3394
4217
  listStyle: "none",
3395
4218
  padding: 0,
3396
4219
  margin: 0,
@@ -3398,160 +4221,51 @@ function PlanCard({
3398
4221
  flexDirection: "column",
3399
4222
  gap: "0.5rem",
3400
4223
  flex: 1
3401
- },
3402
- children: features.map((feature) => /* @__PURE__ */ jsxRuntime.jsxs(
3403
- "li",
3404
- {
3405
- style: {
3406
- display: "flex",
3407
- alignItems: "center",
3408
- gap: "0.5rem",
3409
- color: "var(--nozle-foreground, var(--foreground))",
3410
- fontSize: "0.875rem"
3411
- },
3412
- children: [
3413
- /* @__PURE__ */ jsxRuntime.jsx(
3414
- "span",
3415
- {
3416
- style: { color: "var(--nozle-primary, var(--primary))" },
3417
- "aria-hidden": "true",
3418
- children: "\u2713"
3419
- }
3420
- ),
3421
- feature
3422
- ]
3423
- },
4224
+ }),
4225
+ children: features.map((feature) => /* @__PURE__ */ jsxRuntime.jsxs("li", { ...el("planCard__feature", { display: "flex", alignItems: "center", gap: "0.5rem", ...text.body }), children: [
4226
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: T.primary }, "aria-hidden": "true", children: "\u2713" }),
3424
4227
  feature
3425
- ))
4228
+ ] }, feature))
3426
4229
  }
3427
4230
  ),
3428
- onSelect && /* @__PURE__ */ jsxRuntime.jsx(
3429
- "button",
3430
- {
3431
- onClick: () => onSelect?.({
3432
- planCode: id,
3433
- returnUrl: returnUrl ?? window.location.href
3434
- }),
3435
- style: {
3436
- background: isCurrent ? "var(--nozle-muted, var(--muted))" : "var(--nozle-primary, var(--primary))",
3437
- color: isCurrent ? "var(--nozle-muted-foreground, var(--muted-foreground))" : "var(--nozle-primary-foreground, var(--primary-foreground))",
3438
- border: "none",
3439
- borderRadius: "var(--nozle-radius, 0.5rem)",
3440
- padding: "0.75rem 1.5rem",
3441
- cursor: isCurrent ? "default" : "pointer",
3442
- fontWeight: 500,
3443
- width: "100%"
3444
- },
3445
- disabled: isCurrent,
3446
- "aria-label": isCurrent ? `Current plan: ${name}` : `Select ${name}`,
3447
- children: isCurrent ? "Current Plan" : "Get Started"
3448
- }
3449
- )
4231
+ cta
3450
4232
  ]
3451
4233
  }
3452
4234
  );
3453
4235
  }
3454
4236
  function PlanComparison({
3455
- plans,
3456
- features
4237
+ plans: catalogPlans,
4238
+ features: featureOverride,
4239
+ sort,
4240
+ appearance
3457
4241
  }) {
3458
- return /* @__PURE__ */ jsxRuntime.jsx(
3459
- "div",
3460
- {
3461
- style: {
3462
- overflowX: "auto",
3463
- background: "var(--nozle-background, var(--background))"
3464
- },
3465
- children: /* @__PURE__ */ jsxRuntime.jsxs(
3466
- "table",
3467
- {
3468
- style: {
3469
- width: "100%",
3470
- borderCollapse: "collapse",
3471
- fontSize: "0.875rem",
3472
- color: "var(--nozle-foreground, var(--foreground))"
3473
- },
3474
- children: [
3475
- /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
3476
- /* @__PURE__ */ jsxRuntime.jsx(
3477
- "th",
3478
- {
3479
- style: {
3480
- padding: "0.75rem 1rem",
3481
- textAlign: "left",
3482
- borderBottom: "1px solid var(--nozle-border, var(--border))",
3483
- color: "var(--nozle-muted-foreground, var(--muted-foreground))",
3484
- fontWeight: 500
3485
- },
3486
- children: "Feature"
3487
- }
3488
- ),
3489
- plans.map((plan) => /* @__PURE__ */ jsxRuntime.jsx(
3490
- "th",
3491
- {
3492
- style: {
3493
- padding: "0.75rem 1rem",
3494
- textAlign: "center",
3495
- borderBottom: "1px solid var(--nozle-border, var(--border))",
3496
- fontWeight: 600,
3497
- color: "var(--nozle-foreground, var(--foreground))"
3498
- },
3499
- children: plan.name
3500
- },
3501
- plan.id
3502
- ))
3503
- ] }) }),
3504
- /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: features.map((feature) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
3505
- /* @__PURE__ */ jsxRuntime.jsx(
3506
- "td",
3507
- {
3508
- style: {
3509
- padding: "0.75rem 1rem",
3510
- borderBottom: "1px solid var(--nozle-border, var(--border))",
3511
- color: "var(--nozle-foreground, var(--foreground))"
3512
- },
3513
- children: feature.label
3514
- }
3515
- ),
3516
- plans.map((plan) => {
3517
- const value = feature.values[plan.id];
3518
- return /* @__PURE__ */ jsxRuntime.jsx(
3519
- "td",
3520
- {
3521
- style: {
3522
- padding: "0.75rem 1rem",
3523
- textAlign: "center",
3524
- borderBottom: "1px solid var(--nozle-border, var(--border))"
3525
- },
3526
- children: typeof value === "boolean" ? value ? /* @__PURE__ */ jsxRuntime.jsx(
3527
- "span",
3528
- {
3529
- style: {
3530
- color: "var(--nozle-primary, var(--primary))"
3531
- },
3532
- "aria-label": "Included",
3533
- children: "\u2713"
3534
- }
3535
- ) : /* @__PURE__ */ jsxRuntime.jsx(
3536
- "span",
3537
- {
3538
- style: {
3539
- color: "var(--nozle-muted-foreground, var(--muted-foreground))"
3540
- },
3541
- "aria-label": "Not included",
3542
- children: "\u2715"
3543
- }
3544
- ) : /* @__PURE__ */ jsxRuntime.jsx("span", { children: value ?? "\u2014" })
3545
- },
3546
- plan.id
3547
- );
3548
- })
3549
- ] }, feature.key)) })
3550
- ]
3551
- }
3552
- )
3553
- }
3554
- );
4242
+ const { root, el } = useAppearance(appearance);
4243
+ const plans = sort ? [...catalogPlans].sort(sort) : sortPlansByPrice(catalogPlans);
4244
+ const features = featureOverride ?? comparisonRows(plans);
4245
+ const cell = (style) => el("planComparison__cell", style);
4246
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ...root("planComparison", { minWidth: 0 }), children: [
4247
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: table.frame, children: /* @__PURE__ */ jsxRuntime.jsxs("table", { ...el("planComparison__table", table.table), children: [
4248
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
4249
+ /* @__PURE__ */ jsxRuntime.jsx("th", { ...cell(table.head()), children: "Feature" }),
4250
+ plans.map((plan) => /* @__PURE__ */ jsxRuntime.jsxs("th", { ...cell({ ...table.head(), textAlign: "center", padding: "10px 20px" }), children: [
4251
+ plan.name,
4252
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { ...text.meta, letterSpacing: "normal", textTransform: "none" }, children: [
4253
+ formatPlanPrice(plan.amount_cents, plan.amount_currency),
4254
+ " / ",
4255
+ intervalLabel(plan.interval)
4256
+ ] })
4257
+ ] }, plan.code))
4258
+ ] }) }),
4259
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: features.map((feature, index) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { ...el("planComparison__row"), children: [
4260
+ /* @__PURE__ */ jsxRuntime.jsx("td", { ...cell(table.cell(false, index === features.length - 1)), children: feature.label }),
4261
+ plans.map((plan) => {
4262
+ const value = feature.values[plan.code];
4263
+ return /* @__PURE__ */ jsxRuntime.jsx("td", { ...cell({ ...table.cell(false, index === features.length - 1), textAlign: "center" }), children: typeof value === "boolean" ? value ? /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: T.primary }, "aria-label": "Included", children: "\u2713" }) : /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: T.mutedForeground }, "aria-label": "Not included", children: "\u2715" }) : /* @__PURE__ */ jsxRuntime.jsx("span", { children: value ?? "\u2014" }) }, plan.code);
4264
+ })
4265
+ ] }, feature.key)) })
4266
+ ] }) }),
4267
+ /* @__PURE__ */ jsxRuntime.jsx(NozleBranding, {})
4268
+ ] });
3555
4269
  }
3556
4270
  function UpgradeButton({
3557
4271
  planCode,
@@ -3567,26 +4281,18 @@ function UpgradeButton({
3567
4281
  onCheckoutStarted,
3568
4282
  onDowngradeScheduled,
3569
4283
  onUpgraded,
3570
- onError
4284
+ onError,
4285
+ appearance
3571
4286
  }) {
3572
- const [isOpen, setIsOpen] = React.useState(false);
4287
+ const [isOpen, setIsOpen] = React2.useState(false);
4288
+ const { root } = useAppearance(appearance);
3573
4289
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3574
4290
  /* @__PURE__ */ jsxRuntime.jsx(
3575
4291
  "button",
3576
4292
  {
3577
4293
  type: "button",
3578
4294
  onClick: () => setIsOpen(true),
3579
- className,
3580
- style: {
3581
- padding: "0.75rem 1.5rem",
3582
- borderRadius: "var(--nozle-radius, 0.5rem)",
3583
- border: "none",
3584
- background: "var(--nozle-primary, var(--primary))",
3585
- color: "var(--nozle-primary-foreground, var(--primary-foreground))",
3586
- cursor: "pointer",
3587
- fontWeight: 500,
3588
- ...style
3589
- },
4295
+ ...root("upgradeButton", { ...buttonStyle(), ...style }, className),
3590
4296
  children: label
3591
4297
  }
3592
4298
  ),
@@ -3594,6 +4300,7 @@ function UpgradeButton({
3594
4300
  UpgradeModal,
3595
4301
  {
3596
4302
  isOpen,
4303
+ appearance,
3597
4304
  planCode,
3598
4305
  returnUrl,
3599
4306
  subscriptionId,
@@ -3652,11 +4359,13 @@ function PlanGate({
3652
4359
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
3653
4360
  }
3654
4361
  function UpgradePrompt({
3655
- text = "Upgrade to access this feature",
4362
+ text: text2 = "Upgrade to access this feature",
3656
4363
  planName: planName2,
3657
- onUpgrade
4364
+ onUpgrade,
4365
+ appearance
3658
4366
  }) {
3659
- const displayText = planName2 ? `Upgrade to ${planName2} to access this feature` : text;
4367
+ const { root } = useAppearance(appearance);
4368
+ const displayText = planName2 ? `Upgrade to ${planName2} to access this feature` : text2;
3660
4369
  function handleUpgrade() {
3661
4370
  if (onUpgrade) {
3662
4371
  onUpgrade();
@@ -3667,40 +4376,23 @@ function UpgradePrompt({
3667
4376
  return /* @__PURE__ */ jsxRuntime.jsxs(
3668
4377
  "div",
3669
4378
  {
3670
- style: {
4379
+ ...root("upgradePrompt", {
3671
4380
  display: "flex",
3672
4381
  flexDirection: "column",
3673
4382
  alignItems: "center",
3674
4383
  gap: "0.75rem",
3675
4384
  padding: "1rem",
3676
- textAlign: "center"
3677
- },
4385
+ textAlign: "center",
4386
+ ...text.body
4387
+ }),
3678
4388
  children: [
3679
- /* @__PURE__ */ jsxRuntime.jsx(
3680
- "p",
3681
- {
3682
- style: {
3683
- color: "var(--nozle-foreground, var(--foreground))",
3684
- fontSize: "0.875rem",
3685
- margin: 0
3686
- },
3687
- children: displayText
3688
- }
3689
- ),
4389
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: { margin: 0 }, children: displayText }),
3690
4390
  /* @__PURE__ */ jsxRuntime.jsx(
3691
4391
  "button",
3692
4392
  {
4393
+ type: "button",
3693
4394
  onClick: handleUpgrade,
3694
- style: {
3695
- padding: "0.5rem 1.25rem",
3696
- borderRadius: "var(--nozle-radius, 0.5rem)",
3697
- border: "none",
3698
- background: "var(--nozle-primary, var(--primary))",
3699
- color: "var(--nozle-primary-foreground, var(--primary-foreground))",
3700
- cursor: "pointer",
3701
- fontWeight: 500,
3702
- fontSize: "0.875rem"
3703
- },
4395
+ style: { ...buttonStyle(), padding: "0.5rem 1.25rem" },
3704
4396
  children: "Upgrade"
3705
4397
  }
3706
4398
  )
@@ -3713,12 +4405,14 @@ function LockedOverlay({
3713
4405
  upgradeText,
3714
4406
  upgradePlanName,
3715
4407
  onUpgrade,
3716
- children
4408
+ children,
4409
+ appearance
3717
4410
  }) {
4411
+ const { root, el } = useAppearance(appearance);
3718
4412
  if (!locked) {
3719
4413
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
3720
4414
  }
3721
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative", display: "inline-block" }, children: [
4415
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ...root("lockedOverlay", { position: "relative", display: "inline-block" }), children: [
3722
4416
  /* @__PURE__ */ jsxRuntime.jsx(
3723
4417
  "div",
3724
4418
  {
@@ -3734,16 +4428,15 @@ function LockedOverlay({
3734
4428
  /* @__PURE__ */ jsxRuntime.jsx(
3735
4429
  "div",
3736
4430
  {
3737
- style: {
4431
+ ...el("lockedOverlay__panel", {
3738
4432
  position: "absolute",
3739
4433
  inset: 0,
3740
4434
  display: "flex",
3741
4435
  alignItems: "center",
3742
4436
  justifyContent: "center",
3743
- background: "var(--nozle-background, var(--background))",
3744
- opacity: 0.92,
3745
- borderRadius: "var(--nozle-radius, 0.5rem)"
3746
- },
4437
+ background: `color-mix(in srgb, ${T.background} 92%, transparent)`,
4438
+ borderRadius: T.radius
4439
+ }),
3747
4440
  role: "dialog",
3748
4441
  "aria-label": "Feature locked \u2014 upgrade required",
3749
4442
  children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -3751,7 +4444,8 @@ function LockedOverlay({
3751
4444
  {
3752
4445
  text: upgradeText,
3753
4446
  planName: upgradePlanName,
3754
- onUpgrade
4447
+ onUpgrade,
4448
+ appearance
3755
4449
  }
3756
4450
  )
3757
4451
  }
@@ -3759,69 +4453,110 @@ function LockedOverlay({
3759
4453
  ] });
3760
4454
  }
3761
4455
  function getUsageMeterColor(percentage) {
3762
- if (percentage >= 95) return "var(--nozle-destructive, var(--destructive))";
3763
- if (percentage >= 80) return "var(--nozle-warning, var(--warning))";
3764
- return "var(--nozle-primary, var(--primary))";
4456
+ if (percentage >= 95) return T.danger;
4457
+ if (percentage >= 80) return T.warning;
4458
+ return T.primary;
3765
4459
  }
3766
4460
  function UsageMeter({
3767
4461
  used,
3768
4462
  limit,
3769
4463
  label,
3770
- showText = true
4464
+ showText = true,
4465
+ appearance
3771
4466
  }) {
3772
- const percentage = limit > 0 ? Math.min(100, used / limit * 100) : 0;
4467
+ const { root, el } = useAppearance(appearance);
4468
+ const unlimited = limit == null;
4469
+ const percentage = !unlimited && limit > 0 ? Math.min(100, used / limit * 100) : 0;
3773
4470
  const color = getUsageMeterColor(percentage);
3774
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full space-y-1", children: [
3775
- label && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between text-sm", children: [
3776
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: label }),
3777
- showText && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { color }, children: [
3778
- used.toLocaleString(),
3779
- " / ",
3780
- limit.toLocaleString()
3781
- ] })
3782
- ] }),
3783
- /* @__PURE__ */ jsxRuntime.jsx(
3784
- "div",
3785
- {
3786
- className: "h-2 rounded-full overflow-hidden",
3787
- style: { background: "var(--nozle-border, var(--border))" },
3788
- children: /* @__PURE__ */ jsxRuntime.jsx(
4471
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4472
+ "div",
4473
+ {
4474
+ ...root("usageMeter", {
4475
+ width: "100%",
4476
+ display: "flex",
4477
+ flexDirection: "column",
4478
+ gap: "0.25rem",
4479
+ ...text.body
4480
+ }),
4481
+ children: [
4482
+ label && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
4483
+ /* @__PURE__ */ jsxRuntime.jsx("span", { ...el("usageMeter__label"), children: label }),
4484
+ showText && /* @__PURE__ */ jsxRuntime.jsxs("span", { ...el("usageMeter__value", { color: percentage >= 95 ? T.dangerText : percentage >= 80 ? T.warningText : T.foreground }), children: [
4485
+ used.toLocaleString(),
4486
+ " / ",
4487
+ unlimited ? "Unlimited" : limit.toLocaleString()
4488
+ ] })
4489
+ ] }),
4490
+ /* @__PURE__ */ jsxRuntime.jsx(
3789
4491
  "div",
3790
4492
  {
3791
- className: "h-full rounded-full",
3792
- style: {
3793
- width: `${percentage}%`,
3794
- background: color,
3795
- transition: "width 0.3s ease"
3796
- }
4493
+ ...el("usageMeter__track", {
4494
+ height: "0.5rem",
4495
+ borderRadius: "9999px",
4496
+ overflow: "hidden",
4497
+ background: T.muted
4498
+ }),
4499
+ children: /* @__PURE__ */ jsxRuntime.jsx(
4500
+ "div",
4501
+ {
4502
+ ...el("usageMeter__fill", {
4503
+ height: "100%",
4504
+ borderRadius: "9999px",
4505
+ width: `${percentage}%`,
4506
+ background: color,
4507
+ transition: "width 0.3s ease"
4508
+ })
4509
+ }
4510
+ )
3797
4511
  }
3798
4512
  )
3799
- }
3800
- )
3801
- ] });
4513
+ ]
4514
+ }
4515
+ );
4516
+ }
4517
+ function usageItems(entitlements, results) {
4518
+ return entitlements.flatMap((entitlement) => {
4519
+ const result = results[entitlement.code];
4520
+ if (entitlement.feature_type !== "metered" || !result) return [];
4521
+ return [{ key: entitlement.code, label: entitlement.name, used: result.used, limit: result.limit ?? null }];
4522
+ });
3802
4523
  }
3803
4524
  function UsageDashboard({
3804
4525
  features = [],
3805
- loading = false
4526
+ loading = false,
4527
+ error = null,
4528
+ emptyMessage = "No usage to show yet.",
4529
+ appearance
3806
4530
  }) {
4531
+ const { root } = useAppearance(appearance);
4532
+ const props = root("usageDashboard", {
4533
+ ...surface,
4534
+ margin: 0,
4535
+ display: "flex",
4536
+ flexDirection: "column",
4537
+ gap: loading ? "0.75rem" : "1rem",
4538
+ // Errors and the empty message take the same colours as in the other components.
4539
+ ...error ? { color: T.dangerText } : features.length === 0 && !loading && text.secondary
4540
+ });
3807
4541
  if (loading) {
3808
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-pulse space-y-3", children: [1, 2, 3].map((i) => /* @__PURE__ */ jsxRuntime.jsx(
3809
- "div",
3810
- {
3811
- className: "h-8 rounded",
3812
- style: { background: "var(--nozle-border, var(--border))" }
3813
- },
3814
- i
3815
- )) });
4542
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ...props, "aria-busy": "true", children: [1, 2, 3].map((i) => /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: "2rem", borderRadius: `calc(${T.radius} / 3)`, background: T.skeleton } }, i)) });
4543
+ }
4544
+ if (error) {
4545
+ return /* @__PURE__ */ jsxRuntime.jsx("p", { ...props, role: "alert", children: typeof error === "string" ? error : error.message });
4546
+ }
4547
+ if (features.length === 0) {
4548
+ return /* @__PURE__ */ jsxRuntime.jsx("p", { ...props, role: "status", children: emptyMessage });
3816
4549
  }
3817
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4", children: features.map((f) => /* @__PURE__ */ jsxRuntime.jsx(UsageMeter, { used: f.used, limit: f.limit, label: f.label }, f.key)) });
4550
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ...props, children: features.map((f) => /* @__PURE__ */ jsxRuntime.jsx(UsageMeter, { used: f.used, limit: f.limit, label: f.label, appearance }, f.key)) });
3818
4551
  }
3819
4552
  function UsageAlert({
3820
4553
  features,
3821
4554
  threshold = 80,
3822
- upgradeHref = "/plans"
4555
+ upgradeHref = "/plans",
4556
+ appearance
3823
4557
  }) {
3824
- const [dismissed, setDismissed] = React.useState(/* @__PURE__ */ new Set());
4558
+ const { root, el } = useAppearance(appearance);
4559
+ const [dismissed, setDismissed] = React2.useState(/* @__PURE__ */ new Set());
3825
4560
  const alertFeatures = features.filter(
3826
4561
  (f) => f.percentage >= threshold && !dismissed.has(f.key)
3827
4562
  );
@@ -3831,15 +4566,19 @@ function UsageAlert({
3831
4566
  const handleDismiss = (key) => {
3832
4567
  setDismissed((prev) => new Set(prev).add(key));
3833
4568
  };
3834
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", children: alertFeatures.map((f) => /* @__PURE__ */ jsxRuntime.jsxs(
4569
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ...root("usageAlert", { display: "flex", flexDirection: "column", gap: "0.5rem", ...text.body }), children: alertFeatures.map((f) => /* @__PURE__ */ jsxRuntime.jsxs(
3835
4570
  "div",
3836
4571
  {
3837
4572
  role: "alert",
3838
- className: "flex items-center justify-between rounded-md px-4 py-3 text-sm",
3839
4573
  style: {
3840
- background: "var(--nozle-warning-bg, var(--warning-bg, #fef9c3))",
3841
- color: "var(--nozle-warning-text, var(--warning-text, #854d0e))",
3842
- border: "1px solid var(--nozle-warning-border, var(--warning-border, #fde047))"
4574
+ display: "flex",
4575
+ alignItems: "center",
4576
+ justifyContent: "space-between",
4577
+ borderRadius: `calc(${T.radius} / 2)`,
4578
+ padding: "0.75rem 1rem",
4579
+ background: T.warningSoft,
4580
+ color: T.warningText,
4581
+ border: border(T.warningBorder)
3843
4582
  },
3844
4583
  children: [
3845
4584
  /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
@@ -3862,8 +4601,8 @@ function UsageAlert({
3862
4601
  {
3863
4602
  href: upgradeHref,
3864
4603
  style: {
4604
+ ...text.action,
3865
4605
  color: "inherit",
3866
- fontWeight: 600,
3867
4606
  textDecoration: "underline"
3868
4607
  },
3869
4608
  children: "Upgrade"
@@ -3873,16 +4612,17 @@ function UsageAlert({
3873
4612
  /* @__PURE__ */ jsxRuntime.jsx(
3874
4613
  "button",
3875
4614
  {
4615
+ type: "button",
3876
4616
  onClick: () => handleDismiss(f.key),
3877
4617
  "aria-label": `Dismiss ${f.label} alert`,
3878
- style: {
4618
+ ...el("usageAlert__dismiss", {
3879
4619
  background: "none",
3880
4620
  border: "none",
3881
4621
  cursor: "pointer",
3882
4622
  padding: "0 0 0 0.75rem",
3883
- fontSize: "1rem",
4623
+ ...text.action,
3884
4624
  color: "inherit"
3885
- },
4625
+ }),
3886
4626
  children: "\xD7"
3887
4627
  }
3888
4628
  )
@@ -3891,44 +4631,247 @@ function UsageAlert({
3891
4631
  f.key
3892
4632
  )) });
3893
4633
  }
3894
- var TIER_STYLES = {
3895
- free: {
3896
- background: "var(--nozle-muted, var(--muted, #f1f5f9))",
3897
- color: "var(--nozle-muted-foreground, var(--muted-foreground, #64748b))",
3898
- border: "1px solid var(--nozle-border, var(--border, #e2e8f0))"
3899
- },
3900
- starter: {
3901
- background: "color-mix(in srgb, var(--nozle-primary, var(--primary, #3b82f6)) 10%, transparent)",
3902
- color: "var(--nozle-primary, var(--primary, #3b82f6))",
3903
- border: "1px solid color-mix(in srgb, var(--nozle-primary, var(--primary, #3b82f6)) 30%, transparent)"
3904
- },
3905
- pro: {
3906
- background: "var(--nozle-primary, var(--primary, #3b82f6))",
3907
- color: "var(--nozle-primary-foreground, var(--primary-foreground, #ffffff))",
3908
- border: "1px solid transparent"
3909
- },
3910
- enterprise: {
3911
- background: "var(--nozle-accent, var(--accent, #8b5cf6))",
3912
- color: "var(--nozle-accent-foreground, var(--accent-foreground, #ffffff))",
3913
- border: "1px solid transparent"
4634
+ var STATUS_LABELS = {
4635
+ active: "Active",
4636
+ pending: "Pending",
4637
+ incomplete: "Awaiting payment",
4638
+ canceled: "Canceled",
4639
+ terminated: "Ended"
4640
+ };
4641
+ function statusColors(status, ending) {
4642
+ if (status === "active" && !ending) return { background: T.successSoft, color: T.successText };
4643
+ if (status === "active" || status === "pending" || status === "incomplete") {
4644
+ return { background: T.warningSoft, color: T.warningText };
3914
4645
  }
4646
+ return { background: T.muted, color: T.mutedForeground };
4647
+ }
4648
+ function day(value, locale) {
4649
+ const parsed = new Date(value);
4650
+ return Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleDateString(locale, { dateStyle: "medium" });
4651
+ }
4652
+ function currentPlanMeters(entitlements, usage) {
4653
+ const units = /* @__PURE__ */ new Map();
4654
+ for (const charge of usage?.charges_usage ?? []) {
4655
+ units.set(charge.feature.code, (units.get(charge.feature.code) ?? 0) + (Number(charge.units) || 0));
4656
+ }
4657
+ return (entitlements ?? []).filter((entitlement) => entitlement.feature_type === "metered").map((entitlement) => ({
4658
+ key: entitlement.code,
4659
+ label: entitlement.name,
4660
+ used: units.get(entitlement.code) ?? 0,
4661
+ limit: entitlement.usage_limit ?? null
4662
+ }));
4663
+ }
4664
+ function CurrentPlan(props) {
4665
+ return props.subscription === void 0 ? /* @__PURE__ */ jsxRuntime.jsx(CurrentPlanFromBackend, { ...props }) : /* @__PURE__ */ jsxRuntime.jsx(CurrentPlanView, { ...props });
4666
+ }
4667
+ function CurrentPlanFromBackend(props) {
4668
+ const subscription = useSubscription();
4669
+ const plans = usePlans();
4670
+ const usage = useUsage();
4671
+ const planCode = subscription.data?.subscription?.plan_code;
4672
+ const meters = props.usage ?? currentPlanMeters(plans.plans.find((plan) => plan.code === planCode)?.entitlements, usage.data);
4673
+ return /* @__PURE__ */ jsxRuntime.jsx(
4674
+ CurrentPlanView,
4675
+ {
4676
+ ...props,
4677
+ subscription: subscription.data ?? null,
4678
+ usage: meters,
4679
+ isLoading: props.isLoading || subscription.isPending,
4680
+ error: props.error ?? subscription.error
4681
+ }
4682
+ );
4683
+ }
4684
+ function CurrentPlanView({
4685
+ subscription,
4686
+ usage = [],
4687
+ details = [],
4688
+ actions,
4689
+ warningPercent = 80,
4690
+ title = "Current plan",
4691
+ isLoading = false,
4692
+ error = null,
4693
+ emptyMessage = "No active plan.",
4694
+ pastDueMessage = "A payment is overdue. Pay the open invoice to keep your plan active.",
4695
+ locale = "en-US",
4696
+ appearance
4697
+ }) {
4698
+ const { root, el } = useAppearance(appearance);
4699
+ const frame = { ...surface, padding: 0, overflow: "hidden" };
4700
+ const header = /* @__PURE__ */ jsxRuntime.jsxs(
4701
+ "div",
4702
+ {
4703
+ style: {
4704
+ display: "flex",
4705
+ alignItems: "center",
4706
+ justifyContent: "space-between",
4707
+ gap: 12,
4708
+ padding: "0.875rem 1.25rem",
4709
+ borderBottom: border()
4710
+ },
4711
+ children: [
4712
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: text.heading, children: title }),
4713
+ actions
4714
+ ]
4715
+ }
4716
+ );
4717
+ const body = (children) => /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "1.25rem", display: "grid", gap: "1rem" }, children });
4718
+ if (isLoading) {
4719
+ return /* @__PURE__ */ jsxRuntime.jsxs("section", { ...root("currentPlan", frame), "aria-busy": "true", children: [
4720
+ header,
4721
+ body(/* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 72, borderRadius: `calc(${T.radius} / 2)`, background: T.skeleton } }))
4722
+ ] });
4723
+ }
4724
+ if (error) {
4725
+ return /* @__PURE__ */ jsxRuntime.jsxs("section", { ...root("currentPlan", frame), children: [
4726
+ header,
4727
+ body(/* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", style: { margin: 0, color: T.dangerText }, children: typeof error === "string" ? error : error.message }))
4728
+ ] });
4729
+ }
4730
+ const current = subscription?.subscription;
4731
+ if (!current) {
4732
+ return /* @__PURE__ */ jsxRuntime.jsxs("section", { ...root("currentPlan", frame), children: [
4733
+ header,
4734
+ body(/* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", style: { ...text.secondary, margin: 0 }, children: emptyMessage }))
4735
+ ] });
4736
+ }
4737
+ const pending = subscription?.pending_change;
4738
+ const ending = Boolean(current.ending_at);
4739
+ const near = warningPercent === null ? [] : usage.filter((meter) => meter.limit != null && meter.limit > 0 && meter.used / meter.limit * 100 >= warningPercent);
4740
+ const statusLabel = ending && current.ending_at ? `Ends ${day(current.ending_at, locale)}` : STATUS_LABELS[current.status] ?? current.status;
4741
+ return /* @__PURE__ */ jsxRuntime.jsxs("section", { ...root("currentPlan", frame), "aria-label": title, children: [
4742
+ header,
4743
+ body(
4744
+ /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4745
+ subscription?.past_due && /* @__PURE__ */ jsxRuntime.jsx(
4746
+ "p",
4747
+ {
4748
+ role: "alert",
4749
+ ...el("currentPlan__notice", {
4750
+ margin: 0,
4751
+ padding: "0.625rem 0.875rem",
4752
+ ...text.body,
4753
+ borderRadius: `calc(${T.radius} / 2)`,
4754
+ background: T.dangerSoft,
4755
+ color: T.dangerText,
4756
+ border: border(T.dangerBorder)
4757
+ }),
4758
+ children: pastDueMessage
4759
+ }
4760
+ ),
4761
+ near.map((meter) => /* @__PURE__ */ jsxRuntime.jsxs(
4762
+ "p",
4763
+ {
4764
+ role: "status",
4765
+ ...el("currentPlan__notice", {
4766
+ margin: 0,
4767
+ padding: "0.625rem 0.875rem",
4768
+ ...text.body,
4769
+ borderRadius: `calc(${T.radius} / 2)`,
4770
+ background: T.warningSoft,
4771
+ color: T.warningText,
4772
+ border: border(T.warningBorder)
4773
+ }),
4774
+ children: [
4775
+ "You have used ",
4776
+ Math.round(meter.used / meter.limit * 100),
4777
+ "% of the ",
4778
+ meter.label,
4779
+ " included in",
4780
+ " ",
4781
+ current.plan.name,
4782
+ "."
4783
+ ]
4784
+ },
4785
+ meter.key
4786
+ )),
4787
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexWrap: "wrap", alignItems: "center", gap: "0.625rem" }, children: [
4788
+ /* @__PURE__ */ jsxRuntime.jsx("span", { ...el("currentPlan__name", text.heading), children: current.plan.name }),
4789
+ /* @__PURE__ */ jsxRuntime.jsx(
4790
+ "span",
4791
+ {
4792
+ ...el("currentPlan__status", {
4793
+ ...text.label,
4794
+ ...statusColors(current.status, ending),
4795
+ padding: "0.125rem 0.625rem",
4796
+ borderRadius: 9999
4797
+ }),
4798
+ children: statusLabel
4799
+ }
4800
+ ),
4801
+ pending && /* @__PURE__ */ jsxRuntime.jsxs(
4802
+ "span",
4803
+ {
4804
+ ...el("currentPlan__scheduled", {
4805
+ ...text.label,
4806
+ padding: "0.125rem 0.625rem",
4807
+ borderRadius: 9999,
4808
+ border: border()
4809
+ }),
4810
+ children: [
4811
+ "Changes to ",
4812
+ pending.name,
4813
+ " on ",
4814
+ day(pending.effective_at, locale)
4815
+ ]
4816
+ }
4817
+ ),
4818
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { ...el("currentPlan__price", { ...text.secondary, marginLeft: "auto" }), children: [
4819
+ formatPlanPrice(Number(current.plan.amount_cents), current.plan.currency),
4820
+ " / ",
4821
+ intervalLabel(current.plan.interval)
4822
+ ] })
4823
+ ] }),
4824
+ usage.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { ...el("currentPlan__usage", { display: "grid", gap: "0.875rem" }), children: usage.map((meter) => /* @__PURE__ */ jsxRuntime.jsx(
4825
+ UsageMeter,
4826
+ {
4827
+ label: `${meter.label} used this period`,
4828
+ used: meter.used,
4829
+ limit: meter.limit,
4830
+ appearance
4831
+ },
4832
+ meter.key
4833
+ )) }),
4834
+ details.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
4835
+ "dl",
4836
+ {
4837
+ ...el("currentPlan__details", {
4838
+ margin: 0,
4839
+ display: "grid",
4840
+ gridTemplateColumns: "repeat(auto-fit, minmax(160px, 1fr))",
4841
+ gap: "0.75rem"
4842
+ }),
4843
+ children: details.map((detail) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
4844
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { style: text.meta, children: detail.label }),
4845
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { style: { margin: "0.125rem 0 0", fontWeight: 600 }, children: detail.value })
4846
+ ] }, detail.label))
4847
+ }
4848
+ )
4849
+ ] })
4850
+ ),
4851
+ /* @__PURE__ */ jsxRuntime.jsx(NozleBranding, {})
4852
+ ] });
4853
+ }
4854
+ var TIER_STYLES = {
4855
+ free: { background: T.muted, color: T.mutedForeground, border: border() },
4856
+ starter: { background: T.primarySoft, color: T.primary, border: border(`color-mix(in srgb, ${T.primary} 30%, transparent)`) },
4857
+ pro: { background: T.primary, color: T.primaryForeground, border: border("transparent") },
4858
+ enterprise: { background: T.accent, color: T.accentForeground, border: border("transparent") }
3915
4859
  };
3916
- function PlanBadge({ plan, tier = "free" }) {
4860
+ function PlanBadge({ plan, tier = "free", appearance }) {
4861
+ const { root } = useAppearance(appearance);
3917
4862
  const style = TIER_STYLES[tier] ?? TIER_STYLES.free;
3918
4863
  return /* @__PURE__ */ jsxRuntime.jsx(
3919
4864
  "span",
3920
4865
  {
3921
- style: {
4866
+ ...root("planBadge", {
4867
+ ...text.label,
3922
4868
  ...style,
3923
4869
  display: "inline-flex",
3924
4870
  alignItems: "center",
3925
4871
  padding: "0.125rem 0.5rem",
3926
4872
  borderRadius: "9999px",
3927
- fontSize: "0.75rem",
3928
- fontWeight: 600,
3929
- lineHeight: "1.25rem",
3930
4873
  whiteSpace: "nowrap"
3931
- },
4874
+ }),
3932
4875
  children: plan
3933
4876
  }
3934
4877
  );
@@ -3945,8 +4888,10 @@ var BRAND_LABELS = {
3945
4888
  function PaymentMethodDisplay({
3946
4889
  paymentMethod,
3947
4890
  onUpdatePaymentMethod,
3948
- updateHref = "/billing/update-payment"
4891
+ updateHref = "/billing/update-payment",
4892
+ appearance
3949
4893
  }) {
4894
+ const { root } = useAppearance(appearance);
3950
4895
  const handleUpdate = () => {
3951
4896
  if (onUpdatePaymentMethod) {
3952
4897
  onUpdatePaymentMethod();
@@ -3954,7 +4899,7 @@ function PaymentMethodDisplay({
3954
4899
  window.location.href = updateHref;
3955
4900
  }
3956
4901
  };
3957
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
4902
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ...root("paymentMethod", { ...surface, display: "flex", flexDirection: "column", gap: "0.75rem" }), children: [
3958
4903
  paymentMethod ? /* @__PURE__ */ jsxRuntime.jsxs(
3959
4904
  "div",
3960
4905
  {
@@ -3963,87 +4908,256 @@ function PaymentMethodDisplay({
3963
4908
  alignItems: "center",
3964
4909
  gap: "0.75rem",
3965
4910
  padding: "0.75rem 1rem",
3966
- border: "1px solid var(--nozle-border, var(--border))",
3967
- borderRadius: "0.5rem",
3968
- fontSize: "0.875rem"
4911
+ border: border(),
4912
+ borderRadius: `calc(${T.radius} * 0.6)`
3969
4913
  },
3970
4914
  children: [
3971
- /* @__PURE__ */ jsxRuntime.jsx(
3972
- "span",
3973
- {
3974
- style: {
3975
- fontWeight: 600,
3976
- color: "var(--nozle-foreground, var(--foreground))"
3977
- },
3978
- children: BRAND_LABELS[paymentMethod.brand.toLowerCase()] ?? paymentMethod.brand
3979
- }
3980
- ),
3981
- /* @__PURE__ */ jsxRuntime.jsxs(
3982
- "span",
3983
- {
3984
- style: {
3985
- color: "var(--nozle-muted-foreground, var(--muted-foreground))"
3986
- },
3987
- children: [
3988
- "\u2022\u2022\u2022\u2022 ",
3989
- paymentMethod.last4
3990
- ]
3991
- }
3992
- ),
3993
- /* @__PURE__ */ jsxRuntime.jsxs(
3994
- "span",
3995
- {
3996
- style: {
3997
- color: "var(--nozle-muted-foreground, var(--muted-foreground))",
3998
- fontSize: "0.75rem"
3999
- },
4000
- children: [
4001
- "Expires ",
4002
- String(paymentMethod.expMonth).padStart(2, "0"),
4003
- "/",
4004
- String(paymentMethod.expYear).slice(-2)
4005
- ]
4006
- }
4007
- )
4915
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 600, color: T.foreground }, children: BRAND_LABELS[paymentMethod.brand.toLowerCase()] ?? paymentMethod.brand }),
4916
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: text.secondary, children: [
4917
+ "\u2022\u2022\u2022\u2022 ",
4918
+ paymentMethod.last4
4919
+ ] }),
4920
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: text.meta, children: [
4921
+ "Expires ",
4922
+ String(paymentMethod.expMonth).padStart(2, "0"),
4923
+ "/",
4924
+ String(paymentMethod.expYear).slice(-2)
4925
+ ] })
4008
4926
  ]
4009
4927
  }
4010
- ) : /* @__PURE__ */ jsxRuntime.jsx(
4011
- "p",
4012
- {
4013
- style: {
4014
- color: "var(--nozle-muted-foreground, var(--muted-foreground))",
4015
- fontSize: "0.875rem"
4016
- },
4017
- children: "No payment method on file."
4018
- }
4019
- ),
4928
+ ) : /* @__PURE__ */ jsxRuntime.jsx("p", { style: { ...text.secondary, margin: 0 }, children: "No payment method on file." }),
4020
4929
  /* @__PURE__ */ jsxRuntime.jsx(
4021
4930
  "button",
4022
4931
  {
4932
+ type: "button",
4023
4933
  onClick: handleUpdate,
4024
4934
  style: {
4025
- background: "transparent",
4026
- border: "1px solid var(--nozle-primary, var(--primary))",
4027
- color: "var(--nozle-primary, var(--primary))",
4935
+ ...buttonStyle("outline"),
4936
+ alignSelf: "flex-start",
4028
4937
  padding: "0.375rem 0.875rem",
4029
- borderRadius: "0.375rem",
4030
- fontSize: "0.875rem",
4031
- fontWeight: 500,
4032
- cursor: "pointer"
4938
+ borderRadius: buttonRadius(`calc(${T.radius} / 2)`)
4033
4939
  },
4034
4940
  children: "Update Payment Method"
4035
4941
  }
4036
4942
  )
4037
4943
  ] });
4038
4944
  }
4945
+ var SOURCE_LABELS = {
4946
+ subscription_grant: "Plan grant",
4947
+ top_up: "Top-up",
4948
+ manual_grant: "Granted",
4949
+ adjustment: "Adjustment",
4950
+ allocated_top_up: "Allocated top-up"
4951
+ };
4952
+ var muted = T.mutedForeground;
4953
+ function date2(value) {
4954
+ const parsed = new Date(value);
4955
+ return Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleDateString("en-US", { dateStyle: "medium" });
4956
+ }
4957
+ function CreditBalance({
4958
+ balance,
4959
+ isLoading = false,
4960
+ error = null,
4961
+ emptyMessage = "No credit balance yet.",
4962
+ appearance
4963
+ }) {
4964
+ const { root, el } = useAppearance(appearance);
4965
+ if (isLoading) return /* @__PURE__ */ jsxRuntime.jsx("div", { ...root("creditBalance", { ...surface, height: 96, background: T.skeleton }), role: "status", "aria-label": "Loading credit balance" });
4966
+ if (error) return /* @__PURE__ */ jsxRuntime.jsx("p", { ...root("creditBalance", { ...surface, color: T.dangerText }), role: "alert", children: typeof error === "string" ? error : error.message });
4967
+ if (!balance) return /* @__PURE__ */ jsxRuntime.jsx("p", { ...root("creditBalance", { ...surface, color: muted }), role: "status", children: emptyMessage });
4968
+ const sources = (balance.sources ?? []).filter((source) => source.remaining !== "0");
4969
+ return /* @__PURE__ */ jsxRuntime.jsxs("section", { "aria-label": balance.credit_system_name, ...root("creditBalance", surface), children: [
4970
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { style: { ...text.heading, margin: 0 }, children: balance.credit_system_name }),
4971
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { style: { margin: "0.25rem 0 0" }, children: [
4972
+ /* @__PURE__ */ jsxRuntime.jsx("span", { ...el("creditBalance__amount", text.metric), children: formatDecimalString(balance.available) }),
4973
+ " ",
4974
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: text.secondary, children: [
4975
+ creditUnitLabel(balance.available, balance.unit_name),
4976
+ " available"
4977
+ ] })
4978
+ ] }),
4979
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { style: { ...text.meta, margin: "0.25rem 0 0" }, children: [
4980
+ "As of ",
4981
+ date2(balance.as_of)
4982
+ ] }),
4983
+ sources.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("ul", { style: { listStyle: "none", padding: 0, margin: "1rem 0 0" }, children: sources.map((source) => /* @__PURE__ */ jsxRuntime.jsxs("li", { style: { display: "flex", justifyContent: "space-between", padding: "0.25rem 0" }, children: [
4984
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: SOURCE_LABELS[source.type] ?? source.type }),
4985
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: text.secondary, children: [
4986
+ formatDecimalString(source.remaining),
4987
+ source.expires_at ? ` \xB7 expires ${date2(source.expires_at)}` : ""
4988
+ ] })
4989
+ ] }, source.id)) })
4990
+ ] });
4991
+ }
4992
+ var TYPE_LABELS = {
4993
+ consume: "Used",
4994
+ grant: "Granted",
4995
+ adjustment: "Adjustment",
4996
+ expire: "Expired",
4997
+ revoke: "Revoked",
4998
+ refund: "Refunded",
4999
+ transfer: "Transfer"
5000
+ };
5001
+ var muted2 = T.mutedForeground;
5002
+ function when(value) {
5003
+ const parsed = new Date(value);
5004
+ return Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short" });
5005
+ }
5006
+ function CreditHistory({
5007
+ operations = [],
5008
+ isLoading = false,
5009
+ error = null,
5010
+ emptyMessage = "No credit activity yet.",
5011
+ hasMore = false,
5012
+ onLoadMore,
5013
+ appearance
5014
+ }) {
5015
+ const { root, el } = useAppearance(appearance);
5016
+ if (error) return /* @__PURE__ */ jsxRuntime.jsx("p", { ...root("creditHistory", { ...surface, color: T.dangerText }), role: "alert", children: typeof error === "string" ? error : error.message });
5017
+ if (isLoading && operations.length === 0) return /* @__PURE__ */ jsxRuntime.jsx("div", { ...root("creditHistory", { ...surface, height: 120, background: T.skeleton }), role: "status", "aria-label": "Loading credit history" });
5018
+ if (operations.length === 0) return /* @__PURE__ */ jsxRuntime.jsx("p", { ...root("creditHistory", { ...surface, color: muted2 }), role: "status", children: emptyMessage });
5019
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ...root("creditHistory", { ...text.body, minWidth: 0 }), children: [
5020
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: table.frame, children: /* @__PURE__ */ jsxRuntime.jsxs("table", { ...el("creditHistory__table", table.table), children: [
5021
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsx("tr", { children: ["When", "Type", "Feature", "Credits", "Status"].map((heading) => /* @__PURE__ */ jsxRuntime.jsx("th", { style: table.head(heading === "Credits"), children: heading }, heading)) }) }),
5022
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: operations.map((operation, index) => {
5023
+ const last = index === operations.length - 1;
5024
+ return /* @__PURE__ */ jsxRuntime.jsxs("tr", { ...el("creditHistory__row"), children: [
5025
+ /* @__PURE__ */ jsxRuntime.jsx("td", { style: table.cell(false, last), children: when(operation.occurred_at) }),
5026
+ /* @__PURE__ */ jsxRuntime.jsx("td", { style: table.cell(false, last), children: TYPE_LABELS[operation.type] ?? operation.type }),
5027
+ /* @__PURE__ */ jsxRuntime.jsx("td", { style: table.cell(false, last), children: operation.feature_code ?? "\u2014" }),
5028
+ /* @__PURE__ */ jsxRuntime.jsx("td", { style: table.cell(true, last), children: formatDecimalString(operation.credit_amount) }),
5029
+ /* @__PURE__ */ jsxRuntime.jsx("td", { style: { ...table.cell(false, last), color: operation.status === "succeeded" ? void 0 : muted2 }, children: operation.status })
5030
+ ] }, operation.id);
5031
+ }) })
5032
+ ] }) }),
5033
+ hasMore && onLoadMore && /* @__PURE__ */ jsxRuntime.jsx(
5034
+ "button",
5035
+ {
5036
+ type: "button",
5037
+ onClick: onLoadMore,
5038
+ disabled: isLoading,
5039
+ style: {
5040
+ ...buttonStyle("secondary", isLoading),
5041
+ marginTop: "0.75rem",
5042
+ padding: "0.5rem 0.875rem",
5043
+ borderRadius: buttonRadius(`calc(${T.radius} / 2)`),
5044
+ opacity: 1
5045
+ },
5046
+ children: isLoading ? "Loading\u2026" : "Load more"
5047
+ }
5048
+ )
5049
+ ] });
5050
+ }
5051
+
5052
+ // src/components/usage/usage-data.ts
5053
+ function formatAmount(amountCents, currency) {
5054
+ const amount = amountCents / 100;
5055
+ try {
5056
+ return new Intl.NumberFormat("en-US", { style: "currency", currency }).format(amount);
5057
+ } catch {
5058
+ return `${currency} ${amount.toFixed(2)}`;
5059
+ }
5060
+ }
5061
+ function formatDay(value) {
5062
+ const parsed = new Date(value);
5063
+ return Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleDateString("en-US", { dateStyle: "medium", timeZone: "UTC" });
5064
+ }
5065
+ var muted3 = T.mutedForeground;
5066
+ function UsageBreakdown({
5067
+ usage,
5068
+ isLoading = false,
5069
+ error = null,
5070
+ emptyMessage = "No usage in this period yet.",
5071
+ appearance
5072
+ }) {
5073
+ const { root, el } = useAppearance(appearance);
5074
+ if (isLoading) return /* @__PURE__ */ jsxRuntime.jsx("div", { ...root("usageBreakdown", { ...surface, height: 120, background: T.skeleton }), role: "status", "aria-label": "Loading usage" });
5075
+ if (error) return /* @__PURE__ */ jsxRuntime.jsx("p", { ...root("usageBreakdown", { ...surface, color: T.dangerText }), role: "alert", children: typeof error === "string" ? error : error.message });
5076
+ if (!usage || usage.charges_usage.length === 0) return /* @__PURE__ */ jsxRuntime.jsx("p", { ...root("usageBreakdown", { ...surface, color: muted3 }), role: "status", children: emptyMessage });
5077
+ const rows = [...usage.charges_usage].sort((a, b) => b.amount_cents - a.amount_cents);
5078
+ return /* @__PURE__ */ jsxRuntime.jsxs("section", { ...root("usageBreakdown", { ...text.body, minWidth: 0 }), children: [
5079
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { style: { ...text.secondary, margin: "0 0 0.5rem" }, children: [
5080
+ formatDay(usage.from_datetime),
5081
+ " to ",
5082
+ formatDay(usage.to_datetime)
5083
+ ] }),
5084
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: table.frame, children: /* @__PURE__ */ jsxRuntime.jsxs("table", { style: table.table, children: [
5085
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
5086
+ /* @__PURE__ */ jsxRuntime.jsx("th", { style: table.head(), children: "Feature" }),
5087
+ /* @__PURE__ */ jsxRuntime.jsx("th", { style: table.head(true), children: "Units" }),
5088
+ /* @__PURE__ */ jsxRuntime.jsx("th", { style: table.head(true), children: "Amount" })
5089
+ ] }) }),
5090
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: rows.map((row, index) => {
5091
+ const last = index === rows.length - 1;
5092
+ return /* @__PURE__ */ jsxRuntime.jsxs("tr", { ...el("usageBreakdown__row"), children: [
5093
+ /* @__PURE__ */ jsxRuntime.jsx("td", { style: table.cell(false, last), children: row.charge.invoice_display_name || row.feature.name }),
5094
+ /* @__PURE__ */ jsxRuntime.jsx("td", { style: table.cell(true, last), children: formatDecimalString(row.units, 6) }),
5095
+ /* @__PURE__ */ jsxRuntime.jsx("td", { style: table.cell(true, last), children: formatAmount(row.amount_cents, row.amount_currency) })
5096
+ ] }, row.charge.nozle_id);
5097
+ }) }),
5098
+ /* @__PURE__ */ jsxRuntime.jsx("tfoot", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
5099
+ /* @__PURE__ */ jsxRuntime.jsx("td", { style: table.foot(), colSpan: 2, children: "Total" }),
5100
+ /* @__PURE__ */ jsxRuntime.jsx("td", { style: table.foot(true), children: formatAmount(usage.amount_cents, usage.currency) })
5101
+ ] }) })
5102
+ ] }) })
5103
+ ] });
5104
+ }
5105
+ var muted4 = T.mutedForeground;
5106
+ var BAR_AREA = 120;
5107
+ function unitsFor(period, featureCode) {
5108
+ return period.charges_usage.filter((charge) => charge.feature.code === featureCode).reduce((sum, charge) => sum + (Number(charge.units) || 0), 0);
5109
+ }
5110
+ function UsageTrend({
5111
+ periods,
5112
+ featureCode,
5113
+ isLoading = false,
5114
+ error = null,
5115
+ emptyMessage = "No past usage yet.",
5116
+ appearance
5117
+ }) {
5118
+ const { root, el } = useAppearance(appearance);
5119
+ if (isLoading) return /* @__PURE__ */ jsxRuntime.jsx("div", { ...root("usageTrend", { ...surface, height: 160, background: T.skeleton }), role: "status", "aria-label": "Loading usage trend" });
5120
+ if (error) return /* @__PURE__ */ jsxRuntime.jsx("p", { ...root("usageTrend", { ...surface, color: T.dangerText }), role: "alert", children: typeof error === "string" ? error : error.message });
5121
+ if (!periods || periods.length === 0) return /* @__PURE__ */ jsxRuntime.jsx("p", { ...root("usageTrend", { ...surface, color: muted4 }), role: "status", children: emptyMessage });
5122
+ const ordered = [...periods].sort((a, b) => Date.parse(a.from_datetime) - Date.parse(b.from_datetime));
5123
+ const points = ordered.map((period) => ({
5124
+ key: period.from_datetime,
5125
+ label: formatDay(period.from_datetime),
5126
+ value: featureCode ? unitsFor(period, featureCode) : period.amount_cents,
5127
+ text: featureCode ? `${unitsFor(period, featureCode).toLocaleString("en-US")} ${featureCode}` : formatAmount(period.amount_cents, period.currency)
5128
+ }));
5129
+ const max = Math.max(...points.map((point) => point.value), 0);
5130
+ return /* @__PURE__ */ jsxRuntime.jsx("figure", { ...root("usageTrend", { ...surface, margin: 0 }), children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", alignItems: "flex-end", gap: "0.5rem", height: BAR_AREA + 40 }, children: points.map((point) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { ...text.meta, flex: 1, minWidth: 0, textAlign: "center" }, children: [
5131
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: point.text }),
5132
+ /* @__PURE__ */ jsxRuntime.jsx(
5133
+ "div",
5134
+ {
5135
+ role: "img",
5136
+ "aria-label": `${point.label}: ${point.text}`,
5137
+ title: `${point.label}: ${point.text}`,
5138
+ ...el("usageTrend__bar", {
5139
+ height: max > 0 ? Math.max(2, point.value / max * BAR_AREA) : 2,
5140
+ marginTop: 4,
5141
+ borderRadius: `min(4px, ${T.radius}) min(4px, ${T.radius}) 0 0`,
5142
+ background: T.primary
5143
+ })
5144
+ }
5145
+ ),
5146
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { marginTop: 4 }, children: point.label })
5147
+ ] }, point.key)) }) });
5148
+ }
4039
5149
 
4040
5150
  exports.BillingContext = BillingContext;
4041
5151
  exports.BillingPortal = BillingPortal;
4042
5152
  exports.BillingPortalError = BillingPortalError;
4043
5153
  exports.BillingProvider = BillingProvider;
5154
+ exports.BillingRequestError = BillingRequestError;
4044
5155
  exports.CancellationControl = CancellationControl;
4045
5156
  exports.Checkout = Checkout;
4046
5157
  exports.CheckoutButton = CheckoutButton;
5158
+ exports.CreditBalance = CreditBalance;
5159
+ exports.CreditHistory = CreditHistory;
5160
+ exports.CurrentPlan = CurrentPlan;
4047
5161
  exports.FeatureGate = FeatureGate;
4048
5162
  exports.LockedOverlay = LockedOverlay;
4049
5163
  exports.PaymentMethodDisplay = PaymentMethodDisplay;
@@ -4052,20 +5166,39 @@ exports.PlanCard = PlanCard;
4052
5166
  exports.PlanChangeControl = PlanChangeControl;
4053
5167
  exports.PlanComparison = PlanComparison;
4054
5168
  exports.PlanGate = PlanGate;
5169
+ exports.PoweredByNozle = PoweredByNozle;
4055
5170
  exports.PricingTable = PricingTable;
4056
5171
  exports.UpgradeButton = UpgradeButton;
4057
5172
  exports.UpgradeModal = UpgradeModal;
4058
5173
  exports.UpgradePrompt = UpgradePrompt;
4059
5174
  exports.UsageAlert = UsageAlert;
5175
+ exports.UsageBreakdown = UsageBreakdown;
4060
5176
  exports.UsageDashboard = UsageDashboard;
4061
5177
  exports.UsageGate = UsageGate;
4062
5178
  exports.UsageMeter = UsageMeter;
5179
+ exports.UsageTrend = UsageTrend;
5180
+ exports.comparisonRows = comparisonRows;
5181
+ exports.currentPlanMeters = currentPlanMeters;
4063
5182
  exports.getUsageMeterColor = getUsageMeterColor;
5183
+ exports.planFeatures = planFeatures;
5184
+ exports.sortPlansByPrice = sortPlansByPrice;
5185
+ exports.usageItems = usageItems;
5186
+ exports.useAppearance = useAppearance;
4064
5187
  exports.useBillingContext = useBillingContext;
5188
+ exports.useBillingRefresh = useBillingRefresh;
4065
5189
  exports.useCheckout = useCheckout;
4066
5190
  exports.useCheckoutSession = useCheckout2;
5191
+ exports.useCreditBalance = useCreditBalance;
5192
+ exports.useCreditHistory = useCreditHistory;
5193
+ exports.useEntitlement = useEntitlement;
5194
+ exports.useInvoiceDownload = useInvoiceDownload;
5195
+ exports.useInvoices = useInvoices;
5196
+ exports.useNozleBranding = useNozleBranding;
4067
5197
  exports.useNozleClient = useNozleClient;
4068
5198
  exports.useOptionalBillingContext = useOptionalBillingContext;
4069
5199
  exports.usePlans = usePlans;
5200
+ exports.useSubscription = useSubscription;
5201
+ exports.useUpgradePreview = useUpgradePreview;
5202
+ exports.useUsage = useUsage;
4070
5203
  //# sourceMappingURL=index.cjs.map
4071
5204
  //# sourceMappingURL=index.cjs.map