@nozle-js/react 0.10.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +76 -1
- package/dist/chunk-NLPONQ5O.js +246 -0
- package/dist/chunk-NLPONQ5O.js.map +1 -0
- package/dist/index.cjs +2192 -1059
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +751 -71
- package/dist/index.d.ts +751 -71
- package/dist/index.js +1802 -930
- package/dist/index.js.map +1 -1
- package/dist/styles/preset.cjs.map +1 -1
- package/dist/styles/preset.d.cts +4 -0
- package/dist/styles/preset.d.ts +4 -0
- package/dist/styles/preset.js.map +1 -1
- package/dist/themes.cjs +230 -0
- package/dist/themes.cjs.map +1 -0
- package/dist/themes.d.cts +27 -0
- package/dist/themes.d.ts +27 -0
- package/dist/themes.js +126 -0
- package/dist/themes.js.map +1 -0
- package/dist/tokens-Bm4VWWb0.d.cts +82 -0
- package/dist/tokens-Bm4VWWb0.d.ts +82 -0
- package/package.json +22 -5
package/dist/index.cjs
CHANGED
|
@@ -1,16 +1,485 @@
|
|
|
1
|
-
'use strict';
|
|
1
|
+
"use client";'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
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
|
|
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
|
|
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
|
|
159
|
-
.nozle-billing-portal h3{font
|
|
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
|
|
162
|
-
.nozle-billing-portal button,.nozle-billing-portal .nzp-link{appearance:none;display:inline-flex;align-items:center;justify-content:center;font
|
|
163
|
-
.nozle-billing-portal button:hover,.nozle-billing-portal .nzp-link:hover{background
|
|
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
|
|
166
|
-
.nozle-billing-portal .nzp-primary{background:var(--nozle-
|
|
167
|
-
.nozle-billing-portal .nzp-primary:hover{filter:brightness(.92);background
|
|
168
|
-
.nozle-billing-portal .nzp-muted{color
|
|
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
|
|
172
|
-
.nozle-billing-portal .nzp-
|
|
173
|
-
.nozle-billing-portal .nzp-
|
|
174
|
-
.nozle-billing-portal .nzp-
|
|
175
|
-
.nozle-billing-portal .nzp-
|
|
176
|
-
.nozle-billing-portal .nzp-
|
|
177
|
-
.nozle-billing-portal
|
|
178
|
-
.nozle-billing-portal
|
|
179
|
-
.nozle-billing-portal
|
|
180
|
-
.nozle-billing-portal
|
|
181
|
-
.nozle-billing-portal
|
|
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
|
|
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
|
|
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
|
|
392
|
-
.nozle-cancellation button{font
|
|
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
|
|
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
|
|
397
|
-
.nozle-cancellation .nzc-dialog h3{margin:0 0 16px;font-
|
|
398
|
-
.nozle-cancellation .nzc-dialog p{margin:12px 0
|
|
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
|
|
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
|
|
433
|
-
const [
|
|
434
|
-
const [
|
|
435
|
-
const [
|
|
436
|
-
const [
|
|
437
|
-
const [
|
|
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 =
|
|
441
|
-
const inFlight =
|
|
442
|
-
const trigger =
|
|
443
|
-
const dialog =
|
|
444
|
-
const titleId =
|
|
445
|
-
const descriptionId =
|
|
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
|
-
|
|
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
|
-
|
|
462
|
-
|
|
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", {
|
|
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 =
|
|
992
|
-
const inFlight =
|
|
993
|
-
const [processing, setProcessing] =
|
|
994
|
-
const [loading, setLoading] =
|
|
995
|
-
const [error, setError] =
|
|
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 =
|
|
1509
|
+
var CheckoutContext = React2.createContext(null);
|
|
1073
1510
|
function useCheckout() {
|
|
1074
|
-
const ctx =
|
|
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
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
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
|
|
1111
|
-
const [
|
|
1112
|
-
const [
|
|
1113
|
-
const
|
|
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", {
|
|
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:
|
|
1152
|
-
background:
|
|
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
|
-
|
|
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
|
|
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
|
|
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", {
|
|
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.
|
|
1236
|
-
|
|
1237
|
-
{
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
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
|
|
1252
|
-
const [
|
|
1253
|
-
const [
|
|
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", {
|
|
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-
|
|
1332
|
-
.nozle-
|
|
1333
|
-
.nozle-
|
|
1334
|
-
.nozle-
|
|
1335
|
-
.nozle-
|
|
1336
|
-
.nozle-
|
|
1337
|
-
.nozle-
|
|
1338
|
-
.nozle-
|
|
1339
|
-
.nozle-
|
|
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] =
|
|
1344
|
-
const [error, setError] =
|
|
1345
|
-
const [confirming, setConfirming] =
|
|
1346
|
-
const inFlight =
|
|
1347
|
-
const dialog =
|
|
1348
|
-
const titleId =
|
|
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
|
-
|
|
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
|
|
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", {
|
|
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
|
-
|
|
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
|
-
|
|
1925
|
+
money3(-props.preview.credit)
|
|
1458
1926
|
] }),
|
|
1459
1927
|
/* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
|
|
1460
1928
|
"New plan charge: ",
|
|
1461
|
-
|
|
1929
|
+
money3(props.preview.debit)
|
|
1462
1930
|
] }),
|
|
1463
1931
|
/* @__PURE__ */ jsxRuntime.jsx("p", { children: /* @__PURE__ */ jsxRuntime.jsxs("strong", { children: [
|
|
1464
1932
|
"Due today: ",
|
|
1465
|
-
|
|
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", {
|
|
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
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
.
|
|
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
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
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
|
|
1562
|
-
return
|
|
2022
|
+
function words(code) {
|
|
2023
|
+
return code.replace(/_/g, " ");
|
|
1563
2024
|
}
|
|
1564
|
-
function
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
}
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
)
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
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] =
|
|
1589
|
-
const
|
|
1590
|
-
const
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
const
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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", {
|
|
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.
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
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
|
-
|
|
1979
|
-
|
|
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(
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
plan.
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
"
|
|
2001
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
2079
|
-
const [
|
|
2080
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2140
|
-
const [attempt, setAttempt] =
|
|
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] =
|
|
2143
|
-
const callback =
|
|
2812
|
+
const [state, setState] = React2.useState();
|
|
2813
|
+
const callback = React2.useRef(onError);
|
|
2144
2814
|
callback.current = onError;
|
|
2145
|
-
|
|
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] =
|
|
2168
|
-
const [error, setError] =
|
|
2169
|
-
const controller =
|
|
2170
|
-
|
|
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(
|
|
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
|
|
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 } =
|
|
2906
|
+
const { data, error, retry } = useQuery4(
|
|
2237
2907
|
client,
|
|
2238
2908
|
identityQuery,
|
|
2239
2909
|
{},
|
|
2240
2910
|
onError
|
|
2241
2911
|
);
|
|
2242
|
-
const [view, setView] =
|
|
2243
|
-
const [notice, setNotice] =
|
|
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(
|
|
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",
|
|
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
|
|
2395
|
-
const [
|
|
2396
|
-
const
|
|
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
|
-
|
|
2399
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("section",
|
|
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
|
-
|
|
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
|
-
|
|
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("
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
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
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
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 } =
|
|
3205
|
+
const { data, error, retry } = useQuery4(client, usageQuery, { subscriptionId: subscription.id }, onError);
|
|
2532
3206
|
const usage = data?.customerPortalCustomerUsage;
|
|
2533
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("section",
|
|
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:
|
|
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(
|
|
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:
|
|
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] =
|
|
2565
|
-
const [filter, setFilter] =
|
|
2566
|
-
const { data, error, retry } =
|
|
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",
|
|
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
|
-
"
|
|
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(
|
|
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:
|
|
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] =
|
|
2633
|
-
const [generating, setGenerating] =
|
|
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] =
|
|
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",
|
|
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] =
|
|
2752
|
-
const { data, error, retry } =
|
|
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",
|
|
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",
|
|
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:
|
|
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] =
|
|
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",
|
|
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 ?
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2872
|
-
const {
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
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
|
-
}
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
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] =
|
|
2907
|
-
const [error, setError] =
|
|
2908
|
-
const [checkout, setCheckout] =
|
|
2909
|
-
const fetchClientSecret =
|
|
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
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
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 ?
|
|
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", {
|
|
2997
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => onChange(false),
|
|
2998
|
-
/* @__PURE__ */ jsxRuntime.
|
|
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] =
|
|
3019
|
-
const
|
|
3020
|
-
const
|
|
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
|
-
|
|
3909
|
+
...el("planCard", {
|
|
3035
3910
|
position: "relative",
|
|
3036
3911
|
display: "flex",
|
|
3037
3912
|
flexDirection: "column",
|
|
3038
|
-
background: C.cardBg,
|
|
3039
|
-
|
|
3040
|
-
|
|
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", {
|
|
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:
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
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:
|
|
3063
|
-
isCurrent && !isHighlighted && /* @__PURE__ */ jsxRuntime.jsx("div", {
|
|
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
|
-
|
|
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", {
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
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", {
|
|
3094
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: {
|
|
3095
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: {
|
|
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", {
|
|
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", {
|
|
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
|
-
|
|
3110
|
-
color:
|
|
3111
|
-
|
|
3112
|
-
|
|
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
|
-
|
|
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] =
|
|
3137
|
-
const
|
|
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
|
-
|
|
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:
|
|
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:
|
|
3193
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 40, width: "50%", borderRadius: 6, background:
|
|
3194
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 14, width: "80%", borderRadius: 6, background:
|
|
3195
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 14, width: "70%", borderRadius: 6, background:
|
|
3196
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 14, width: "60%", borderRadius: 6, background:
|
|
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
|
-
|
|
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
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
const
|
|
3220
|
-
const
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
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
|
|
3277
|
-
const
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
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", {
|
|
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
|
|
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
|
-
|
|
3308
|
-
|
|
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
|
|
3319
|
-
const
|
|
3320
|
-
|
|
3321
|
-
|
|
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
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
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
|
-
|
|
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.
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
"table",
|
|
3467
|
-
|
|
3468
|
-
style: {
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
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] =
|
|
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
|
|
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
|
-
|
|
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", {
|
|
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
|
-
|
|
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:
|
|
3744
|
-
|
|
3745
|
-
|
|
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
|
|
3763
|
-
if (percentage >= 80) return
|
|
3764
|
-
return
|
|
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
|
|
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(
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
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
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
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", {
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
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", {
|
|
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
|
|
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", {
|
|
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
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
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
|
-
|
|
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", {
|
|
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:
|
|
3967
|
-
borderRadius:
|
|
3968
|
-
fontSize: "0.875rem"
|
|
4911
|
+
border: border(),
|
|
4912
|
+
borderRadius: `calc(${T.radius} * 0.6)`
|
|
3969
4913
|
},
|
|
3970
4914
|
children: [
|
|
3971
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
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
|
-
|
|
4026
|
-
|
|
4027
|
-
color: "var(--nozle-primary, var(--primary))",
|
|
4935
|
+
...buttonStyle("outline"),
|
|
4936
|
+
alignSelf: "flex-start",
|
|
4028
4937
|
padding: "0.375rem 0.875rem",
|
|
4029
|
-
borderRadius:
|
|
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
|