@nozle-js/react 0.10.2 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/chunk-NLPONQ5O.js +246 -0
- package/dist/chunk-NLPONQ5O.js.map +1 -0
- package/dist/index.cjs +2192 -1060
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +716 -69
- package/dist/index.d.ts +716 -69
- package/dist/index.js +1802 -931
- package/dist/index.js.map +1 -1
- package/dist/styles/preset.cjs.map +1 -1
- package/dist/styles/preset.d.cts +4 -0
- package/dist/styles/preset.d.ts +4 -0
- package/dist/styles/preset.js.map +1 -1
- package/dist/themes.cjs +230 -0
- package/dist/themes.cjs.map +1 -0
- package/dist/themes.d.cts +27 -0
- package/dist/themes.d.ts +27 -0
- package/dist/themes.js +126 -0
- package/dist/themes.js.map +1 -0
- package/dist/tokens-Bm4VWWb0.d.cts +82 -0
- package/dist/tokens-Bm4VWWb0.d.ts +82 -0
- package/package.json +22 -6
package/dist/index.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,42 +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 (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
3234
|
-
const data = await res.json();
|
|
3235
|
-
if (!cancelled) setPlans(data.plans || []);
|
|
3236
|
-
}).catch((cause) => {
|
|
3237
|
-
if (!cancelled) setCheckoutError(`Plans could not be loaded: ${cause instanceof Error ? cause.message : "unknown error"}`);
|
|
3238
|
-
}).finally(() => {
|
|
3239
|
-
if (!cancelled) setLoading(false);
|
|
3240
|
-
});
|
|
3241
|
-
return () => {
|
|
3242
|
-
cancelled = true;
|
|
3243
|
-
};
|
|
3244
|
-
}, [client, propPlans]);
|
|
3245
|
-
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(() => {
|
|
3246
4069
|
if (currentPlanCode !== void 0) setCurrentPlan(currentPlanCode);
|
|
3247
4070
|
}, [currentPlanCode]);
|
|
3248
4071
|
async function selectPlan(plan) {
|
|
@@ -3274,124 +4097,123 @@ function PricingTable({
|
|
|
3274
4097
|
onCheckoutError?.(error);
|
|
3275
4098
|
}
|
|
3276
4099
|
}
|
|
3277
|
-
const
|
|
3278
|
-
const
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
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 }),
|
|
3286
4108
|
processing && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", children: "Payment is processing. Confirmation may take a little while." }),
|
|
3287
|
-
checkoutError && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", children: checkoutError }),
|
|
3288
|
-
/* @__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", {
|
|
3289
4111
|
display: "grid",
|
|
3290
4112
|
gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
|
|
3291
4113
|
gap: "1.5rem"
|
|
3292
|
-
}, children: filtered.map((plan, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
4114
|
+
}), children: filtered.map((plan, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3293
4115
|
Card,
|
|
3294
4116
|
{
|
|
3295
4117
|
plan,
|
|
3296
|
-
features: features
|
|
4118
|
+
features: featureLines(features, plan, i),
|
|
3297
4119
|
isCurrent: currentPlan === plan.code,
|
|
4120
|
+
isUpgrade: current && current.amount_currency === plan.amount_currency ? plan.amount_cents > current.amount_cents : void 0,
|
|
3298
4121
|
isHighlighted: highlightPlan === plan.code,
|
|
4122
|
+
filled: highlightStyle === "filled",
|
|
4123
|
+
highlightLabel,
|
|
3299
4124
|
isEnt: isEnterprise(plan),
|
|
3300
4125
|
enterpriseEmail,
|
|
3301
|
-
onSelect: () => void selectPlan(plan)
|
|
4126
|
+
onSelect: () => void selectPlan(plan),
|
|
4127
|
+
el,
|
|
4128
|
+
panel: planCardLayout === "panel"
|
|
3302
4129
|
},
|
|
3303
4130
|
plan.code
|
|
3304
|
-
)) })
|
|
4131
|
+
)) }),
|
|
4132
|
+
/* @__PURE__ */ jsxRuntime.jsx(NozleBranding, {})
|
|
3305
4133
|
] });
|
|
3306
4134
|
}
|
|
3307
4135
|
function PlanCard({
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
monthlyPrice,
|
|
3311
|
-
annualPrice,
|
|
3312
|
-
features,
|
|
3313
|
-
description,
|
|
3314
|
-
isAnnual,
|
|
4136
|
+
plan,
|
|
4137
|
+
features: featureOverride,
|
|
3315
4138
|
isCurrent,
|
|
3316
4139
|
returnUrl,
|
|
3317
|
-
onSelect
|
|
4140
|
+
onSelect,
|
|
4141
|
+
appearance
|
|
3318
4142
|
}) {
|
|
3319
|
-
const
|
|
3320
|
-
const
|
|
3321
|
-
|
|
3322
|
-
|
|
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",
|
|
3323
4186
|
{
|
|
3324
4187
|
"data-testid": `plan-card-${id}`,
|
|
3325
4188
|
"aria-current": isCurrent ? "true" : void 0,
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
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,
|
|
3330
4196
|
padding: "1.5rem",
|
|
3331
4197
|
display: "flex",
|
|
3332
4198
|
flexDirection: "column",
|
|
3333
4199
|
gap: "1rem"
|
|
3334
|
-
},
|
|
3335
|
-
className: isCurrent ? "border-primary" : "",
|
|
4200
|
+
}),
|
|
3336
4201
|
children: [
|
|
3337
4202
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
3338
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3339
|
-
|
|
3340
|
-
{
|
|
3341
|
-
style: {
|
|
3342
|
-
fontSize: "1.25rem",
|
|
3343
|
-
fontWeight: 600,
|
|
3344
|
-
color: "var(--nozle-foreground, var(--foreground))",
|
|
3345
|
-
margin: 0
|
|
3346
|
-
},
|
|
3347
|
-
children: name
|
|
3348
|
-
}
|
|
3349
|
-
),
|
|
3350
|
-
description && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3351
|
-
"p",
|
|
3352
|
-
{
|
|
3353
|
-
style: {
|
|
3354
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))",
|
|
3355
|
-
fontSize: "0.875rem",
|
|
3356
|
-
margin: "0.25rem 0 0"
|
|
3357
|
-
},
|
|
3358
|
-
children: description
|
|
3359
|
-
}
|
|
3360
|
-
)
|
|
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 })
|
|
3361
4205
|
] }),
|
|
3362
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
3363
|
-
/* @__PURE__ */ jsxRuntime.
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
fontWeight: 700,
|
|
3369
|
-
color: "var(--nozle-foreground, var(--foreground))"
|
|
3370
|
-
},
|
|
3371
|
-
children: [
|
|
3372
|
-
"$",
|
|
3373
|
-
price
|
|
3374
|
-
]
|
|
3375
|
-
}
|
|
3376
|
-
),
|
|
3377
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3378
|
-
"span",
|
|
3379
|
-
{
|
|
3380
|
-
style: {
|
|
3381
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))",
|
|
3382
|
-
marginLeft: "0.25rem"
|
|
3383
|
-
},
|
|
3384
|
-
children: [
|
|
3385
|
-
"/",
|
|
3386
|
-
interval
|
|
3387
|
-
]
|
|
3388
|
-
}
|
|
3389
|
-
)
|
|
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
|
+
] })
|
|
3390
4212
|
] }),
|
|
3391
4213
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3392
4214
|
"ul",
|
|
3393
4215
|
{
|
|
3394
|
-
|
|
4216
|
+
...el("planCard__features", {
|
|
3395
4217
|
listStyle: "none",
|
|
3396
4218
|
padding: 0,
|
|
3397
4219
|
margin: 0,
|
|
@@ -3399,160 +4221,51 @@ function PlanCard({
|
|
|
3399
4221
|
flexDirection: "column",
|
|
3400
4222
|
gap: "0.5rem",
|
|
3401
4223
|
flex: 1
|
|
3402
|
-
},
|
|
3403
|
-
children: features.map((feature) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3404
|
-
"
|
|
3405
|
-
{
|
|
3406
|
-
style: {
|
|
3407
|
-
display: "flex",
|
|
3408
|
-
alignItems: "center",
|
|
3409
|
-
gap: "0.5rem",
|
|
3410
|
-
color: "var(--nozle-foreground, var(--foreground))",
|
|
3411
|
-
fontSize: "0.875rem"
|
|
3412
|
-
},
|
|
3413
|
-
children: [
|
|
3414
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3415
|
-
"span",
|
|
3416
|
-
{
|
|
3417
|
-
style: { color: "var(--nozle-primary, var(--primary))" },
|
|
3418
|
-
"aria-hidden": "true",
|
|
3419
|
-
children: "\u2713"
|
|
3420
|
-
}
|
|
3421
|
-
),
|
|
3422
|
-
feature
|
|
3423
|
-
]
|
|
3424
|
-
},
|
|
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" }),
|
|
3425
4227
|
feature
|
|
3426
|
-
))
|
|
4228
|
+
] }, feature))
|
|
3427
4229
|
}
|
|
3428
4230
|
),
|
|
3429
|
-
|
|
3430
|
-
"button",
|
|
3431
|
-
{
|
|
3432
|
-
onClick: () => onSelect?.({
|
|
3433
|
-
planCode: id,
|
|
3434
|
-
returnUrl: returnUrl ?? window.location.href
|
|
3435
|
-
}),
|
|
3436
|
-
style: {
|
|
3437
|
-
background: isCurrent ? "var(--nozle-muted, var(--muted))" : "var(--nozle-primary, var(--primary))",
|
|
3438
|
-
color: isCurrent ? "var(--nozle-muted-foreground, var(--muted-foreground))" : "var(--nozle-primary-foreground, var(--primary-foreground))",
|
|
3439
|
-
border: "none",
|
|
3440
|
-
borderRadius: "var(--nozle-radius, 0.5rem)",
|
|
3441
|
-
padding: "0.75rem 1.5rem",
|
|
3442
|
-
cursor: isCurrent ? "default" : "pointer",
|
|
3443
|
-
fontWeight: 500,
|
|
3444
|
-
width: "100%"
|
|
3445
|
-
},
|
|
3446
|
-
disabled: isCurrent,
|
|
3447
|
-
"aria-label": isCurrent ? `Current plan: ${name}` : `Select ${name}`,
|
|
3448
|
-
children: isCurrent ? "Current Plan" : "Get Started"
|
|
3449
|
-
}
|
|
3450
|
-
)
|
|
4231
|
+
cta
|
|
3451
4232
|
]
|
|
3452
4233
|
}
|
|
3453
4234
|
);
|
|
3454
4235
|
}
|
|
3455
4236
|
function PlanComparison({
|
|
3456
|
-
plans,
|
|
3457
|
-
features
|
|
4237
|
+
plans: catalogPlans,
|
|
4238
|
+
features: featureOverride,
|
|
4239
|
+
sort,
|
|
4240
|
+
appearance
|
|
3458
4241
|
}) {
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
"table",
|
|
3468
|
-
|
|
3469
|
-
style: {
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
},
|
|
3487
|
-
children: "Feature"
|
|
3488
|
-
}
|
|
3489
|
-
),
|
|
3490
|
-
plans.map((plan) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3491
|
-
"th",
|
|
3492
|
-
{
|
|
3493
|
-
style: {
|
|
3494
|
-
padding: "0.75rem 1rem",
|
|
3495
|
-
textAlign: "center",
|
|
3496
|
-
borderBottom: "1px solid var(--nozle-border, var(--border))",
|
|
3497
|
-
fontWeight: 600,
|
|
3498
|
-
color: "var(--nozle-foreground, var(--foreground))"
|
|
3499
|
-
},
|
|
3500
|
-
children: plan.name
|
|
3501
|
-
},
|
|
3502
|
-
plan.id
|
|
3503
|
-
))
|
|
3504
|
-
] }) }),
|
|
3505
|
-
/* @__PURE__ */ jsxRuntime.jsx("tbody", { children: features.map((feature) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
|
|
3506
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3507
|
-
"td",
|
|
3508
|
-
{
|
|
3509
|
-
style: {
|
|
3510
|
-
padding: "0.75rem 1rem",
|
|
3511
|
-
borderBottom: "1px solid var(--nozle-border, var(--border))",
|
|
3512
|
-
color: "var(--nozle-foreground, var(--foreground))"
|
|
3513
|
-
},
|
|
3514
|
-
children: feature.label
|
|
3515
|
-
}
|
|
3516
|
-
),
|
|
3517
|
-
plans.map((plan) => {
|
|
3518
|
-
const value = feature.values[plan.id];
|
|
3519
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3520
|
-
"td",
|
|
3521
|
-
{
|
|
3522
|
-
style: {
|
|
3523
|
-
padding: "0.75rem 1rem",
|
|
3524
|
-
textAlign: "center",
|
|
3525
|
-
borderBottom: "1px solid var(--nozle-border, var(--border))"
|
|
3526
|
-
},
|
|
3527
|
-
children: typeof value === "boolean" ? value ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
3528
|
-
"span",
|
|
3529
|
-
{
|
|
3530
|
-
style: {
|
|
3531
|
-
color: "var(--nozle-primary, var(--primary))"
|
|
3532
|
-
},
|
|
3533
|
-
"aria-label": "Included",
|
|
3534
|
-
children: "\u2713"
|
|
3535
|
-
}
|
|
3536
|
-
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
3537
|
-
"span",
|
|
3538
|
-
{
|
|
3539
|
-
style: {
|
|
3540
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))"
|
|
3541
|
-
},
|
|
3542
|
-
"aria-label": "Not included",
|
|
3543
|
-
children: "\u2715"
|
|
3544
|
-
}
|
|
3545
|
-
) : /* @__PURE__ */ jsxRuntime.jsx("span", { children: value ?? "\u2014" })
|
|
3546
|
-
},
|
|
3547
|
-
plan.id
|
|
3548
|
-
);
|
|
3549
|
-
})
|
|
3550
|
-
] }, feature.key)) })
|
|
3551
|
-
]
|
|
3552
|
-
}
|
|
3553
|
-
)
|
|
3554
|
-
}
|
|
3555
|
-
);
|
|
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
|
+
] });
|
|
3556
4269
|
}
|
|
3557
4270
|
function UpgradeButton({
|
|
3558
4271
|
planCode,
|
|
@@ -3568,26 +4281,18 @@ function UpgradeButton({
|
|
|
3568
4281
|
onCheckoutStarted,
|
|
3569
4282
|
onDowngradeScheduled,
|
|
3570
4283
|
onUpgraded,
|
|
3571
|
-
onError
|
|
4284
|
+
onError,
|
|
4285
|
+
appearance
|
|
3572
4286
|
}) {
|
|
3573
|
-
const [isOpen, setIsOpen] =
|
|
4287
|
+
const [isOpen, setIsOpen] = React2.useState(false);
|
|
4288
|
+
const { root } = useAppearance(appearance);
|
|
3574
4289
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3575
4290
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3576
4291
|
"button",
|
|
3577
4292
|
{
|
|
3578
4293
|
type: "button",
|
|
3579
4294
|
onClick: () => setIsOpen(true),
|
|
3580
|
-
className,
|
|
3581
|
-
style: {
|
|
3582
|
-
padding: "0.75rem 1.5rem",
|
|
3583
|
-
borderRadius: "var(--nozle-radius, 0.5rem)",
|
|
3584
|
-
border: "none",
|
|
3585
|
-
background: "var(--nozle-primary, var(--primary))",
|
|
3586
|
-
color: "var(--nozle-primary-foreground, var(--primary-foreground))",
|
|
3587
|
-
cursor: "pointer",
|
|
3588
|
-
fontWeight: 500,
|
|
3589
|
-
...style
|
|
3590
|
-
},
|
|
4295
|
+
...root("upgradeButton", { ...buttonStyle(), ...style }, className),
|
|
3591
4296
|
children: label
|
|
3592
4297
|
}
|
|
3593
4298
|
),
|
|
@@ -3595,6 +4300,7 @@ function UpgradeButton({
|
|
|
3595
4300
|
UpgradeModal,
|
|
3596
4301
|
{
|
|
3597
4302
|
isOpen,
|
|
4303
|
+
appearance,
|
|
3598
4304
|
planCode,
|
|
3599
4305
|
returnUrl,
|
|
3600
4306
|
subscriptionId,
|
|
@@ -3653,11 +4359,13 @@ function PlanGate({
|
|
|
3653
4359
|
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
|
|
3654
4360
|
}
|
|
3655
4361
|
function UpgradePrompt({
|
|
3656
|
-
text = "Upgrade to access this feature",
|
|
4362
|
+
text: text2 = "Upgrade to access this feature",
|
|
3657
4363
|
planName: planName2,
|
|
3658
|
-
onUpgrade
|
|
4364
|
+
onUpgrade,
|
|
4365
|
+
appearance
|
|
3659
4366
|
}) {
|
|
3660
|
-
const
|
|
4367
|
+
const { root } = useAppearance(appearance);
|
|
4368
|
+
const displayText = planName2 ? `Upgrade to ${planName2} to access this feature` : text2;
|
|
3661
4369
|
function handleUpgrade() {
|
|
3662
4370
|
if (onUpgrade) {
|
|
3663
4371
|
onUpgrade();
|
|
@@ -3668,40 +4376,23 @@ function UpgradePrompt({
|
|
|
3668
4376
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3669
4377
|
"div",
|
|
3670
4378
|
{
|
|
3671
|
-
|
|
4379
|
+
...root("upgradePrompt", {
|
|
3672
4380
|
display: "flex",
|
|
3673
4381
|
flexDirection: "column",
|
|
3674
4382
|
alignItems: "center",
|
|
3675
4383
|
gap: "0.75rem",
|
|
3676
4384
|
padding: "1rem",
|
|
3677
|
-
textAlign: "center"
|
|
3678
|
-
|
|
4385
|
+
textAlign: "center",
|
|
4386
|
+
...text.body
|
|
4387
|
+
}),
|
|
3679
4388
|
children: [
|
|
3680
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3681
|
-
"p",
|
|
3682
|
-
{
|
|
3683
|
-
style: {
|
|
3684
|
-
color: "var(--nozle-foreground, var(--foreground))",
|
|
3685
|
-
fontSize: "0.875rem",
|
|
3686
|
-
margin: 0
|
|
3687
|
-
},
|
|
3688
|
-
children: displayText
|
|
3689
|
-
}
|
|
3690
|
-
),
|
|
4389
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { style: { margin: 0 }, children: displayText }),
|
|
3691
4390
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3692
4391
|
"button",
|
|
3693
4392
|
{
|
|
4393
|
+
type: "button",
|
|
3694
4394
|
onClick: handleUpgrade,
|
|
3695
|
-
style: {
|
|
3696
|
-
padding: "0.5rem 1.25rem",
|
|
3697
|
-
borderRadius: "var(--nozle-radius, 0.5rem)",
|
|
3698
|
-
border: "none",
|
|
3699
|
-
background: "var(--nozle-primary, var(--primary))",
|
|
3700
|
-
color: "var(--nozle-primary-foreground, var(--primary-foreground))",
|
|
3701
|
-
cursor: "pointer",
|
|
3702
|
-
fontWeight: 500,
|
|
3703
|
-
fontSize: "0.875rem"
|
|
3704
|
-
},
|
|
4395
|
+
style: { ...buttonStyle(), padding: "0.5rem 1.25rem" },
|
|
3705
4396
|
children: "Upgrade"
|
|
3706
4397
|
}
|
|
3707
4398
|
)
|
|
@@ -3714,12 +4405,14 @@ function LockedOverlay({
|
|
|
3714
4405
|
upgradeText,
|
|
3715
4406
|
upgradePlanName,
|
|
3716
4407
|
onUpgrade,
|
|
3717
|
-
children
|
|
4408
|
+
children,
|
|
4409
|
+
appearance
|
|
3718
4410
|
}) {
|
|
4411
|
+
const { root, el } = useAppearance(appearance);
|
|
3719
4412
|
if (!locked) {
|
|
3720
4413
|
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
|
|
3721
4414
|
}
|
|
3722
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", {
|
|
4415
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ...root("lockedOverlay", { position: "relative", display: "inline-block" }), children: [
|
|
3723
4416
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3724
4417
|
"div",
|
|
3725
4418
|
{
|
|
@@ -3735,16 +4428,15 @@ function LockedOverlay({
|
|
|
3735
4428
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3736
4429
|
"div",
|
|
3737
4430
|
{
|
|
3738
|
-
|
|
4431
|
+
...el("lockedOverlay__panel", {
|
|
3739
4432
|
position: "absolute",
|
|
3740
4433
|
inset: 0,
|
|
3741
4434
|
display: "flex",
|
|
3742
4435
|
alignItems: "center",
|
|
3743
4436
|
justifyContent: "center",
|
|
3744
|
-
background:
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
},
|
|
4437
|
+
background: `color-mix(in srgb, ${T.background} 92%, transparent)`,
|
|
4438
|
+
borderRadius: T.radius
|
|
4439
|
+
}),
|
|
3748
4440
|
role: "dialog",
|
|
3749
4441
|
"aria-label": "Feature locked \u2014 upgrade required",
|
|
3750
4442
|
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -3752,7 +4444,8 @@ function LockedOverlay({
|
|
|
3752
4444
|
{
|
|
3753
4445
|
text: upgradeText,
|
|
3754
4446
|
planName: upgradePlanName,
|
|
3755
|
-
onUpgrade
|
|
4447
|
+
onUpgrade,
|
|
4448
|
+
appearance
|
|
3756
4449
|
}
|
|
3757
4450
|
)
|
|
3758
4451
|
}
|
|
@@ -3760,69 +4453,110 @@ function LockedOverlay({
|
|
|
3760
4453
|
] });
|
|
3761
4454
|
}
|
|
3762
4455
|
function getUsageMeterColor(percentage) {
|
|
3763
|
-
if (percentage >= 95) return
|
|
3764
|
-
if (percentage >= 80) return
|
|
3765
|
-
return
|
|
4456
|
+
if (percentage >= 95) return T.danger;
|
|
4457
|
+
if (percentage >= 80) return T.warning;
|
|
4458
|
+
return T.primary;
|
|
3766
4459
|
}
|
|
3767
4460
|
function UsageMeter({
|
|
3768
4461
|
used,
|
|
3769
4462
|
limit,
|
|
3770
4463
|
label,
|
|
3771
|
-
showText = true
|
|
4464
|
+
showText = true,
|
|
4465
|
+
appearance
|
|
3772
4466
|
}) {
|
|
3773
|
-
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;
|
|
3774
4470
|
const color = getUsageMeterColor(percentage);
|
|
3775
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
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(
|
|
3790
4491
|
"div",
|
|
3791
4492
|
{
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
}
|
|
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
|
+
)
|
|
3798
4511
|
}
|
|
3799
4512
|
)
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
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
|
+
});
|
|
3803
4523
|
}
|
|
3804
4524
|
function UsageDashboard({
|
|
3805
4525
|
features = [],
|
|
3806
|
-
loading = false
|
|
4526
|
+
loading = false,
|
|
4527
|
+
error = null,
|
|
4528
|
+
emptyMessage = "No usage to show yet.",
|
|
4529
|
+
appearance
|
|
3807
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
|
+
});
|
|
3808
4541
|
if (loading) {
|
|
3809
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", {
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
)) });
|
|
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 });
|
|
3817
4549
|
}
|
|
3818
|
-
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)) });
|
|
3819
4551
|
}
|
|
3820
4552
|
function UsageAlert({
|
|
3821
4553
|
features,
|
|
3822
4554
|
threshold = 80,
|
|
3823
|
-
upgradeHref = "/plans"
|
|
4555
|
+
upgradeHref = "/plans",
|
|
4556
|
+
appearance
|
|
3824
4557
|
}) {
|
|
3825
|
-
const
|
|
4558
|
+
const { root, el } = useAppearance(appearance);
|
|
4559
|
+
const [dismissed, setDismissed] = React2.useState(/* @__PURE__ */ new Set());
|
|
3826
4560
|
const alertFeatures = features.filter(
|
|
3827
4561
|
(f) => f.percentage >= threshold && !dismissed.has(f.key)
|
|
3828
4562
|
);
|
|
@@ -3832,15 +4566,19 @@ function UsageAlert({
|
|
|
3832
4566
|
const handleDismiss = (key) => {
|
|
3833
4567
|
setDismissed((prev) => new Set(prev).add(key));
|
|
3834
4568
|
};
|
|
3835
|
-
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(
|
|
3836
4570
|
"div",
|
|
3837
4571
|
{
|
|
3838
4572
|
role: "alert",
|
|
3839
|
-
className: "flex items-center justify-between rounded-md px-4 py-3 text-sm",
|
|
3840
4573
|
style: {
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
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)
|
|
3844
4582
|
},
|
|
3845
4583
|
children: [
|
|
3846
4584
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
@@ -3863,8 +4601,8 @@ function UsageAlert({
|
|
|
3863
4601
|
{
|
|
3864
4602
|
href: upgradeHref,
|
|
3865
4603
|
style: {
|
|
4604
|
+
...text.action,
|
|
3866
4605
|
color: "inherit",
|
|
3867
|
-
fontWeight: 600,
|
|
3868
4606
|
textDecoration: "underline"
|
|
3869
4607
|
},
|
|
3870
4608
|
children: "Upgrade"
|
|
@@ -3874,16 +4612,17 @@ function UsageAlert({
|
|
|
3874
4612
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3875
4613
|
"button",
|
|
3876
4614
|
{
|
|
4615
|
+
type: "button",
|
|
3877
4616
|
onClick: () => handleDismiss(f.key),
|
|
3878
4617
|
"aria-label": `Dismiss ${f.label} alert`,
|
|
3879
|
-
|
|
4618
|
+
...el("usageAlert__dismiss", {
|
|
3880
4619
|
background: "none",
|
|
3881
4620
|
border: "none",
|
|
3882
4621
|
cursor: "pointer",
|
|
3883
4622
|
padding: "0 0 0 0.75rem",
|
|
3884
|
-
|
|
4623
|
+
...text.action,
|
|
3885
4624
|
color: "inherit"
|
|
3886
|
-
},
|
|
4625
|
+
}),
|
|
3887
4626
|
children: "\xD7"
|
|
3888
4627
|
}
|
|
3889
4628
|
)
|
|
@@ -3892,44 +4631,247 @@ function UsageAlert({
|
|
|
3892
4631
|
f.key
|
|
3893
4632
|
)) });
|
|
3894
4633
|
}
|
|
3895
|
-
var
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
pro: {
|
|
3907
|
-
background: "var(--nozle-primary, var(--primary, #3b82f6))",
|
|
3908
|
-
color: "var(--nozle-primary-foreground, var(--primary-foreground, #ffffff))",
|
|
3909
|
-
border: "1px solid transparent"
|
|
3910
|
-
},
|
|
3911
|
-
enterprise: {
|
|
3912
|
-
background: "var(--nozle-accent, var(--accent, #8b5cf6))",
|
|
3913
|
-
color: "var(--nozle-accent-foreground, var(--accent-foreground, #ffffff))",
|
|
3914
|
-
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 };
|
|
3915
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") }
|
|
3916
4859
|
};
|
|
3917
|
-
function PlanBadge({ plan, tier = "free" }) {
|
|
4860
|
+
function PlanBadge({ plan, tier = "free", appearance }) {
|
|
4861
|
+
const { root } = useAppearance(appearance);
|
|
3918
4862
|
const style = TIER_STYLES[tier] ?? TIER_STYLES.free;
|
|
3919
4863
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3920
4864
|
"span",
|
|
3921
4865
|
{
|
|
3922
|
-
|
|
4866
|
+
...root("planBadge", {
|
|
4867
|
+
...text.label,
|
|
3923
4868
|
...style,
|
|
3924
4869
|
display: "inline-flex",
|
|
3925
4870
|
alignItems: "center",
|
|
3926
4871
|
padding: "0.125rem 0.5rem",
|
|
3927
4872
|
borderRadius: "9999px",
|
|
3928
|
-
fontSize: "0.75rem",
|
|
3929
|
-
fontWeight: 600,
|
|
3930
|
-
lineHeight: "1.25rem",
|
|
3931
4873
|
whiteSpace: "nowrap"
|
|
3932
|
-
},
|
|
4874
|
+
}),
|
|
3933
4875
|
children: plan
|
|
3934
4876
|
}
|
|
3935
4877
|
);
|
|
@@ -3946,8 +4888,10 @@ var BRAND_LABELS = {
|
|
|
3946
4888
|
function PaymentMethodDisplay({
|
|
3947
4889
|
paymentMethod,
|
|
3948
4890
|
onUpdatePaymentMethod,
|
|
3949
|
-
updateHref = "/billing/update-payment"
|
|
4891
|
+
updateHref = "/billing/update-payment",
|
|
4892
|
+
appearance
|
|
3950
4893
|
}) {
|
|
4894
|
+
const { root } = useAppearance(appearance);
|
|
3951
4895
|
const handleUpdate = () => {
|
|
3952
4896
|
if (onUpdatePaymentMethod) {
|
|
3953
4897
|
onUpdatePaymentMethod();
|
|
@@ -3955,7 +4899,7 @@ function PaymentMethodDisplay({
|
|
|
3955
4899
|
window.location.href = updateHref;
|
|
3956
4900
|
}
|
|
3957
4901
|
};
|
|
3958
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", {
|
|
4902
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ...root("paymentMethod", { ...surface, display: "flex", flexDirection: "column", gap: "0.75rem" }), children: [
|
|
3959
4903
|
paymentMethod ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3960
4904
|
"div",
|
|
3961
4905
|
{
|
|
@@ -3964,87 +4908,256 @@ function PaymentMethodDisplay({
|
|
|
3964
4908
|
alignItems: "center",
|
|
3965
4909
|
gap: "0.75rem",
|
|
3966
4910
|
padding: "0.75rem 1rem",
|
|
3967
|
-
border:
|
|
3968
|
-
borderRadius:
|
|
3969
|
-
fontSize: "0.875rem"
|
|
4911
|
+
border: border(),
|
|
4912
|
+
borderRadius: `calc(${T.radius} * 0.6)`
|
|
3970
4913
|
},
|
|
3971
4914
|
children: [
|
|
3972
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
"span",
|
|
3984
|
-
{
|
|
3985
|
-
style: {
|
|
3986
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))"
|
|
3987
|
-
},
|
|
3988
|
-
children: [
|
|
3989
|
-
"\u2022\u2022\u2022\u2022 ",
|
|
3990
|
-
paymentMethod.last4
|
|
3991
|
-
]
|
|
3992
|
-
}
|
|
3993
|
-
),
|
|
3994
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3995
|
-
"span",
|
|
3996
|
-
{
|
|
3997
|
-
style: {
|
|
3998
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))",
|
|
3999
|
-
fontSize: "0.75rem"
|
|
4000
|
-
},
|
|
4001
|
-
children: [
|
|
4002
|
-
"Expires ",
|
|
4003
|
-
String(paymentMethod.expMonth).padStart(2, "0"),
|
|
4004
|
-
"/",
|
|
4005
|
-
String(paymentMethod.expYear).slice(-2)
|
|
4006
|
-
]
|
|
4007
|
-
}
|
|
4008
|
-
)
|
|
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
|
+
] })
|
|
4009
4926
|
]
|
|
4010
4927
|
}
|
|
4011
|
-
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
4012
|
-
"p",
|
|
4013
|
-
{
|
|
4014
|
-
style: {
|
|
4015
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))",
|
|
4016
|
-
fontSize: "0.875rem"
|
|
4017
|
-
},
|
|
4018
|
-
children: "No payment method on file."
|
|
4019
|
-
}
|
|
4020
|
-
),
|
|
4928
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("p", { style: { ...text.secondary, margin: 0 }, children: "No payment method on file." }),
|
|
4021
4929
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4022
4930
|
"button",
|
|
4023
4931
|
{
|
|
4932
|
+
type: "button",
|
|
4024
4933
|
onClick: handleUpdate,
|
|
4025
4934
|
style: {
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
color: "var(--nozle-primary, var(--primary))",
|
|
4935
|
+
...buttonStyle("outline"),
|
|
4936
|
+
alignSelf: "flex-start",
|
|
4029
4937
|
padding: "0.375rem 0.875rem",
|
|
4030
|
-
borderRadius:
|
|
4031
|
-
fontSize: "0.875rem",
|
|
4032
|
-
fontWeight: 500,
|
|
4033
|
-
cursor: "pointer"
|
|
4938
|
+
borderRadius: buttonRadius(`calc(${T.radius} / 2)`)
|
|
4034
4939
|
},
|
|
4035
4940
|
children: "Update Payment Method"
|
|
4036
4941
|
}
|
|
4037
4942
|
)
|
|
4038
4943
|
] });
|
|
4039
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
|
+
}
|
|
4040
5149
|
|
|
4041
5150
|
exports.BillingContext = BillingContext;
|
|
4042
5151
|
exports.BillingPortal = BillingPortal;
|
|
4043
5152
|
exports.BillingPortalError = BillingPortalError;
|
|
4044
5153
|
exports.BillingProvider = BillingProvider;
|
|
5154
|
+
exports.BillingRequestError = BillingRequestError;
|
|
4045
5155
|
exports.CancellationControl = CancellationControl;
|
|
4046
5156
|
exports.Checkout = Checkout;
|
|
4047
5157
|
exports.CheckoutButton = CheckoutButton;
|
|
5158
|
+
exports.CreditBalance = CreditBalance;
|
|
5159
|
+
exports.CreditHistory = CreditHistory;
|
|
5160
|
+
exports.CurrentPlan = CurrentPlan;
|
|
4048
5161
|
exports.FeatureGate = FeatureGate;
|
|
4049
5162
|
exports.LockedOverlay = LockedOverlay;
|
|
4050
5163
|
exports.PaymentMethodDisplay = PaymentMethodDisplay;
|
|
@@ -4053,20 +5166,39 @@ exports.PlanCard = PlanCard;
|
|
|
4053
5166
|
exports.PlanChangeControl = PlanChangeControl;
|
|
4054
5167
|
exports.PlanComparison = PlanComparison;
|
|
4055
5168
|
exports.PlanGate = PlanGate;
|
|
5169
|
+
exports.PoweredByNozle = PoweredByNozle;
|
|
4056
5170
|
exports.PricingTable = PricingTable;
|
|
4057
5171
|
exports.UpgradeButton = UpgradeButton;
|
|
4058
5172
|
exports.UpgradeModal = UpgradeModal;
|
|
4059
5173
|
exports.UpgradePrompt = UpgradePrompt;
|
|
4060
5174
|
exports.UsageAlert = UsageAlert;
|
|
5175
|
+
exports.UsageBreakdown = UsageBreakdown;
|
|
4061
5176
|
exports.UsageDashboard = UsageDashboard;
|
|
4062
5177
|
exports.UsageGate = UsageGate;
|
|
4063
5178
|
exports.UsageMeter = UsageMeter;
|
|
5179
|
+
exports.UsageTrend = UsageTrend;
|
|
5180
|
+
exports.comparisonRows = comparisonRows;
|
|
5181
|
+
exports.currentPlanMeters = currentPlanMeters;
|
|
4064
5182
|
exports.getUsageMeterColor = getUsageMeterColor;
|
|
5183
|
+
exports.planFeatures = planFeatures;
|
|
5184
|
+
exports.sortPlansByPrice = sortPlansByPrice;
|
|
5185
|
+
exports.usageItems = usageItems;
|
|
5186
|
+
exports.useAppearance = useAppearance;
|
|
4065
5187
|
exports.useBillingContext = useBillingContext;
|
|
5188
|
+
exports.useBillingRefresh = useBillingRefresh;
|
|
4066
5189
|
exports.useCheckout = useCheckout;
|
|
4067
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;
|
|
4068
5197
|
exports.useNozleClient = useNozleClient;
|
|
4069
5198
|
exports.useOptionalBillingContext = useOptionalBillingContext;
|
|
4070
5199
|
exports.usePlans = usePlans;
|
|
5200
|
+
exports.useSubscription = useSubscription;
|
|
5201
|
+
exports.useUpgradePreview = useUpgradePreview;
|
|
5202
|
+
exports.useUsage = useUsage;
|
|
4071
5203
|
//# sourceMappingURL=index.cjs.map
|
|
4072
5204
|
//# sourceMappingURL=index.cjs.map
|