@nozle-js/react 0.10.2 → 0.12.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 +50 -1
- package/dist/chunk-NLPONQ5O.js +246 -0
- package/dist/chunk-NLPONQ5O.js.map +1 -0
- package/dist/index.cjs +2691 -1134
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +876 -78
- package/dist/index.d.ts +876 -78
- package/dist/index.js +2254 -964
- 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,490 @@
|
|
|
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 {
|
|
415
|
+
plans: data.plans ?? [],
|
|
416
|
+
families: Array.isArray(data.families) ? data.families : null,
|
|
417
|
+
nozleBranding: data.meta?.nozle_branding === true
|
|
418
|
+
};
|
|
419
|
+
},
|
|
420
|
+
staleTime: 5 * 6e4,
|
|
421
|
+
// The pricing page shows a load error at once; the app can call refetch().
|
|
422
|
+
retry: false
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
function usePlans() {
|
|
426
|
+
const { client, queryClient } = useBillingContext();
|
|
427
|
+
const query = reactQuery.useQuery(catalogQuery(client), queryClient);
|
|
428
|
+
return {
|
|
429
|
+
plans: query.data?.plans ?? [],
|
|
430
|
+
families: query.data?.families ?? null,
|
|
431
|
+
isLoading: query.isPending,
|
|
432
|
+
error: query.error ?? null,
|
|
433
|
+
refetch: query.refetch
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// src/components/branding/logo.ts
|
|
438
|
+
var LOGO_VIEW_BOX = "0 0 3208 1122";
|
|
439
|
+
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";
|
|
440
|
+
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";
|
|
441
|
+
var BrandingScope = React2.createContext(false);
|
|
442
|
+
function useNozleBranding() {
|
|
443
|
+
const billing = useOptionalBillingContext();
|
|
444
|
+
const { data } = reactQuery.useQuery(
|
|
445
|
+
// Reads the catalog request the components already make: mounting this must not refetch a
|
|
446
|
+
// failed catalog, or a table that shows the error would loop back into loading.
|
|
447
|
+
{ ...catalogQuery(billing?.client), enabled: Boolean(billing), refetchOnMount: false, retryOnMount: false },
|
|
448
|
+
billing?.queryClient ?? idleQueryClient
|
|
449
|
+
);
|
|
450
|
+
return data?.nozleBranding === true;
|
|
451
|
+
}
|
|
452
|
+
function PoweredByNozle() {
|
|
453
|
+
const { root } = useAppearance();
|
|
454
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
455
|
+
"a",
|
|
456
|
+
{
|
|
457
|
+
href: "https://nozle.dev",
|
|
458
|
+
target: "_blank",
|
|
459
|
+
rel: "noopener noreferrer",
|
|
460
|
+
...root("poweredBy", {
|
|
461
|
+
...text.meta,
|
|
462
|
+
display: "flex",
|
|
463
|
+
alignItems: "center",
|
|
464
|
+
justifyContent: "center",
|
|
465
|
+
gap: "6px",
|
|
466
|
+
marginTop: "1rem",
|
|
467
|
+
textDecoration: "none"
|
|
468
|
+
}),
|
|
469
|
+
children: [
|
|
470
|
+
"Powered by",
|
|
471
|
+
/* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: LOGO_VIEW_BOX, height: "14", width: "40", role: "img", "aria-label": "Nozle", style: { color: T.foreground }, children: [
|
|
472
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { fill: "#F34213", d: LOGO_MARK_PATH }),
|
|
473
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { fill: "currentColor", d: LOGO_WORDMARK_PATH })
|
|
474
|
+
] })
|
|
475
|
+
]
|
|
476
|
+
}
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
function NozleBranding({ show, children }) {
|
|
480
|
+
const nested = React2.useContext(BrandingScope);
|
|
481
|
+
const fromCatalog = useNozleBranding();
|
|
482
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(BrandingScope.Provider, { value: true, children: [
|
|
483
|
+
children,
|
|
484
|
+
!nested && (show ?? fromCatalog) && /* @__PURE__ */ jsxRuntime.jsx(PoweredByNozle, {})
|
|
485
|
+
] });
|
|
486
|
+
}
|
|
487
|
+
|
|
14
488
|
// src/components/portal/client.ts
|
|
15
489
|
var BillingPortalError = class extends Error {
|
|
16
490
|
constructor(code) {
|
|
@@ -102,7 +576,7 @@ var identityQuery = `query SdkPortalIdentity {
|
|
|
102
576
|
addressLine1 addressLine2 city state zipcode country
|
|
103
577
|
shippingAddress { addressLine1 addressLine2 city state zipcode country }
|
|
104
578
|
}
|
|
105
|
-
customerPortalOrganization { id name }
|
|
579
|
+
customerPortalOrganization { id name nozleBranding }
|
|
106
580
|
}`;
|
|
107
581
|
var subscriptionsQuery = `query SdkPortalSubscriptions($page: Int) {
|
|
108
582
|
customerPortalSubscriptions(status: [active], limit: 20, page: $page) {
|
|
@@ -151,40 +625,55 @@ var topUpMutation = `mutation SdkPortalTopUp($input: CreateCustomerPortalWalletT
|
|
|
151
625
|
}`;
|
|
152
626
|
|
|
153
627
|
// src/components/portal/styles.ts
|
|
628
|
+
var line = `${T.borderWidth} solid ${T.border}`;
|
|
629
|
+
var r = (factor) => `calc(${T.radius} * ${factor})`;
|
|
154
630
|
var portalStyles = `
|
|
155
|
-
.nozle-billing-portal{color
|
|
631
|
+
.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
632
|
.nozle-billing-portal *{box-sizing:border-box}
|
|
157
633
|
.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
|
|
634
|
+
.nozle-billing-portal h2{font:${T.fontTitle}}
|
|
635
|
+
.nozle-billing-portal h3{font:${T.fontHeading}}
|
|
160
636
|
.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
|
|
637
|
+
.nozle-billing-portal section{margin-top:28px;padding-top:24px;border-top:${line}}
|
|
638
|
+
.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}
|
|
639
|
+
.nozle-billing-portal button:hover,.nozle-billing-portal .nzp-link:hover{background:${T.muted}}
|
|
164
640
|
.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
|
|
641
|
+
.nozle-billing-portal :focus-visible{outline:2px solid ${T.primary};outline-offset:3px}
|
|
642
|
+
.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}}
|
|
643
|
+
.nozle-billing-portal .nzp-primary:hover{filter:brightness(.92);background:${T.primary}}
|
|
644
|
+
.nozle-billing-portal .nzp-muted{color:${T.mutedForeground}}
|
|
645
|
+
.nozle-billing-portal .nzp-notice{color:${T.successText}}
|
|
169
646
|
.nozle-billing-portal .nzp-row{display:flex;align-items:center;justify-content:space-between;gap:14px;flex-wrap:wrap}
|
|
170
647
|
.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
|
|
648
|
+
.nozle-billing-portal .nzp-card{background:${T.card};border:${line};border-radius:${r(0.85)};box-shadow:${T.shadow};padding:20px;min-width:0}
|
|
649
|
+
.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}}
|
|
650
|
+
.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}
|
|
651
|
+
.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}
|
|
652
|
+
.nozle-billing-portal .nzp-card-actions>.nozle-cancellation>button{margin-left:auto}
|
|
653
|
+
.nozle-billing-portal .nzp-price{font:${T.fontMetric};font-variant-numeric:tabular-nums;margin:12px 0 4px}
|
|
654
|
+
.nozle-billing-portal .nzp-price .nzp-muted{font:${T.fontMeta}}
|
|
655
|
+
.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}
|
|
656
|
+
.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}
|
|
657
|
+
.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}
|
|
658
|
+
.nozle-billing-portal .nzp-table{overflow-x:auto;margin-top:16px;background:${T.card};border:${line};border-radius:${T.radius}}
|
|
659
|
+
.nozle-billing-portal table{width:100%;border-collapse:collapse;text-align:left;font:inherit}
|
|
660
|
+
.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}
|
|
661
|
+
.nozle-billing-portal td{height:48px;padding:14px 20px;border-bottom:${T.borderWidth} solid ${T.rowLine};white-space:nowrap}
|
|
662
|
+
.nozle-billing-portal tbody tr:last-child td{border-bottom:0}
|
|
663
|
+
.nozle-billing-portal .nzp-num{text-align:right}
|
|
664
|
+
.nozle-billing-portal td.nzp-num{font-family:${T.fontNumbers};font-variant-numeric:tabular-nums}
|
|
665
|
+
.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}}
|
|
666
|
+
.nozle-billing-portal .nzp-search:focus-within{border-color:${T.foreground}}
|
|
667
|
+
.nozle-billing-portal .nzp-search input{flex:1;height:100%;border:0;padding:0;background:transparent;color:${T.foreground};outline:none}
|
|
668
|
+
.nozle-billing-portal .nzp-search input::placeholder{color:${T.mutedForeground}}
|
|
669
|
+
.nozle-billing-portal label{display:grid;gap:6px;font-weight:500;min-width:0}
|
|
670
|
+
.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
671
|
.nozle-billing-portal .nzp-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,220px),1fr));gap:16px;margin:20px 0}
|
|
183
672
|
.nozle-billing-portal .nzp-actions{display:flex;align-items:flex-end;gap:10px;flex-wrap:wrap;margin-top:20px}
|
|
184
673
|
.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
|
|
674
|
+
.nozle-billing-portal dt{font:${T.fontMeta};color:${T.mutedForeground}}
|
|
186
675
|
.nozle-billing-portal dd{margin:3px 0 0;overflow-wrap:anywhere}
|
|
187
|
-
.nozle-billing-portal footer{margin-top:30px;text-align:center;color
|
|
676
|
+
.nozle-billing-portal footer{margin-top:30px;text-align:center;color:${T.mutedForeground};font:${T.fontMeta}}
|
|
188
677
|
`;
|
|
189
678
|
|
|
190
679
|
// src/components/portal/timezones.ts
|
|
@@ -368,6 +857,25 @@ function compareDecimalStrings(left, right) {
|
|
|
368
857
|
}
|
|
369
858
|
return magnitude * a.sign;
|
|
370
859
|
}
|
|
860
|
+
function formatDecimalString(value, maximumFractionDigits = 12) {
|
|
861
|
+
const parsed = parseDecimal(value);
|
|
862
|
+
if (!parsed) return value;
|
|
863
|
+
const fractionLimit = Math.max(
|
|
864
|
+
0,
|
|
865
|
+
Math.min(30, Math.trunc(maximumFractionDigits))
|
|
866
|
+
);
|
|
867
|
+
const firstGroupLength = parsed.integer.length % 3 || 3;
|
|
868
|
+
const groups = [parsed.integer.slice(0, firstGroupLength)];
|
|
869
|
+
for (let index = firstGroupLength; index < parsed.integer.length; index += 3) {
|
|
870
|
+
groups.push(parsed.integer.slice(index, index + 3));
|
|
871
|
+
}
|
|
872
|
+
const groupedInteger = groups.join(",");
|
|
873
|
+
const fraction = parsed.fraction.slice(0, fractionLimit);
|
|
874
|
+
return `${parsed.sign < 0 ? "-" : ""}${groupedInteger}${fraction ? `.${fraction}` : ""}`;
|
|
875
|
+
}
|
|
876
|
+
function creditUnitLabel(value, unitName) {
|
|
877
|
+
return compareDecimalStrings(value, "1") === 0 ? unitName : `${unitName}s`;
|
|
878
|
+
}
|
|
371
879
|
|
|
372
880
|
// src/components/portal/amounts.ts
|
|
373
881
|
function walletAmountCents(credits, rate, currencyDigits) {
|
|
@@ -388,16 +896,16 @@ function walletAmountCents(credits, rate, currencyDigits) {
|
|
|
388
896
|
return `${padded.slice(0, -scale)}.${padded.slice(-scale)}`;
|
|
389
897
|
}
|
|
390
898
|
var styles = `
|
|
391
|
-
.nozle-cancellation{font
|
|
392
|
-
.nozle-cancellation button{font
|
|
899
|
+
.nozle-cancellation{font:${T.fontBody};color:${T.foreground}}
|
|
900
|
+
.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
901
|
.nozle-cancellation button:disabled{opacity:.55;cursor:wait}
|
|
394
|
-
.nozle-cancellation :focus-visible{outline:2px solid
|
|
902
|
+
.nozle-cancellation :focus-visible{outline:2px solid ${T.primary};outline-offset:3px}
|
|
395
903
|
.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
|
|
904
|
+
.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}
|
|
905
|
+
.nozle-cancellation .nzc-dialog h3{margin:0 0 16px;font:${T.fontTitle};letter-spacing:normal;color:${T.foreground}}
|
|
906
|
+
.nozle-cancellation .nzc-dialog p{margin:12px 0}
|
|
399
907
|
.nozle-cancellation .nzc-buttons{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:10px;margin-top:20px}
|
|
400
|
-
.nozle-cancellation [role=alert]{color
|
|
908
|
+
.nozle-cancellation [role=alert],.nozle-cancellation .nzc-danger{color:${T.dangerText}}
|
|
401
909
|
`;
|
|
402
910
|
function formatDate(value, locale, timezone) {
|
|
403
911
|
const timeZone = timezone.startsWith("TZ_") ? portalTimezones[timezone] ?? "UTC" : timezone;
|
|
@@ -427,27 +935,29 @@ function CancellationFlow({
|
|
|
427
935
|
onError,
|
|
428
936
|
locale = "en-US",
|
|
429
937
|
timezone = "UTC",
|
|
430
|
-
nonce
|
|
938
|
+
nonce,
|
|
939
|
+
appearance
|
|
431
940
|
}) {
|
|
432
|
-
const
|
|
433
|
-
const [
|
|
434
|
-
const [
|
|
435
|
-
const [
|
|
436
|
-
const [
|
|
437
|
-
const [
|
|
941
|
+
const { root } = useAppearance(appearance);
|
|
942
|
+
const [open, setOpen] = React2.useState(false);
|
|
943
|
+
const [busy, setBusy] = React2.useState(false);
|
|
944
|
+
const [preview, setPreview] = React2.useState();
|
|
945
|
+
const [failure, setFailure] = React2.useState();
|
|
946
|
+
const [notice, setNotice] = React2.useState("");
|
|
947
|
+
const [observed, setObserved] = React2.useState(
|
|
438
948
|
subscription
|
|
439
949
|
);
|
|
440
|
-
const request =
|
|
441
|
-
const inFlight =
|
|
442
|
-
const trigger =
|
|
443
|
-
const dialog =
|
|
444
|
-
const titleId =
|
|
445
|
-
const descriptionId =
|
|
950
|
+
const request = React2.useRef(void 0);
|
|
951
|
+
const inFlight = React2.useRef(null);
|
|
952
|
+
const trigger = React2.useRef(null);
|
|
953
|
+
const dialog = React2.useRef(null);
|
|
954
|
+
const titleId = React2.useId();
|
|
955
|
+
const descriptionId = React2.useId();
|
|
446
956
|
const active = observed?.status === "active";
|
|
447
957
|
const ending = observed?.endingAt;
|
|
448
958
|
const expired = Boolean(ending && Date.parse(ending) <= Date.now());
|
|
449
959
|
const operation = ending ? "uncancel" : "cancel";
|
|
450
|
-
|
|
960
|
+
React2.useEffect(
|
|
451
961
|
() => setObserved(subscription),
|
|
452
962
|
[
|
|
453
963
|
subscription.id,
|
|
@@ -458,8 +968,8 @@ function CancellationFlow({
|
|
|
458
968
|
subscription.pendingPlanName
|
|
459
969
|
]
|
|
460
970
|
);
|
|
461
|
-
|
|
462
|
-
|
|
971
|
+
React2.useEffect(() => () => inFlight.current?.abort(), []);
|
|
972
|
+
React2.useEffect(() => {
|
|
463
973
|
if (!open) return;
|
|
464
974
|
dialog.current?.focus();
|
|
465
975
|
return () => trigger.current?.focus();
|
|
@@ -615,7 +1125,7 @@ function CancellationFlow({
|
|
|
615
1125
|
);
|
|
616
1126
|
});
|
|
617
1127
|
}
|
|
618
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", {
|
|
1128
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ...root("cancellationControl", void 0, "nozle-cancellation"), children: [
|
|
619
1129
|
/* @__PURE__ */ jsxRuntime.jsx("style", { nonce, children: styles }),
|
|
620
1130
|
notice && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", children: notice }),
|
|
621
1131
|
active && !expired && /* @__PURE__ */ jsxRuntime.jsx("button", { ref: trigger, type: "button", onClick: () => void loadPreview(), children: ending ? "Keep my subscription" : "Cancel subscription" }),
|
|
@@ -700,6 +1210,7 @@ function CancellationFlow({
|
|
|
700
1210
|
"button",
|
|
701
1211
|
{
|
|
702
1212
|
type: "button",
|
|
1213
|
+
className: preview.operation === "cancel" ? "nzc-danger" : void 0,
|
|
703
1214
|
disabled: busy,
|
|
704
1215
|
onClick: () => void confirm(),
|
|
705
1216
|
children: request.current ? "Check status / retry" : preview.operation === "cancel" ? "Confirm cancellation" : "Confirm keep subscription"
|
|
@@ -711,66 +1222,6 @@ function CancellationFlow({
|
|
|
711
1222
|
) })
|
|
712
1223
|
] });
|
|
713
1224
|
}
|
|
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
1225
|
|
|
775
1226
|
// src/components/billing/razorpay-checkout.ts
|
|
776
1227
|
var loadingScript;
|
|
@@ -985,14 +1436,16 @@ function CheckoutButton({
|
|
|
985
1436
|
onStripeClientSecret,
|
|
986
1437
|
onSuccess,
|
|
987
1438
|
onComplete,
|
|
988
|
-
onScheduled
|
|
1439
|
+
onScheduled,
|
|
1440
|
+
appearance
|
|
989
1441
|
}) {
|
|
1442
|
+
const { root, vars } = useAppearance(appearance);
|
|
990
1443
|
const { createCheckout, verifyCheckout, getCheckoutStatus } = useBillingContext();
|
|
991
|
-
const attempt =
|
|
992
|
-
const inFlight =
|
|
993
|
-
const [processing, setProcessing] =
|
|
994
|
-
const [loading, setLoading] =
|
|
995
|
-
const [error, setError] =
|
|
1444
|
+
const attempt = React2.useRef(void 0);
|
|
1445
|
+
const inFlight = React2.useRef(false);
|
|
1446
|
+
const [processing, setProcessing] = React2.useState(false);
|
|
1447
|
+
const [loading, setLoading] = React2.useState(false);
|
|
1448
|
+
const [error, setError] = React2.useState(null);
|
|
996
1449
|
async function handleClick() {
|
|
997
1450
|
if (inFlight.current) return;
|
|
998
1451
|
inFlight.current = true;
|
|
@@ -1049,51 +1502,79 @@ function CheckoutButton({
|
|
|
1049
1502
|
type: "button",
|
|
1050
1503
|
onClick: () => void handleClick(),
|
|
1051
1504
|
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
|
-
},
|
|
1505
|
+
...root("checkoutButton", { ...buttonStyle("primary", loading), ...style }, className),
|
|
1064
1506
|
"aria-busy": loading,
|
|
1065
1507
|
children: loading ? "Loading..." : label
|
|
1066
1508
|
}
|
|
1067
1509
|
),
|
|
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 })
|
|
1510
|
+
processing && /* @__PURE__ */ jsxRuntime.jsx("span", { role: "status", style: { ...vars, ...text.body }, children: "Payment is processing. Confirmation may take a little while." }),
|
|
1511
|
+
error && /* @__PURE__ */ jsxRuntime.jsx("span", { role: "alert", style: { ...vars, ...text.body, color: T.dangerText }, children: error.message })
|
|
1070
1512
|
] });
|
|
1071
1513
|
}
|
|
1072
|
-
var CheckoutContext =
|
|
1514
|
+
var CheckoutContext = React2.createContext(null);
|
|
1073
1515
|
function useCheckout() {
|
|
1074
|
-
const ctx =
|
|
1516
|
+
const ctx = React2.useContext(CheckoutContext);
|
|
1075
1517
|
if (ctx === null) {
|
|
1076
1518
|
throw new Error("useCheckout must be used inside a <Checkout> component");
|
|
1077
1519
|
}
|
|
1078
1520
|
return ctx;
|
|
1079
1521
|
}
|
|
1080
|
-
function
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1522
|
+
function rgb(color) {
|
|
1523
|
+
const match = color?.match(/^rgba?\(\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)/);
|
|
1524
|
+
return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
|
|
1525
|
+
}
|
|
1526
|
+
function stripeColor(color) {
|
|
1527
|
+
return rgb(color) ? color : color?.startsWith("#") ? color : void 0;
|
|
1528
|
+
}
|
|
1529
|
+
function toStripeAppearance(colors) {
|
|
1530
|
+
const background = rgb(colors.background);
|
|
1531
|
+
const dark = background ? (0.2126 * background[0] + 0.7152 * background[1] + 0.0722 * background[2]) / 255 < 0.5 : false;
|
|
1532
|
+
const variables = Object.fromEntries(
|
|
1533
|
+
Object.entries({
|
|
1534
|
+
colorPrimary: stripeColor(colors.primary),
|
|
1535
|
+
colorBackground: stripeColor(colors.background),
|
|
1536
|
+
colorText: stripeColor(colors.foreground),
|
|
1537
|
+
colorDanger: stripeColor(colors.danger),
|
|
1538
|
+
borderRadius: colors.radius,
|
|
1539
|
+
fontFamily: colors.font
|
|
1540
|
+
}).filter(([, value]) => value)
|
|
1541
|
+
);
|
|
1542
|
+
return { theme: dark ? "night" : "stripe", variables };
|
|
1543
|
+
}
|
|
1544
|
+
function useStripeAppearance(appearanceKey) {
|
|
1545
|
+
const probe = React2.useRef(null);
|
|
1546
|
+
const [stripeAppearance, setStripeAppearance] = React2.useState(null);
|
|
1547
|
+
React2.useEffect(() => {
|
|
1548
|
+
if (!probe.current) return;
|
|
1549
|
+
const style = getComputedStyle(probe.current);
|
|
1550
|
+
setStripeAppearance(
|
|
1551
|
+
toStripeAppearance({
|
|
1552
|
+
primary: style.color,
|
|
1553
|
+
background: style.backgroundColor,
|
|
1554
|
+
foreground: style.borderTopColor,
|
|
1555
|
+
danger: style.borderBottomColor,
|
|
1556
|
+
radius: style.borderTopLeftRadius,
|
|
1557
|
+
font: style.fontFamily
|
|
1558
|
+
})
|
|
1559
|
+
);
|
|
1560
|
+
}, [appearanceKey]);
|
|
1561
|
+
const probeElement = /* @__PURE__ */ jsxRuntime.jsx(
|
|
1562
|
+
"span",
|
|
1563
|
+
{
|
|
1564
|
+
ref: probe,
|
|
1565
|
+
"aria-hidden": "true",
|
|
1566
|
+
style: {
|
|
1567
|
+
display: "none",
|
|
1568
|
+
color: T.primary,
|
|
1569
|
+
backgroundColor: T.card,
|
|
1570
|
+
borderTop: `1px solid ${T.foreground}`,
|
|
1571
|
+
borderBottom: `1px solid ${T.danger}`,
|
|
1572
|
+
borderRadius: T.radius,
|
|
1573
|
+
fontFamily: T.font
|
|
1574
|
+
}
|
|
1095
1575
|
}
|
|
1096
|
-
|
|
1576
|
+
);
|
|
1577
|
+
return { probeElement, stripeAppearance };
|
|
1097
1578
|
}
|
|
1098
1579
|
function CheckoutInner({
|
|
1099
1580
|
submitLabel,
|
|
@@ -1103,14 +1584,16 @@ function CheckoutInner({
|
|
|
1103
1584
|
className,
|
|
1104
1585
|
style,
|
|
1105
1586
|
returnUrl,
|
|
1106
|
-
children
|
|
1587
|
+
children,
|
|
1588
|
+
appearance
|
|
1107
1589
|
}) {
|
|
1108
1590
|
const stripe = reactStripeJs.useStripe();
|
|
1109
1591
|
const elements = reactStripeJs.useElements();
|
|
1110
|
-
const
|
|
1111
|
-
const [
|
|
1112
|
-
const [
|
|
1113
|
-
const
|
|
1592
|
+
const { el } = useAppearance(appearance);
|
|
1593
|
+
const [isProcessing, setIsProcessing] = React2.useState(false);
|
|
1594
|
+
const [error, setError] = React2.useState(null);
|
|
1595
|
+
const [isReady, setIsReady] = React2.useState(false);
|
|
1596
|
+
const inFlight = React2.useRef(false);
|
|
1114
1597
|
async function confirmPayment() {
|
|
1115
1598
|
if (!stripe || !elements || inFlight.current) return;
|
|
1116
1599
|
inFlight.current = true;
|
|
@@ -1140,7 +1623,7 @@ function CheckoutInner({
|
|
|
1140
1623
|
setIsProcessing(false);
|
|
1141
1624
|
}
|
|
1142
1625
|
}
|
|
1143
|
-
return /* @__PURE__ */ jsxRuntime.jsx(CheckoutContext.Provider, { value: { confirmPayment, isProcessing, error }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", {
|
|
1626
|
+
return /* @__PURE__ */ jsxRuntime.jsx(CheckoutContext.Provider, { value: { confirmPayment, isProcessing, error }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1144
1627
|
/* @__PURE__ */ jsxRuntime.jsx("style", { children: `@keyframes nozle-skeleton-pulse { 0%{background-position:200% 0} 100%{background-position:-200% 0} }` }),
|
|
1145
1628
|
!isReady && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1146
1629
|
"div",
|
|
@@ -1148,8 +1631,8 @@ function CheckoutInner({
|
|
|
1148
1631
|
"data-testid": "checkout-skeleton",
|
|
1149
1632
|
style: {
|
|
1150
1633
|
height: "200px",
|
|
1151
|
-
borderRadius:
|
|
1152
|
-
background:
|
|
1634
|
+
borderRadius: T.radius,
|
|
1635
|
+
background: `linear-gradient(90deg, ${T.muted} 25%, ${T.skeleton} 50%, ${T.muted} 75%)`,
|
|
1153
1636
|
backgroundSize: "200% 100%",
|
|
1154
1637
|
animation: "nozle-skeleton-pulse 1.5s ease-in-out infinite"
|
|
1155
1638
|
}
|
|
@@ -1164,16 +1647,7 @@ function CheckoutInner({
|
|
|
1164
1647
|
}
|
|
1165
1648
|
}
|
|
1166
1649
|
),
|
|
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
|
-
),
|
|
1650
|
+
error !== null && /* @__PURE__ */ jsxRuntime.jsx("p", { ...el("checkout__error", { color: T.dangerText, margin: "0.5rem 0 0" }), children: error.message }),
|
|
1177
1651
|
children ?? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1178
1652
|
"button",
|
|
1179
1653
|
{
|
|
@@ -1181,18 +1655,7 @@ function CheckoutInner({
|
|
|
1181
1655
|
onClick: () => void confirmPayment(),
|
|
1182
1656
|
disabled: isProcessing || !stripe,
|
|
1183
1657
|
"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
|
-
},
|
|
1658
|
+
...el("checkout__submit", { ...buttonStyle("primary", isProcessing), marginTop: "1rem", width: "100%" }),
|
|
1196
1659
|
children: isProcessing ? "Processing..." : submitLabel ?? "Pay now"
|
|
1197
1660
|
}
|
|
1198
1661
|
)
|
|
@@ -1210,19 +1673,21 @@ function StripeCheckout({
|
|
|
1210
1673
|
onReady,
|
|
1211
1674
|
className,
|
|
1212
1675
|
style,
|
|
1213
|
-
children
|
|
1676
|
+
children,
|
|
1677
|
+
appearance: themeAppearance
|
|
1214
1678
|
}) {
|
|
1215
|
-
const
|
|
1679
|
+
const { root } = useAppearance(themeAppearance);
|
|
1680
|
+
const { probeElement, stripeAppearance } = useStripeAppearance(JSON.stringify(root("checkout").style));
|
|
1681
|
+
const stripePromise = React2.useMemo(
|
|
1216
1682
|
() => stripeJs.loadStripe(publishableKey, stripeAccount ? { stripeAccount } : void 0),
|
|
1217
1683
|
[publishableKey, stripeAccount]
|
|
1218
1684
|
);
|
|
1219
|
-
const
|
|
1220
|
-
const embeddedOptions = React.useMemo(
|
|
1685
|
+
const embeddedOptions = React2.useMemo(
|
|
1221
1686
|
() => ({ clientSecret, onComplete }),
|
|
1222
1687
|
[clientSecret, onComplete]
|
|
1223
1688
|
);
|
|
1224
1689
|
if (clientSecret.startsWith("cs_")) {
|
|
1225
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", {
|
|
1690
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { ...root("checkout", { ...text.body, ...style }, className), children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1226
1691
|
reactStripeJs.EmbeddedCheckoutProvider,
|
|
1227
1692
|
{
|
|
1228
1693
|
stripe: stripePromise,
|
|
@@ -1231,26 +1696,29 @@ function StripeCheckout({
|
|
|
1231
1696
|
}
|
|
1232
1697
|
) });
|
|
1233
1698
|
}
|
|
1234
|
-
const options = { clientSecret, appearance };
|
|
1235
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
1236
|
-
|
|
1237
|
-
{
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1699
|
+
const options = stripeAppearance && { clientSecret, appearance: stripeAppearance };
|
|
1700
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ...root("checkout", { ...text.body, ...style }, className), children: [
|
|
1701
|
+
probeElement,
|
|
1702
|
+
options && /* @__PURE__ */ jsxRuntime.jsx(reactStripeJs.Elements, { stripe: stripePromise, options, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1703
|
+
CheckoutInner,
|
|
1704
|
+
{
|
|
1705
|
+
submitLabel,
|
|
1706
|
+
onSuccess,
|
|
1707
|
+
onError,
|
|
1708
|
+
onReady,
|
|
1709
|
+
returnUrl,
|
|
1710
|
+
children,
|
|
1711
|
+
appearance: themeAppearance
|
|
1712
|
+
}
|
|
1713
|
+
) }, clientSecret)
|
|
1714
|
+
] });
|
|
1248
1715
|
}
|
|
1249
1716
|
function ProviderCheckout(props) {
|
|
1250
1717
|
const billing = useOptionalBillingContext();
|
|
1251
|
-
const
|
|
1252
|
-
const [
|
|
1253
|
-
const [
|
|
1718
|
+
const { root, el } = useAppearance(props.appearance);
|
|
1719
|
+
const [busy, setBusy] = React2.useState(false);
|
|
1720
|
+
const [error, setError] = React2.useState(null);
|
|
1721
|
+
const [processing, setProcessing] = React2.useState(false);
|
|
1254
1722
|
const pay = async () => {
|
|
1255
1723
|
if (busy) return;
|
|
1256
1724
|
setBusy(true);
|
|
@@ -1297,17 +1765,19 @@ function ProviderCheckout(props) {
|
|
|
1297
1765
|
submitLabel: props.submitLabel,
|
|
1298
1766
|
onSuccess: props.onSuccess,
|
|
1299
1767
|
onComplete: props.onComplete,
|
|
1300
|
-
onError: props.onError
|
|
1768
|
+
onError: props.onError,
|
|
1769
|
+
className: props.className,
|
|
1770
|
+
appearance: props.appearance
|
|
1301
1771
|
}
|
|
1302
1772
|
);
|
|
1303
1773
|
}
|
|
1304
|
-
return /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", children: "Payment setup is incomplete. Contact support to continue." });
|
|
1774
|
+
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
1775
|
}
|
|
1306
1776
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1307
1777
|
CheckoutContext.Provider,
|
|
1308
1778
|
{
|
|
1309
1779
|
value: { confirmPayment: pay, isProcessing: busy, error },
|
|
1310
|
-
children: /* @__PURE__ */ jsxRuntime.jsxs("div", {
|
|
1780
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { ...root("checkout", text.body, props.className), children: [
|
|
1311
1781
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1312
1782
|
"button",
|
|
1313
1783
|
{
|
|
@@ -1315,39 +1785,42 @@ function ProviderCheckout(props) {
|
|
|
1315
1785
|
disabled: busy,
|
|
1316
1786
|
"aria-busy": busy,
|
|
1317
1787
|
onClick: () => void pay(),
|
|
1788
|
+
...el("checkout__submit", buttonStyle("primary", busy)),
|
|
1318
1789
|
children: busy ? "Processing\u2026" : props.submitLabel || "Pay now"
|
|
1319
1790
|
}
|
|
1320
1791
|
),
|
|
1321
1792
|
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 })
|
|
1793
|
+
error && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", ...el("checkout__error", { color: T.dangerText }), children: error.message })
|
|
1323
1794
|
] })
|
|
1324
1795
|
}
|
|
1325
1796
|
);
|
|
1326
1797
|
}
|
|
1327
1798
|
function Checkout(props) {
|
|
1328
|
-
return "checkout" in props ? /* @__PURE__ */ jsxRuntime.jsx(ProviderCheckout, { ...props }) : /* @__PURE__ */ jsxRuntime.jsx(StripeCheckout, { ...props });
|
|
1799
|
+
return /* @__PURE__ */ jsxRuntime.jsx(NozleBranding, { children: "checkout" in props ? /* @__PURE__ */ jsxRuntime.jsx(ProviderCheckout, { ...props }) : /* @__PURE__ */ jsxRuntime.jsx(StripeCheckout, { ...props }) });
|
|
1329
1800
|
}
|
|
1330
1801
|
var styles2 = `
|
|
1331
|
-
.nozle-
|
|
1332
|
-
.nozle-
|
|
1333
|
-
.nozle-
|
|
1334
|
-
.nozle-
|
|
1335
|
-
.nozle-
|
|
1336
|
-
.nozle-
|
|
1337
|
-
.nozle-
|
|
1338
|
-
.nozle-
|
|
1339
|
-
.nozle-
|
|
1802
|
+
.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}}
|
|
1803
|
+
.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}
|
|
1804
|
+
.nozle-upgradeModal h2{font:${T.fontTitle};letter-spacing:normal;color:${T.foreground};margin:0 0 16px}
|
|
1805
|
+
.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}
|
|
1806
|
+
.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}}
|
|
1807
|
+
.nozle-upgradeModal button:disabled{opacity:.55;cursor:wait}
|
|
1808
|
+
.nozle-upgradeModal :focus-visible{outline:2px solid ${T.primary};outline-offset:3px}
|
|
1809
|
+
.nozle-upgradeModal .nozle-upgradeModal__actions{display:flex;flex-wrap:wrap;gap:10px;justify-content:flex-end;margin-top:20px}
|
|
1810
|
+
.nozle-upgradeModal .nzu-back{margin-right:auto}
|
|
1811
|
+
.nozle-upgradeModal [role=alert]{color:${T.dangerText}}
|
|
1340
1812
|
`;
|
|
1341
1813
|
function UpgradeModal(props) {
|
|
1342
1814
|
const billing = useOptionalBillingContext();
|
|
1343
|
-
const [processing, setProcessing] =
|
|
1344
|
-
const [error, setError] =
|
|
1345
|
-
const [confirming, setConfirming] =
|
|
1346
|
-
const inFlight =
|
|
1347
|
-
const dialog =
|
|
1348
|
-
const titleId =
|
|
1815
|
+
const [processing, setProcessing] = React2.useState(false);
|
|
1816
|
+
const [error, setError] = React2.useState(null);
|
|
1817
|
+
const [confirming, setConfirming] = React2.useState(false);
|
|
1818
|
+
const inFlight = React2.useRef(false);
|
|
1819
|
+
const dialog = React2.useRef(null);
|
|
1820
|
+
const titleId = React2.useId();
|
|
1349
1821
|
const busy = Boolean(props.busy || confirming);
|
|
1350
|
-
|
|
1822
|
+
const { root, el } = useAppearance(props.appearance);
|
|
1823
|
+
React2.useEffect(() => {
|
|
1351
1824
|
if (!props.isOpen) return;
|
|
1352
1825
|
const previous = document.activeElement;
|
|
1353
1826
|
setError(null);
|
|
@@ -1406,17 +1879,17 @@ function UpgradeModal(props) {
|
|
|
1406
1879
|
}
|
|
1407
1880
|
}
|
|
1408
1881
|
if (!props.isOpen) return null;
|
|
1409
|
-
const
|
|
1882
|
+
const money3 = (value) => new Intl.NumberFormat(props.locale ?? "en-US", {
|
|
1410
1883
|
style: "currency",
|
|
1411
1884
|
currency: props.preview?.currency ?? "USD"
|
|
1412
1885
|
}).format(value);
|
|
1413
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", {
|
|
1886
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ...root("upgradeModal", void 0, "nozle-upgrade-modal"), children: [
|
|
1414
1887
|
/* @__PURE__ */ jsxRuntime.jsx("style", { nonce: props.nonce, children: styles2 }),
|
|
1415
1888
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1416
1889
|
"div",
|
|
1417
1890
|
{
|
|
1418
1891
|
ref: dialog,
|
|
1419
|
-
|
|
1892
|
+
...el("upgradeModal__dialog"),
|
|
1420
1893
|
role: "dialog",
|
|
1421
1894
|
"aria-modal": "true",
|
|
1422
1895
|
"aria-labelledby": titleId,
|
|
@@ -1454,15 +1927,15 @@ function UpgradeModal(props) {
|
|
|
1454
1927
|
props.preview && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1455
1928
|
props.preview.credit > 0 && /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
|
|
1456
1929
|
"Credits applied: ",
|
|
1457
|
-
|
|
1930
|
+
money3(-props.preview.credit)
|
|
1458
1931
|
] }),
|
|
1459
1932
|
/* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
|
|
1460
1933
|
"New plan charge: ",
|
|
1461
|
-
|
|
1934
|
+
money3(props.preview.debit)
|
|
1462
1935
|
] }),
|
|
1463
1936
|
/* @__PURE__ */ jsxRuntime.jsx("p", { children: /* @__PURE__ */ jsxRuntime.jsxs("strong", { children: [
|
|
1464
1937
|
"Due today: ",
|
|
1465
|
-
|
|
1938
|
+
money3(props.preview.net)
|
|
1466
1939
|
] }) }),
|
|
1467
1940
|
/* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
|
|
1468
1941
|
"Next billing date:",
|
|
@@ -1474,18 +1947,21 @@ function UpgradeModal(props) {
|
|
|
1474
1947
|
] })
|
|
1475
1948
|
] }),
|
|
1476
1949
|
props.children,
|
|
1477
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", {
|
|
1950
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { ...el("upgradeModal__actions"), children: [
|
|
1951
|
+
props.onBack && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "nzu-back", onClick: props.onBack, disabled: busy, children: "Back" }),
|
|
1478
1952
|
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: props.onCancel, disabled: busy, children: "Close" }),
|
|
1479
1953
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1480
1954
|
"button",
|
|
1481
1955
|
{
|
|
1482
1956
|
type: "button",
|
|
1957
|
+
className: "nzu-primary",
|
|
1483
1958
|
onClick: () => void handleConfirm(),
|
|
1484
1959
|
disabled: busy || props.confirmDisabled,
|
|
1485
1960
|
children: busy ? "Checking subscription\u2026" : props.confirmLabel ?? "Confirm"
|
|
1486
1961
|
}
|
|
1487
1962
|
)
|
|
1488
|
-
] })
|
|
1963
|
+
] }),
|
|
1964
|
+
/* @__PURE__ */ jsxRuntime.jsx(NozleBranding, {})
|
|
1489
1965
|
]
|
|
1490
1966
|
}
|
|
1491
1967
|
)
|
|
@@ -1493,7 +1969,7 @@ function UpgradeModal(props) {
|
|
|
1493
1969
|
}
|
|
1494
1970
|
|
|
1495
1971
|
// src/components/billing/amounts.ts
|
|
1496
|
-
function formatMinorAmount(value, currency, locale = "en-US") {
|
|
1972
|
+
function formatMinorAmount(value, currency, locale = "en-US", options = {}) {
|
|
1497
1973
|
const raw = String(value);
|
|
1498
1974
|
if (!/^-?\d+$/.test(raw) || typeof value === "number" && !Number.isSafeInteger(value)) {
|
|
1499
1975
|
return "Amount unavailable";
|
|
@@ -1509,12 +1985,16 @@ function formatMinorAmount(value, currency, locale = "en-US") {
|
|
|
1509
1985
|
const scale = 10n ** BigInt(digits);
|
|
1510
1986
|
const whole = absolute / scale;
|
|
1511
1987
|
const signed = minor < 0n ? whole === 0n ? -0 : -whole : whole;
|
|
1512
|
-
const
|
|
1988
|
+
const remainder = absolute % scale;
|
|
1989
|
+
const trim = options.trimZeroFraction === true && remainder === 0n;
|
|
1990
|
+
const decimal = String(remainder).padStart(digits, "0");
|
|
1513
1991
|
const digitFormatter = new Intl.NumberFormat(locale, {
|
|
1514
1992
|
useGrouping: false
|
|
1515
1993
|
});
|
|
1516
1994
|
const fraction = [...decimal].map((digit) => digitFormatter.format(Number(digit))).join("");
|
|
1517
|
-
return formatter.formatToParts(signed).
|
|
1995
|
+
return formatter.formatToParts(signed).filter(
|
|
1996
|
+
(part) => !trim || part.type !== "fraction" && part.type !== "decimal"
|
|
1997
|
+
).map((part) => part.type === "fraction" ? fraction : part.value).join("");
|
|
1518
1998
|
} catch {
|
|
1519
1999
|
return "Amount unavailable";
|
|
1520
2000
|
}
|
|
@@ -1533,67 +2013,238 @@ function formatBillingDate(value, locale = "en-US", timezone) {
|
|
|
1533
2013
|
}).format(new Date(value));
|
|
1534
2014
|
}
|
|
1535
2015
|
}
|
|
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.";
|
|
2016
|
+
|
|
2017
|
+
// src/components/pricing/plan-features.ts
|
|
2018
|
+
function number(value) {
|
|
2019
|
+
const parsed = Number(value);
|
|
2020
|
+
return Number.isFinite(parsed) ? parsed.toLocaleString("en-US", { maximumFractionDigits: 6 }) : String(value);
|
|
1555
2021
|
}
|
|
1556
|
-
function
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
2022
|
+
function money(amount, currency) {
|
|
2023
|
+
const parsed = Number(amount);
|
|
2024
|
+
if (!Number.isFinite(parsed)) return `${amount} ${currency}`;
|
|
2025
|
+
try {
|
|
2026
|
+
return new Intl.NumberFormat("en-US", { style: "currency", currency, maximumFractionDigits: 6 }).format(parsed);
|
|
2027
|
+
} catch {
|
|
2028
|
+
return `${currency} ${number(amount)}`;
|
|
2029
|
+
}
|
|
1560
2030
|
}
|
|
1561
|
-
function
|
|
1562
|
-
return
|
|
2031
|
+
function words(code) {
|
|
2032
|
+
return code.replace(/_/g, " ");
|
|
1563
2033
|
}
|
|
1564
|
-
function
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
}
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
)
|
|
1579
|
-
|
|
2034
|
+
function entitlementRows(entitlement) {
|
|
2035
|
+
if (entitlement.feature_type === "config") {
|
|
2036
|
+
return (entitlement.privileges ?? []).flatMap((privilege) => {
|
|
2037
|
+
if (privilege.value === null || privilege.value === false) return [];
|
|
2038
|
+
if (privilege.value === true) return [`${entitlement.name}: ${privilege.name}`];
|
|
2039
|
+
return [`${entitlement.name}: ${privilege.value}`];
|
|
2040
|
+
});
|
|
2041
|
+
}
|
|
2042
|
+
if (entitlement.feature_type === "metered" && entitlement.usage_limit != null) {
|
|
2043
|
+
return [`${number(entitlement.usage_limit)} ${entitlement.name}`];
|
|
2044
|
+
}
|
|
2045
|
+
return [entitlement.name];
|
|
2046
|
+
}
|
|
2047
|
+
function creditGrantRow(rule) {
|
|
2048
|
+
const credits = `${number(rule.amount)} ${words(rule.credit_system_code)}`;
|
|
2049
|
+
if (rule.interval === "lifetime") return `${credits} (one time)`;
|
|
2050
|
+
const count = rule.interval_count ?? 1;
|
|
2051
|
+
return count === 1 ? `${credits} every ${rule.interval}` : `${credits} every ${count} ${rule.interval}s`;
|
|
2052
|
+
}
|
|
2053
|
+
function isZero(amount) {
|
|
2054
|
+
return amount === void 0 || Number(amount) === 0;
|
|
2055
|
+
}
|
|
2056
|
+
function rangeLabel(range, unit) {
|
|
2057
|
+
const from = number(range.from_value);
|
|
2058
|
+
return range.to_value === null ? `${from}+ ${unit}` : `${from} to ${number(range.to_value)} ${unit}`;
|
|
2059
|
+
}
|
|
2060
|
+
function rangeRows(ranges, unit, currency) {
|
|
2061
|
+
return ranges.map((range) => {
|
|
2062
|
+
const parts = [];
|
|
2063
|
+
if (!isZero(range.flat_amount)) parts.push(`${money(range.flat_amount, currency)} flat`);
|
|
2064
|
+
if (!isZero(range.per_unit_amount)) parts.push(`${money(range.per_unit_amount, currency)} each`);
|
|
2065
|
+
if (!isZero(range.rate)) parts.push(`${number(range.rate)}%`);
|
|
2066
|
+
return `${rangeLabel(range, unit)}: ${parts.length ? parts.join(" + ") : "free"}`;
|
|
2067
|
+
});
|
|
2068
|
+
}
|
|
2069
|
+
function chargeRows(charge, currency) {
|
|
2070
|
+
const properties = charge.properties ?? {};
|
|
2071
|
+
const unit = charge.feature_code ? words(charge.feature_code) : "unit";
|
|
2072
|
+
switch (charge.charge_model) {
|
|
2073
|
+
case "standard":
|
|
2074
|
+
return properties.amount === void 0 ? [] : [`${money(properties.amount, currency)} per ${unit}`];
|
|
2075
|
+
case "package": {
|
|
2076
|
+
if (properties.amount === void 0 || !properties.package_size) return [];
|
|
2077
|
+
const free = properties.free_units ? `, first ${number(properties.free_units)} free` : "";
|
|
2078
|
+
return [`${money(properties.amount, currency)} per ${number(properties.package_size)} ${unit}${free}`];
|
|
2079
|
+
}
|
|
2080
|
+
case "graduated":
|
|
2081
|
+
return rangeRows(properties.graduated_ranges ?? [], unit, currency);
|
|
2082
|
+
case "volume":
|
|
2083
|
+
return rangeRows(properties.volume_ranges ?? [], unit, currency);
|
|
2084
|
+
case "graduated_percentage":
|
|
2085
|
+
return rangeRows(properties.graduated_percentage_ranges ?? [], unit, currency);
|
|
2086
|
+
case "percentage": {
|
|
2087
|
+
if (properties.rate === void 0) return [];
|
|
2088
|
+
const fixed = isZero(properties.fixed_amount) ? "" : ` + ${money(properties.fixed_amount, currency)} per ${unit}`;
|
|
2089
|
+
return [`${number(properties.rate)}% of each ${unit}${fixed}`];
|
|
2090
|
+
}
|
|
2091
|
+
default:
|
|
2092
|
+
return [];
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
function planFeatures(plan) {
|
|
2096
|
+
const rows = (plan.entitlements ?? []).flatMap(entitlementRows);
|
|
2097
|
+
rows.push(...(plan.credit_grant_rules ?? []).map(creditGrantRow));
|
|
2098
|
+
for (const charge of plan.charges ?? []) rows.push(...chargeRows(charge, plan.amount_currency));
|
|
2099
|
+
return rows;
|
|
2100
|
+
}
|
|
2101
|
+
var INTERVAL_LABELS = {
|
|
2102
|
+
weekly: "week",
|
|
2103
|
+
monthly: "month",
|
|
2104
|
+
quarterly: "quarter",
|
|
2105
|
+
semiannual: "6 months",
|
|
2106
|
+
yearly: "year"
|
|
2107
|
+
};
|
|
2108
|
+
function intervalLabel(interval) {
|
|
2109
|
+
return INTERVAL_LABELS[interval] ?? interval;
|
|
2110
|
+
}
|
|
2111
|
+
function formatPlanPrice(amountCents, currency) {
|
|
2112
|
+
const amount = amountCents / 100;
|
|
2113
|
+
try {
|
|
2114
|
+
return new Intl.NumberFormat("en-US", {
|
|
2115
|
+
style: "currency",
|
|
2116
|
+
currency,
|
|
2117
|
+
minimumFractionDigits: Number.isInteger(amount) ? 0 : 2,
|
|
2118
|
+
maximumFractionDigits: 2
|
|
2119
|
+
}).format(amount);
|
|
2120
|
+
} catch {
|
|
2121
|
+
return `${currency} ${amount}`;
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
function entitlementValue(entitlement) {
|
|
2125
|
+
if (entitlement.feature_type === "config") {
|
|
2126
|
+
const values = (entitlement.privileges ?? []).filter((privilege) => privilege.value !== null && privilege.value !== false).map((privilege) => privilege.value === true ? privilege.name : String(privilege.value));
|
|
2127
|
+
return values.length ? values.join(", ") : false;
|
|
2128
|
+
}
|
|
2129
|
+
if (entitlement.feature_type === "metered" && entitlement.usage_limit != null) {
|
|
2130
|
+
return number(entitlement.usage_limit);
|
|
2131
|
+
}
|
|
2132
|
+
return true;
|
|
2133
|
+
}
|
|
2134
|
+
function comparisonRows(plans) {
|
|
2135
|
+
const rows = /* @__PURE__ */ new Map();
|
|
2136
|
+
const row = (key, label) => {
|
|
2137
|
+
let existing = rows.get(key);
|
|
2138
|
+
if (!existing) {
|
|
2139
|
+
existing = { key, label, values: {} };
|
|
2140
|
+
rows.set(key, existing);
|
|
2141
|
+
}
|
|
2142
|
+
return existing;
|
|
2143
|
+
};
|
|
2144
|
+
for (const plan of plans) {
|
|
2145
|
+
for (const entitlement of plan.entitlements ?? []) {
|
|
2146
|
+
row(`entitlement:${entitlement.code}`, entitlement.name).values[plan.code] = entitlementValue(entitlement);
|
|
2147
|
+
}
|
|
2148
|
+
for (const rule of plan.credit_grant_rules ?? []) {
|
|
2149
|
+
row(`credit:${rule.credit_system_code}`, words(rule.credit_system_code)).values[plan.code] = creditGrantRow(rule);
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
const all = [...rows.values()];
|
|
2153
|
+
for (const item of all) {
|
|
2154
|
+
for (const plan of plans) item.values[plan.code] ??= false;
|
|
2155
|
+
}
|
|
2156
|
+
return all;
|
|
2157
|
+
}
|
|
2158
|
+
function sortPlansByPrice(plans) {
|
|
2159
|
+
const currencyRank = /* @__PURE__ */ new Map();
|
|
2160
|
+
for (const plan of plans) {
|
|
2161
|
+
if (!currencyRank.has(plan.amount_currency)) currencyRank.set(plan.amount_currency, currencyRank.size);
|
|
2162
|
+
}
|
|
2163
|
+
return plans.map((plan, index) => ({ plan, index })).sort(
|
|
2164
|
+
(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
|
|
2165
|
+
).map(({ plan }) => plan);
|
|
2166
|
+
}
|
|
2167
|
+
var styles3 = `
|
|
2168
|
+
.nozle-plan-change{font:${T.fontBody};color:${T.foreground};margin-top:12px}
|
|
2169
|
+
.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}
|
|
2170
|
+
.nozle-plan-change button:disabled{opacity:.55;cursor:wait}
|
|
2171
|
+
.nozle-plan-change :focus-visible{outline:2px solid ${T.primary};outline-offset:3px}
|
|
2172
|
+
.nozle-plan-change [role=alert]{color:${T.dangerText}}
|
|
2173
|
+
.nozle-plan-change .nzpc-actions{display:flex;gap:10px;flex-wrap:wrap}
|
|
2174
|
+
.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}}
|
|
2175
|
+
.nozle-upgrade-modal dl{display:block;margin:16px 0;padding:0}
|
|
2176
|
+
.nozle-upgrade-modal dl div{display:flex;justify-content:space-between;gap:16px;margin:12px 0}
|
|
2177
|
+
.nozle-upgrade-modal dt{font:${T.fontMeta};color:${T.mutedForeground}}
|
|
2178
|
+
.nozle-upgrade-modal dd{margin:0;text-align:right}
|
|
2179
|
+
.nozle-upgrade-modal label{font:${T.fontBody};font-weight:500}
|
|
2180
|
+
.nozle-plan-change .nzpc-plans{display:grid;gap:10px}
|
|
2181
|
+
.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}}
|
|
2182
|
+
.nozle-plan-change label.nzpc-plan[data-selected]{border-color:${T.primary};background:${T.primarySoft}}
|
|
2183
|
+
.nozle-plan-change label.nzpc-plan input{flex:none;width:auto;margin:4px 0 0;padding:0;accent-color:${T.primary}}
|
|
2184
|
+
.nozle-plan-change .nzpc-plan-body{display:grid;gap:4px;min-width:0;flex:1}
|
|
2185
|
+
.nozle-plan-change .nzpc-plan-head{display:flex;justify-content:space-between;align-items:baseline;gap:8px}
|
|
2186
|
+
.nozle-plan-change .nzpc-plan-name{font:${T.fontHeading}}
|
|
2187
|
+
.nozle-plan-change .nzpc-plan-op{font:${T.labelFont};letter-spacing:${T.labelSpacing};text-transform:${T.labelTransform};color:${T.mutedForeground}}
|
|
2188
|
+
.nozle-plan-change .nzpc-plan-price{font-variant-numeric:tabular-nums}
|
|
2189
|
+
.nozle-plan-change .nzpc-plan-muted{font:${T.fontMeta};color:${T.mutedForeground};margin:0}
|
|
2190
|
+
.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}}
|
|
2191
|
+
`;
|
|
2192
|
+
var paymentPending = (state) => ["awaiting_payment", "processing", "needs_review"].includes(
|
|
2193
|
+
state.checkoutStatus
|
|
2194
|
+
);
|
|
2195
|
+
function paymentMessage(state) {
|
|
2196
|
+
if (state.checkoutStatus === "needs_review")
|
|
2197
|
+
return "Payment needs review. Contact support before trying another payment.";
|
|
2198
|
+
return state.checkoutStatus === "awaiting_payment" ? "Complete payment to change your plan." : "Your change is processing. Your current plan remains active until confirmation.";
|
|
2199
|
+
}
|
|
2200
|
+
function done(attempt, state) {
|
|
2201
|
+
if (attempt.operation === "withdraw")
|
|
2202
|
+
return !state.pendingChange && state.status === "active" && state.currentPlan.code === attempt.fromPlan;
|
|
2203
|
+
return state.currentPlan.code === attempt.target && state.status === "active" || state.pendingChange?.plan.code === attempt.target;
|
|
2204
|
+
}
|
|
2205
|
+
function PlanChangeControl(props) {
|
|
2206
|
+
return /* @__PURE__ */ jsxRuntime.jsx(PlanChangeFlow, { ...props }, props.subscriptionId);
|
|
2207
|
+
}
|
|
2208
|
+
function PlanChangeFlow({
|
|
2209
|
+
subscriptionId,
|
|
2210
|
+
refreshKey,
|
|
2211
|
+
actions,
|
|
2212
|
+
onChanged,
|
|
2213
|
+
onError,
|
|
2214
|
+
returnUrl,
|
|
2215
|
+
locale = "en-US",
|
|
2216
|
+
timezone = "UTC",
|
|
2217
|
+
nonce,
|
|
2218
|
+
appearance
|
|
2219
|
+
}) {
|
|
2220
|
+
const { root } = useAppearance(appearance);
|
|
2221
|
+
const owner = React2.useMemo(
|
|
2222
|
+
() => ({ controller: new AbortController() }),
|
|
2223
|
+
[subscriptionId, actions]
|
|
2224
|
+
);
|
|
2225
|
+
const latest = React2.useRef(owner);
|
|
1580
2226
|
latest.current = owner;
|
|
1581
|
-
const [loaded, setLoaded] =
|
|
1582
|
-
const [busy, setBusy] =
|
|
1583
|
-
const [failure, setFailure] =
|
|
1584
|
-
const [notice, setNotice] =
|
|
1585
|
-
const [mode, setMode] =
|
|
2227
|
+
const [loaded, setLoaded] = React2.useState();
|
|
2228
|
+
const [busy, setBusy] = React2.useState(false);
|
|
2229
|
+
const [failure, setFailure] = React2.useState();
|
|
2230
|
+
const [notice, setNotice] = React2.useState("");
|
|
2231
|
+
const [mode, setMode] = React2.useState(
|
|
1586
2232
|
null
|
|
1587
2233
|
);
|
|
1588
|
-
const [target, setTarget] =
|
|
1589
|
-
const
|
|
1590
|
-
const
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
const
|
|
2234
|
+
const [target, setTarget] = React2.useState("");
|
|
2235
|
+
const billing = useOptionalBillingContext();
|
|
2236
|
+
const catalog = reactQuery.useQuery(
|
|
2237
|
+
{ ...catalogQuery(billing?.client), enabled: Boolean(billing) },
|
|
2238
|
+
billing?.queryClient ?? idleQueryClient
|
|
2239
|
+
);
|
|
2240
|
+
const [quote, setQuote] = React2.useState();
|
|
2241
|
+
const [checkout, setCheckout] = React2.useState(null);
|
|
2242
|
+
const [withdrawTarget, setWithdrawTarget] = React2.useState();
|
|
2243
|
+
const pending = React2.useRef(void 0);
|
|
2244
|
+
const previousRefresh = React2.useRef(refreshKey);
|
|
2245
|
+
const inFlight = React2.useRef(null);
|
|
1595
2246
|
const state = loaded?.owner === owner ? loaded.value : void 0;
|
|
1596
|
-
const errorCallback =
|
|
2247
|
+
const errorCallback = React2.useRef(onError), changedCallback = React2.useRef(onChanged);
|
|
1597
2248
|
errorCallback.current = onError;
|
|
1598
2249
|
changedCallback.current = onChanged;
|
|
1599
2250
|
async function run(task) {
|
|
@@ -1683,7 +2334,7 @@ function PlanChangeFlow({
|
|
|
1683
2334
|
}
|
|
1684
2335
|
return value;
|
|
1685
2336
|
}
|
|
1686
|
-
|
|
2337
|
+
React2.useEffect(() => {
|
|
1687
2338
|
inFlight.current?.abort();
|
|
1688
2339
|
inFlight.current = null;
|
|
1689
2340
|
if (owner.controller.signal.aborted)
|
|
@@ -1701,7 +2352,7 @@ function PlanChangeFlow({
|
|
|
1701
2352
|
owner.controller.abort();
|
|
1702
2353
|
};
|
|
1703
2354
|
}, [owner]);
|
|
1704
|
-
|
|
2355
|
+
React2.useEffect(() => {
|
|
1705
2356
|
if (previousRefresh.current === refreshKey) return;
|
|
1706
2357
|
previousRefresh.current = refreshKey;
|
|
1707
2358
|
let stopped = false;
|
|
@@ -1722,7 +2373,7 @@ function PlanChangeFlow({
|
|
|
1722
2373
|
clearTimeout(timer);
|
|
1723
2374
|
};
|
|
1724
2375
|
}, [refreshKey, owner]);
|
|
1725
|
-
|
|
2376
|
+
React2.useEffect(() => {
|
|
1726
2377
|
if (!state || state.checkoutStatus === "needs_review" || !paymentPending(state) && !pending.current?.accepted)
|
|
1727
2378
|
return;
|
|
1728
2379
|
const timer = setInterval(() => {
|
|
@@ -1853,7 +2504,7 @@ function PlanChangeFlow({
|
|
|
1853
2504
|
await reconcile(signal);
|
|
1854
2505
|
});
|
|
1855
2506
|
const selected = state?.eligiblePlans.find((plan) => plan.code === target);
|
|
1856
|
-
const
|
|
2507
|
+
const money3 = (amount) => formatMinorAmount(
|
|
1857
2508
|
amount,
|
|
1858
2509
|
quote?.currency ?? state?.currentPlan.currency ?? "USD",
|
|
1859
2510
|
locale
|
|
@@ -1861,7 +2512,7 @@ function PlanChangeFlow({
|
|
|
1861
2512
|
const blocked = Boolean(
|
|
1862
2513
|
state?.endingAt || state?.pendingChange || state && paymentPending(state) || state?.blockedReason || pending.current?.accepted
|
|
1863
2514
|
);
|
|
1864
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", {
|
|
2515
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ...root("planChangeControl", void 0, "nozle-plan-change"), children: [
|
|
1865
2516
|
/* @__PURE__ */ jsxRuntime.jsx("style", { nonce, children: styles3 }),
|
|
1866
2517
|
!state ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1867
2518
|
/* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", children: "Loading plan changes\u2026" }),
|
|
@@ -1959,6 +2610,7 @@ function PlanChangeFlow({
|
|
|
1959
2610
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1960
2611
|
UpgradeModal,
|
|
1961
2612
|
{
|
|
2613
|
+
appearance,
|
|
1962
2614
|
isOpen: mode !== null,
|
|
1963
2615
|
planCode: target,
|
|
1964
2616
|
title: mode === "withdraw" ? "Keep your current plan?" : mode === "choose" ? "Change plan" : `Confirm ${quote?.operation ?? "plan change"}`,
|
|
@@ -1967,42 +2619,55 @@ function PlanChangeFlow({
|
|
|
1967
2619
|
confirmDisabled: mode === "choose" && (!target || blocked) || mode === "confirm" && !quote,
|
|
1968
2620
|
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
2621
|
onCancel: () => setMode(null),
|
|
2622
|
+
onBack: mode === "confirm" ? () => {
|
|
2623
|
+
setMode("choose");
|
|
2624
|
+
setQuote(void 0);
|
|
2625
|
+
setFailure(void 0);
|
|
2626
|
+
} : void 0,
|
|
1970
2627
|
onSubmit: confirm,
|
|
1971
2628
|
children: [
|
|
1972
2629
|
failure && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", children: failure }),
|
|
1973
|
-
mode === "choose" && /* @__PURE__ */ jsxRuntime.
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
2630
|
+
mode === "choose" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nzpc-plans", role: "radiogroup", "aria-label": "Choose a plan", children: state.eligiblePlans.map((plan) => {
|
|
2631
|
+
const details = catalog.data?.plans.find((entry) => entry.code === plan.code);
|
|
2632
|
+
const features = details ? planFeatures(details) : [];
|
|
2633
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2634
|
+
"label",
|
|
1977
2635
|
{
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
setTarget(event.target.value);
|
|
1981
|
-
setQuote(void 0);
|
|
1982
|
-
},
|
|
1983
|
-
disabled: busy,
|
|
2636
|
+
className: "nzpc-plan",
|
|
2637
|
+
"data-selected": target === plan.code ? "" : void 0,
|
|
1984
2638
|
children: [
|
|
1985
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
plan.
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
"
|
|
2001
|
-
|
|
2639
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2640
|
+
"input",
|
|
2641
|
+
{
|
|
2642
|
+
type: "radio",
|
|
2643
|
+
name: `nozle-plan-${subscriptionId}`,
|
|
2644
|
+
value: plan.code,
|
|
2645
|
+
checked: target === plan.code,
|
|
2646
|
+
disabled: busy,
|
|
2647
|
+
onChange: () => {
|
|
2648
|
+
setTarget(plan.code);
|
|
2649
|
+
setQuote(void 0);
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
),
|
|
2653
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "nzpc-plan-body", children: [
|
|
2654
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "nzpc-plan-head", children: [
|
|
2655
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "nzpc-plan-name", children: plan.name }),
|
|
2656
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "nzpc-plan-op", children: plan.operation === "upgrade" ? "Upgrade" : "Downgrade" })
|
|
2657
|
+
] }),
|
|
2658
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "nzpc-plan-price", children: [
|
|
2659
|
+
formatMinorAmount(plan.amountCents, plan.currency, locale),
|
|
2660
|
+
" / ",
|
|
2661
|
+
plan.interval
|
|
2662
|
+
] }),
|
|
2663
|
+
details?.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "nzpc-plan-muted", children: details.description }),
|
|
2664
|
+
features.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("ul", { children: features.map((feature) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: feature }, feature)) })
|
|
2665
|
+
] })
|
|
2002
2666
|
]
|
|
2003
|
-
}
|
|
2004
|
-
|
|
2005
|
-
|
|
2667
|
+
},
|
|
2668
|
+
plan.code
|
|
2669
|
+
);
|
|
2670
|
+
}) }),
|
|
2006
2671
|
mode === "withdraw" && /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
|
|
2007
2672
|
"Remove the scheduled change to ",
|
|
2008
2673
|
withdrawTarget?.name,
|
|
@@ -2031,19 +2696,19 @@ function PlanChangeFlow({
|
|
|
2031
2696
|
/* @__PURE__ */ jsxRuntime.jsxs("dl", { children: [
|
|
2032
2697
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2033
2698
|
/* @__PURE__ */ jsxRuntime.jsx("dt", { children: "New plan charge" }),
|
|
2034
|
-
/* @__PURE__ */ jsxRuntime.jsx("dd", { children:
|
|
2699
|
+
/* @__PURE__ */ jsxRuntime.jsx("dd", { children: money3(quote.debitAmountCents) })
|
|
2035
2700
|
] }),
|
|
2036
2701
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2037
2702
|
/* @__PURE__ */ jsxRuntime.jsx("dt", { children: "Credits applied" }),
|
|
2038
|
-
/* @__PURE__ */ jsxRuntime.jsx("dd", { children:
|
|
2703
|
+
/* @__PURE__ */ jsxRuntime.jsx("dd", { children: money3(quote.creditAmountCents) })
|
|
2039
2704
|
] }),
|
|
2040
2705
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2041
2706
|
/* @__PURE__ */ jsxRuntime.jsx("dt", { children: "Due now" }),
|
|
2042
|
-
/* @__PURE__ */ jsxRuntime.jsx("dd", { children:
|
|
2707
|
+
/* @__PURE__ */ jsxRuntime.jsx("dd", { children: money3(quote.amountDueNowCents) })
|
|
2043
2708
|
] }),
|
|
2044
2709
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2045
2710
|
/* @__PURE__ */ jsxRuntime.jsx("dt", { children: "Due at effective date" }),
|
|
2046
|
-
/* @__PURE__ */ jsxRuntime.jsx("dd", { children:
|
|
2711
|
+
/* @__PURE__ */ jsxRuntime.jsx("dd", { children: money3(quote.amountDueAtEffectiveCents) })
|
|
2047
2712
|
] })
|
|
2048
2713
|
] }),
|
|
2049
2714
|
/* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
|
|
@@ -2062,6 +2727,19 @@ function PlanChangeFlow({
|
|
|
2062
2727
|
] })
|
|
2063
2728
|
] });
|
|
2064
2729
|
}
|
|
2730
|
+
var PortalAppearance = React2.createContext({
|
|
2731
|
+
el: (key) => ({ className: "nozle-" + key })
|
|
2732
|
+
});
|
|
2733
|
+
function Part({
|
|
2734
|
+
as: Tag = "div",
|
|
2735
|
+
part,
|
|
2736
|
+
className,
|
|
2737
|
+
style,
|
|
2738
|
+
...rest
|
|
2739
|
+
}) {
|
|
2740
|
+
const themed = React2.useContext(PortalAppearance).el(part, style, className);
|
|
2741
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Tag, { ...rest, ...themed });
|
|
2742
|
+
}
|
|
2065
2743
|
function asError(error) {
|
|
2066
2744
|
return error instanceof BillingPortalError ? error : new BillingPortalError("request");
|
|
2067
2745
|
}
|
|
@@ -2073,13 +2751,15 @@ function BillingPortal({
|
|
|
2073
2751
|
className = "",
|
|
2074
2752
|
style,
|
|
2075
2753
|
nonce,
|
|
2076
|
-
onError
|
|
2754
|
+
onError,
|
|
2755
|
+
appearance
|
|
2077
2756
|
}) {
|
|
2078
|
-
const
|
|
2079
|
-
const [
|
|
2080
|
-
const
|
|
2757
|
+
const { root, el } = useAppearance(appearance);
|
|
2758
|
+
const [attempt, setAttempt] = React2.useState(0);
|
|
2759
|
+
const [state, setState] = React2.useState();
|
|
2760
|
+
const errorCallback = React2.useRef(onError);
|
|
2081
2761
|
errorCallback.current = onError;
|
|
2082
|
-
|
|
2762
|
+
React2.useEffect(() => {
|
|
2083
2763
|
const controller = new AbortController();
|
|
2084
2764
|
let active = true;
|
|
2085
2765
|
const timer = setTimeout(() => {
|
|
@@ -2111,11 +2791,10 @@ function BillingPortal({
|
|
|
2111
2791
|
}, [createSession, attempt]);
|
|
2112
2792
|
const current = state?.source === createSession && state.attempt === attempt ? state : void 0;
|
|
2113
2793
|
const reconnect = () => setAttempt((value) => value + 1);
|
|
2114
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2794
|
+
return /* @__PURE__ */ jsxRuntime.jsx(PortalAppearance.Provider, { value: { el, appearance }, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2115
2795
|
"div",
|
|
2116
2796
|
{
|
|
2117
|
-
|
|
2118
|
-
style,
|
|
2797
|
+
...root("billingPortal", style, `nozle-billing-portal ${className}`),
|
|
2119
2798
|
"aria-label": "Nozle billing portal",
|
|
2120
2799
|
children: [
|
|
2121
2800
|
/* @__PURE__ */ jsxRuntime.jsx("style", { nonce, children: portalStyles }),
|
|
@@ -2134,15 +2813,15 @@ function BillingPortal({
|
|
|
2134
2813
|
) : current?.error ? /* @__PURE__ */ jsxRuntime.jsx(Failure, { error: current.error, retry: reconnect }) : /* @__PURE__ */ jsxRuntime.jsx(Loading, { label: "Starting billing session\u2026" })
|
|
2135
2814
|
]
|
|
2136
2815
|
}
|
|
2137
|
-
);
|
|
2816
|
+
) });
|
|
2138
2817
|
}
|
|
2139
|
-
function
|
|
2140
|
-
const [attempt, setAttempt] =
|
|
2818
|
+
function useQuery4(client, query, variables, onError, retainDuringRefresh = false) {
|
|
2819
|
+
const [attempt, setAttempt] = React2.useState(0);
|
|
2141
2820
|
const key = JSON.stringify(variables);
|
|
2142
|
-
const [state, setState] =
|
|
2143
|
-
const callback =
|
|
2821
|
+
const [state, setState] = React2.useState();
|
|
2822
|
+
const callback = React2.useRef(onError);
|
|
2144
2823
|
callback.current = onError;
|
|
2145
|
-
|
|
2824
|
+
React2.useEffect(() => {
|
|
2146
2825
|
const controller = new AbortController();
|
|
2147
2826
|
client.request(query, JSON.parse(key), controller.signal).then((data) => {
|
|
2148
2827
|
if (!controller.signal.aborted)
|
|
@@ -2164,10 +2843,10 @@ function useQuery(client, query, variables, onError, retainDuringRefresh = false
|
|
|
2164
2843
|
};
|
|
2165
2844
|
}
|
|
2166
2845
|
function useAction(onError) {
|
|
2167
|
-
const [pending, setPending] =
|
|
2168
|
-
const [error, setError] =
|
|
2169
|
-
const controller =
|
|
2170
|
-
|
|
2846
|
+
const [pending, setPending] = React2.useState(false);
|
|
2847
|
+
const [error, setError] = React2.useState();
|
|
2848
|
+
const controller = React2.useRef(null);
|
|
2849
|
+
React2.useEffect(() => () => controller.current?.abort(), []);
|
|
2171
2850
|
return {
|
|
2172
2851
|
pending,
|
|
2173
2852
|
error,
|
|
@@ -2201,12 +2880,12 @@ function Failure({
|
|
|
2201
2880
|
reconnect
|
|
2202
2881
|
}) {
|
|
2203
2882
|
const action = error.code === "unauthorized" ? reconnect ?? retry : retry;
|
|
2204
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2883
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(Part, { role: "alert", className: "nzp-error", part: "billingPortal__error", children: [
|
|
2205
2884
|
/* @__PURE__ */ jsxRuntime.jsx("span", { children: error.message }),
|
|
2206
2885
|
action && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: action, children: error.code === "unauthorized" ? "Reconnect" : "Try again" })
|
|
2207
2886
|
] });
|
|
2208
2887
|
}
|
|
2209
|
-
function
|
|
2888
|
+
function money2(cents, currency, locale) {
|
|
2210
2889
|
const formatter = new Intl.NumberFormat(locale, {
|
|
2211
2890
|
style: "currency",
|
|
2212
2891
|
currency
|
|
@@ -2233,20 +2912,20 @@ var intervals = {
|
|
|
2233
2912
|
};
|
|
2234
2913
|
function Portal(props) {
|
|
2235
2914
|
const { client, locale, reconnect, onError } = props;
|
|
2236
|
-
const { data, error, retry } =
|
|
2915
|
+
const { data, error, retry } = useQuery4(
|
|
2237
2916
|
client,
|
|
2238
2917
|
identityQuery,
|
|
2239
2918
|
{},
|
|
2240
2919
|
onError
|
|
2241
2920
|
);
|
|
2242
|
-
const [view, setView] =
|
|
2243
|
-
const [notice, setNotice] =
|
|
2921
|
+
const [view, setView] = React2.useState({ kind: "overview" });
|
|
2922
|
+
const [notice, setNotice] = React2.useState("");
|
|
2244
2923
|
const back = () => setView({ kind: "overview" });
|
|
2245
2924
|
if (error)
|
|
2246
2925
|
return /* @__PURE__ */ jsxRuntime.jsx(Failure, { error, retry, reconnect });
|
|
2247
2926
|
if (!data) return /* @__PURE__ */ jsxRuntime.jsx(Loading, {});
|
|
2248
2927
|
const customer = data.customerPortalUser;
|
|
2249
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2928
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(NozleBranding, { show: data.customerPortalOrganization.nozleBranding === true, children: [
|
|
2250
2929
|
/* @__PURE__ */ jsxRuntime.jsxs("header", { children: [
|
|
2251
2930
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nzp-row", children: [
|
|
2252
2931
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "nzp-muted", children: data.customerPortalOrganization.name }),
|
|
@@ -2255,7 +2934,7 @@ function Portal(props) {
|
|
|
2255
2934
|
/* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Billing portal" }),
|
|
2256
2935
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "nzp-muted", children: customer.name || [customer.firstname, customer.lastname].filter(Boolean).join(" ") || customer.email || "Your account" })
|
|
2257
2936
|
] }),
|
|
2258
|
-
notice && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", children: notice }),
|
|
2937
|
+
notice && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "nzp-notice", role: "status", children: notice }),
|
|
2259
2938
|
view.kind !== "overview" && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: back, children: "\u2190 Back to overview" }),
|
|
2260
2939
|
view.kind === "overview" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2261
2940
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -2273,7 +2952,7 @@ function Portal(props) {
|
|
|
2273
2952
|
onWallet: (wallet) => setView({ kind: "wallet", wallet })
|
|
2274
2953
|
}
|
|
2275
2954
|
),
|
|
2276
|
-
/* @__PURE__ */ jsxRuntime.jsxs("section",
|
|
2955
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Part, { as: "section", part: "billingPortal__section", "aria-label": "Billing information", children: [
|
|
2277
2956
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nzp-row", children: [
|
|
2278
2957
|
/* @__PURE__ */ jsxRuntime.jsx("h3", { children: "Billing information" }),
|
|
2279
2958
|
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => setView({ kind: "edit" }), children: "Edit billing information" })
|
|
@@ -2343,8 +3022,7 @@ function Portal(props) {
|
|
|
2343
3022
|
back();
|
|
2344
3023
|
}
|
|
2345
3024
|
}
|
|
2346
|
-
)
|
|
2347
|
-
/* @__PURE__ */ jsxRuntime.jsx("footer", { children: "Powered by Nozle" })
|
|
3025
|
+
)
|
|
2348
3026
|
] });
|
|
2349
3027
|
}
|
|
2350
3028
|
function Pager({
|
|
@@ -2391,17 +3069,20 @@ function Subscriptions({
|
|
|
2391
3069
|
planChangeActions,
|
|
2392
3070
|
nonce
|
|
2393
3071
|
}) {
|
|
2394
|
-
const
|
|
2395
|
-
const [
|
|
2396
|
-
const
|
|
3072
|
+
const portalAppearance = React2.useContext(PortalAppearance).appearance;
|
|
3073
|
+
const [page, setPage] = React2.useState(1);
|
|
3074
|
+
const [updated, setUpdated] = React2.useState({});
|
|
3075
|
+
const { data, error, retry } = useQuery4(client, subscriptionsQuery, { page }, onError, true);
|
|
2397
3076
|
const result = data?.customerPortalSubscriptions;
|
|
2398
|
-
|
|
2399
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("section",
|
|
3077
|
+
React2.useEffect(() => setUpdated({}), [result]);
|
|
3078
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(Part, { as: "section", part: "billingPortal__section", "aria-label": "Subscriptions", children: [
|
|
2400
3079
|
/* @__PURE__ */ jsxRuntime.jsx("h3", { children: "Subscriptions" }),
|
|
2401
3080
|
error ? /* @__PURE__ */ jsxRuntime.jsx(Failure, { error, retry, reconnect }) : !result ? /* @__PURE__ */ jsxRuntime.jsx(Loading, {}) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2402
3081
|
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
|
-
|
|
3082
|
+
Part,
|
|
2404
3083
|
{
|
|
3084
|
+
as: "article",
|
|
3085
|
+
part: "billingPortal__card",
|
|
2405
3086
|
className: "nzp-card",
|
|
2406
3087
|
"data-subscription-id": subscription.externalId,
|
|
2407
3088
|
children: [
|
|
@@ -2410,7 +3091,7 @@ function Subscriptions({
|
|
|
2410
3091
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "nzp-badge", children: subscription.endingAt ? "Scheduled to end" : "Active" })
|
|
2411
3092
|
] }),
|
|
2412
3093
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nzp-price", children: [
|
|
2413
|
-
|
|
3094
|
+
money2(
|
|
2414
3095
|
subscription.plan.amountCents,
|
|
2415
3096
|
subscription.plan.amountCurrency,
|
|
2416
3097
|
locale
|
|
@@ -2422,25 +3103,15 @@ function Subscriptions({
|
|
|
2422
3103
|
intervals[subscription.plan.interval] || subscription.plan.interval
|
|
2423
3104
|
] })
|
|
2424
3105
|
] }),
|
|
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
|
-
}
|
|
3106
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "nzp-muted", children: [
|
|
3107
|
+
"Base price, excluding tax and usage",
|
|
3108
|
+
/* @__PURE__ */ jsxRuntime.jsx("br", {}),
|
|
3109
|
+
"Current period ends",
|
|
3110
|
+
" ",
|
|
3111
|
+
date(
|
|
3112
|
+
subscription.currentBillingPeriodEndingAt,
|
|
3113
|
+
locale,
|
|
3114
|
+
timezone
|
|
2444
3115
|
)
|
|
2445
3116
|
] }),
|
|
2446
3117
|
subscription.endingAt && /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
|
|
@@ -2453,48 +3124,60 @@ function Subscriptions({
|
|
|
2453
3124
|
" ",
|
|
2454
3125
|
subscription.nextSubscription.plan.name
|
|
2455
3126
|
] }),
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
3127
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nzp-card-actions", children: [
|
|
3128
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3129
|
+
"button",
|
|
3130
|
+
{
|
|
3131
|
+
type: "button",
|
|
3132
|
+
onClick: () => onUsage(subscription),
|
|
3133
|
+
children: "View usage"
|
|
3134
|
+
}
|
|
3135
|
+
),
|
|
3136
|
+
planChangeActions && subscription.externalId && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3137
|
+
PlanChangeControl,
|
|
3138
|
+
{
|
|
3139
|
+
subscriptionId: subscription.externalId,
|
|
3140
|
+
refreshKey: `${subscription.id}:${subscription.endingAt ?? ""}:${subscription.nextSubscription?.id ?? ""}:${subscription.plan.code}`,
|
|
3141
|
+
actions: planChangeActions,
|
|
3142
|
+
appearance: portalAppearance,
|
|
3143
|
+
locale,
|
|
3144
|
+
timezone,
|
|
3145
|
+
nonce,
|
|
3146
|
+
onError,
|
|
3147
|
+
onChanged: retry
|
|
3148
|
+
}
|
|
3149
|
+
),
|
|
3150
|
+
cancellationActions && subscription.externalId && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3151
|
+
CancellationControl,
|
|
3152
|
+
{
|
|
3153
|
+
subscription: cancellationState(subscription),
|
|
3154
|
+
actions: cancellationActions,
|
|
3155
|
+
appearance: portalAppearance,
|
|
3156
|
+
locale,
|
|
3157
|
+
timezone,
|
|
3158
|
+
nonce,
|
|
3159
|
+
onError,
|
|
3160
|
+
onChanged: retry,
|
|
3161
|
+
refresh: async (signal) => {
|
|
3162
|
+
const result2 = await client.request(
|
|
3163
|
+
subscriptionQuery,
|
|
3164
|
+
{ id: subscription.id },
|
|
3165
|
+
signal
|
|
3166
|
+
);
|
|
3167
|
+
if (!signal.aborted && result2.customerPortalSubscription) {
|
|
3168
|
+
const fresh = result2.customerPortalSubscription;
|
|
3169
|
+
setUpdated((previous) => ({
|
|
3170
|
+
...previous,
|
|
3171
|
+
[fresh.id]: fresh
|
|
3172
|
+
}));
|
|
3173
|
+
}
|
|
3174
|
+
return result2.customerPortalSubscription ? cancellationState(
|
|
3175
|
+
result2.customerPortalSubscription
|
|
3176
|
+
) : null;
|
|
2478
3177
|
}
|
|
2479
|
-
return result2.customerPortalSubscription ? cancellationState(
|
|
2480
|
-
result2.customerPortalSubscription
|
|
2481
|
-
) : null;
|
|
2482
3178
|
}
|
|
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
|
-
)
|
|
3179
|
+
)
|
|
3180
|
+
] })
|
|
2498
3181
|
]
|
|
2499
3182
|
},
|
|
2500
3183
|
subscription.externalId || subscription.id
|
|
@@ -2528,9 +3211,9 @@ function UsageDetails({
|
|
|
2528
3211
|
subscription,
|
|
2529
3212
|
timezone
|
|
2530
3213
|
}) {
|
|
2531
|
-
const { data, error, retry } =
|
|
3214
|
+
const { data, error, retry } = useQuery4(client, usageQuery, { subscriptionId: subscription.id }, onError);
|
|
2532
3215
|
const usage = data?.customerPortalCustomerUsage;
|
|
2533
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("section",
|
|
3216
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(Part, { as: "section", part: "billingPortal__section", "aria-label": "Subscription usage", children: [
|
|
2534
3217
|
/* @__PURE__ */ jsxRuntime.jsxs("h3", { children: [
|
|
2535
3218
|
planName(subscription),
|
|
2536
3219
|
" \xB7 Usage"
|
|
@@ -2542,18 +3225,18 @@ function UsageDetails({
|
|
|
2542
3225
|
" ",
|
|
2543
3226
|
date(usage.toDatetime, locale, timezone)
|
|
2544
3227
|
] }),
|
|
2545
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "nzp-price", children:
|
|
3228
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "nzp-price", children: money2(usage.amountCents, usage.currency, locale) }),
|
|
2546
3229
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "nzp-muted", children: "Current usage, excluding tax" }),
|
|
2547
|
-
usage.chargesUsage.length ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
3230
|
+
usage.chargesUsage.length ? /* @__PURE__ */ jsxRuntime.jsx(Part, { className: "nzp-table", part: "billingPortal__table", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { children: [
|
|
2548
3231
|
/* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
|
|
2549
3232
|
/* @__PURE__ */ jsxRuntime.jsx("th", { children: "Feature" }),
|
|
2550
|
-
/* @__PURE__ */ jsxRuntime.jsx("th", { children: "Units" }),
|
|
2551
|
-
/* @__PURE__ */ jsxRuntime.jsx("th", { children: "Amount" })
|
|
3233
|
+
/* @__PURE__ */ jsxRuntime.jsx("th", { className: "nzp-num", children: "Units" }),
|
|
3234
|
+
/* @__PURE__ */ jsxRuntime.jsx("th", { className: "nzp-num", children: "Amount" })
|
|
2552
3235
|
] }) }),
|
|
2553
3236
|
/* @__PURE__ */ jsxRuntime.jsx("tbody", { children: usage.chargesUsage.map((charge, index) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
|
|
2554
3237
|
/* @__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:
|
|
3238
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { className: "nzp-num", children: charge.units }),
|
|
3239
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { className: "nzp-num", children: money2(charge.amountCents, usage.currency, locale) })
|
|
2557
3240
|
] }, `${charge.id}-${index}`)) })
|
|
2558
3241
|
] }) }) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "nzp-empty", children: "No usage recorded for this billing period." })
|
|
2559
3242
|
] })
|
|
@@ -2561,27 +3244,33 @@ function UsageDetails({
|
|
|
2561
3244
|
}
|
|
2562
3245
|
function Invoices(props) {
|
|
2563
3246
|
const { client, locale, reconnect, onError } = props;
|
|
2564
|
-
const [search, setSearch] =
|
|
2565
|
-
const [filter, setFilter] =
|
|
2566
|
-
const { data, error, retry } =
|
|
3247
|
+
const [search, setSearch] = React2.useState("");
|
|
3248
|
+
const [filter, setFilter] = React2.useState({ page: 1, searchTerm: "" });
|
|
3249
|
+
const { data, error, retry } = useQuery4(client, invoicesQuery, filter, onError);
|
|
2567
3250
|
const result = data?.customerPortalInvoices;
|
|
2568
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("section",
|
|
3251
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(Part, { as: "section", part: "billingPortal__section", "aria-label": "Invoice history", children: [
|
|
2569
3252
|
/* @__PURE__ */ jsxRuntime.jsx("h3", { children: "Invoice history" }),
|
|
2570
3253
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2571
3254
|
"form",
|
|
2572
3255
|
{
|
|
2573
3256
|
className: "nzp-actions",
|
|
3257
|
+
role: "search",
|
|
2574
3258
|
onSubmit: (event) => {
|
|
2575
3259
|
event.preventDefault();
|
|
2576
3260
|
setFilter({ page: 1, searchTerm: search.trim() });
|
|
2577
3261
|
},
|
|
2578
3262
|
children: [
|
|
2579
|
-
/* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
|
|
2580
|
-
"
|
|
3263
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "nzp-search", children: [
|
|
3264
|
+
/* @__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: [
|
|
3265
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "11", cy: "11", r: "7" }),
|
|
3266
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "m20 20-3.5-3.5" })
|
|
3267
|
+
] }),
|
|
2581
3268
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2582
3269
|
"input",
|
|
2583
3270
|
{
|
|
2584
3271
|
type: "search",
|
|
3272
|
+
"aria-label": "Search invoices",
|
|
3273
|
+
placeholder: "Search invoices",
|
|
2585
3274
|
value: search,
|
|
2586
3275
|
onChange: (event) => setSearch(event.target.value)
|
|
2587
3276
|
}
|
|
@@ -2592,18 +3281,18 @@ function Invoices(props) {
|
|
|
2592
3281
|
}
|
|
2593
3282
|
),
|
|
2594
3283
|
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(
|
|
3284
|
+
!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
3285
|
/* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
|
|
2597
3286
|
/* @__PURE__ */ jsxRuntime.jsx("th", { children: "Invoice" }),
|
|
2598
3287
|
/* @__PURE__ */ jsxRuntime.jsx("th", { children: "Date" }),
|
|
2599
|
-
/* @__PURE__ */ jsxRuntime.jsx("th", { children: "Amount" }),
|
|
3288
|
+
/* @__PURE__ */ jsxRuntime.jsx("th", { className: "nzp-num", children: "Amount" }),
|
|
2600
3289
|
/* @__PURE__ */ jsxRuntime.jsx("th", { children: "Status" }),
|
|
2601
3290
|
/* @__PURE__ */ jsxRuntime.jsx("th", { children: "PDF" })
|
|
2602
3291
|
] }) }),
|
|
2603
3292
|
/* @__PURE__ */ jsxRuntime.jsx("tbody", { children: result.collection.map((invoice) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
|
|
2604
3293
|
/* @__PURE__ */ jsxRuntime.jsx("td", { children: invoice.number }),
|
|
2605
3294
|
/* @__PURE__ */ jsxRuntime.jsx("td", { children: date(invoice.issuingDate, locale) }),
|
|
2606
|
-
/* @__PURE__ */ jsxRuntime.jsx("td", { children:
|
|
3295
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { className: "nzp-num", children: money2(
|
|
2607
3296
|
invoice.totalAmountCents,
|
|
2608
3297
|
invoice.currency,
|
|
2609
3298
|
locale
|
|
@@ -2629,8 +3318,8 @@ function InvoiceDownload({
|
|
|
2629
3318
|
onError,
|
|
2630
3319
|
reconnect
|
|
2631
3320
|
}) {
|
|
2632
|
-
const [url, setUrl] =
|
|
2633
|
-
const [generating, setGenerating] =
|
|
3321
|
+
const [url, setUrl] = React2.useState("");
|
|
3322
|
+
const [generating, setGenerating] = React2.useState(false);
|
|
2634
3323
|
const action = useAction(onError);
|
|
2635
3324
|
const download = () => action.run(async (signal) => {
|
|
2636
3325
|
const data = await client.request(downloadInvoiceMutation, { input: { id: invoice.id } }, signal);
|
|
@@ -2688,7 +3377,7 @@ function CustomerForm({
|
|
|
2688
3377
|
onSaved,
|
|
2689
3378
|
onCancel
|
|
2690
3379
|
}) {
|
|
2691
|
-
const [values, setValues] =
|
|
3380
|
+
const [values, setValues] = React2.useState(
|
|
2692
3381
|
() => Object.fromEntries(
|
|
2693
3382
|
customerFields.map(([key]) => [key, customer[key] ?? ""])
|
|
2694
3383
|
)
|
|
@@ -2707,7 +3396,7 @@ function CustomerForm({
|
|
|
2707
3396
|
if (!signal.aborted) onSaved();
|
|
2708
3397
|
});
|
|
2709
3398
|
};
|
|
2710
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("section",
|
|
3399
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(Part, { as: "section", part: "billingPortal__section", "aria-label": "Edit billing information", children: [
|
|
2711
3400
|
/* @__PURE__ */ jsxRuntime.jsx("h3", { children: "Edit billing information" }),
|
|
2712
3401
|
/* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: submit, children: [
|
|
2713
3402
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "nzp-fields", children: customerFields.map(([key, label]) => /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
|
|
@@ -2748,16 +3437,16 @@ function Wallets({
|
|
|
2748
3437
|
onError,
|
|
2749
3438
|
onWallet
|
|
2750
3439
|
}) {
|
|
2751
|
-
const [page, setPage] =
|
|
2752
|
-
const { data, error, retry } =
|
|
3440
|
+
const [page, setPage] = React2.useState(1);
|
|
3441
|
+
const { data, error, retry } = useQuery4(client, walletsQuery, { page }, onError);
|
|
2753
3442
|
const result = data?.customerPortalWallets;
|
|
2754
3443
|
if (result && !result.collection.length) return null;
|
|
2755
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("section",
|
|
3444
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(Part, { as: "section", part: "billingPortal__section", "aria-label": "Wallets", children: [
|
|
2756
3445
|
/* @__PURE__ */ jsxRuntime.jsx("h3", { children: "Wallets" }),
|
|
2757
3446
|
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",
|
|
3447
|
+
/* @__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
3448
|
/* @__PURE__ */ jsxRuntime.jsx("h3", { children: wallet.name || "Prepaid credits" }),
|
|
2760
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "nzp-price", children:
|
|
3449
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "nzp-price", children: money2(wallet.balanceCents, wallet.currency, locale) }),
|
|
2761
3450
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nzp-row", children: [
|
|
2762
3451
|
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "nzp-muted", children: [
|
|
2763
3452
|
wallet.creditsBalance,
|
|
@@ -2788,7 +3477,7 @@ function WalletTopUp({
|
|
|
2788
3477
|
onError,
|
|
2789
3478
|
onCreated
|
|
2790
3479
|
}) {
|
|
2791
|
-
const [credits, setCredits] =
|
|
3480
|
+
const [credits, setCredits] = React2.useState("");
|
|
2792
3481
|
const action = useAction(onError);
|
|
2793
3482
|
const digits = new Intl.NumberFormat(locale, {
|
|
2794
3483
|
style: "currency",
|
|
@@ -2802,7 +3491,7 @@ function WalletTopUp({
|
|
|
2802
3491
|
amountCents,
|
|
2803
3492
|
String(wallet.paidTopUpMaxAmountCents)
|
|
2804
3493
|
) <= 0);
|
|
2805
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("section",
|
|
3494
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(Part, { as: "section", part: "billingPortal__section", "aria-label": "Add credits", children: [
|
|
2806
3495
|
/* @__PURE__ */ jsxRuntime.jsxs("h3", { children: [
|
|
2807
3496
|
"Add credits \xB7 ",
|
|
2808
3497
|
wallet.name || "Wallet"
|
|
@@ -2839,19 +3528,19 @@ function WalletTopUp({
|
|
|
2839
3528
|
)
|
|
2840
3529
|
] }) }),
|
|
2841
3530
|
/* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
|
|
2842
|
-
amountCents !== null ?
|
|
3531
|
+
amountCents !== null ? money2(amountCents, wallet.currency, locale) : "\u2014",
|
|
2843
3532
|
" ",
|
|
2844
3533
|
"excluding tax"
|
|
2845
3534
|
] }),
|
|
2846
3535
|
wallet.paidTopUpMinAmountCents != null && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "nzp-muted", children: [
|
|
2847
3536
|
"Minimum",
|
|
2848
3537
|
" ",
|
|
2849
|
-
|
|
3538
|
+
money2(wallet.paidTopUpMinAmountCents, wallet.currency, locale)
|
|
2850
3539
|
] }),
|
|
2851
3540
|
wallet.paidTopUpMaxAmountCents != null && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "nzp-muted", children: [
|
|
2852
3541
|
"Maximum",
|
|
2853
3542
|
" ",
|
|
2854
|
-
|
|
3543
|
+
money2(wallet.paidTopUpMaxAmountCents, wallet.currency, locale)
|
|
2855
3544
|
] }),
|
|
2856
3545
|
action.error && /* @__PURE__ */ jsxRuntime.jsx(Failure, { error: action.error, reconnect }),
|
|
2857
3546
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "nzp-actions", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -2868,45 +3557,119 @@ function WalletTopUp({
|
|
|
2868
3557
|
)
|
|
2869
3558
|
] });
|
|
2870
3559
|
}
|
|
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
|
-
|
|
3560
|
+
function useBilling() {
|
|
3561
|
+
const { billingFetch, billingEndpoint, queryClient } = useBillingContext();
|
|
3562
|
+
if (!billingFetch || !billingEndpoint) {
|
|
3563
|
+
throw new Error("Billing hooks need BillingProvider billingEndpoint, a backend route served by createBillingHandler");
|
|
3564
|
+
}
|
|
3565
|
+
return { fetchRoute: billingFetch, queryClient, endpoint: billingEndpoint };
|
|
3566
|
+
}
|
|
3567
|
+
function useSubscription() {
|
|
3568
|
+
const { fetchRoute, queryClient, endpoint } = useBilling();
|
|
3569
|
+
return reactQuery.useQuery(
|
|
3570
|
+
{ queryKey: ["nozle", endpoint, "subscription"], queryFn: () => fetchRoute("subscription") },
|
|
3571
|
+
queryClient
|
|
3572
|
+
);
|
|
3573
|
+
}
|
|
3574
|
+
function useEntitlement(featureCode, options = {}) {
|
|
3575
|
+
const { fetchRoute, queryClient, endpoint } = useBilling();
|
|
3576
|
+
return reactQuery.useQuery(
|
|
3577
|
+
{
|
|
3578
|
+
queryKey: ["nozle", endpoint, "entitlement", featureCode],
|
|
3579
|
+
queryFn: () => fetchRoute("entitlement", { feature: featureCode }),
|
|
3580
|
+
enabled: Boolean(featureCode) && options.enabled !== false,
|
|
3581
|
+
staleTime: 1e4
|
|
3582
|
+
},
|
|
3583
|
+
queryClient
|
|
3584
|
+
);
|
|
3585
|
+
}
|
|
3586
|
+
function useCreditBalance(creditSystemCode) {
|
|
3587
|
+
const { fetchRoute, queryClient, endpoint } = useBilling();
|
|
3588
|
+
return reactQuery.useQuery(
|
|
3589
|
+
{
|
|
3590
|
+
queryKey: ["nozle", endpoint, "credits"],
|
|
3591
|
+
queryFn: () => fetchRoute("credits"),
|
|
3592
|
+
select: (data) => {
|
|
3593
|
+
const balances = data.balances ?? [];
|
|
3594
|
+
if (creditSystemCode) return balances.find((b) => b.credit_system === creditSystemCode) ?? null;
|
|
3595
|
+
return balances.find((b) => Number(b.available) > 0) ?? balances[0] ?? null;
|
|
2894
3596
|
}
|
|
2895
|
-
}
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
3597
|
+
},
|
|
3598
|
+
queryClient
|
|
3599
|
+
);
|
|
3600
|
+
}
|
|
3601
|
+
function useCreditHistory(options = {}) {
|
|
3602
|
+
const { fetchRoute, queryClient, endpoint } = useBilling();
|
|
3603
|
+
const query = reactQuery.useInfiniteQuery(
|
|
3604
|
+
{
|
|
3605
|
+
queryKey: ["nozle", endpoint, "credit-history", options.creditSystemCode, options.limit],
|
|
3606
|
+
queryFn: ({ pageParam }) => fetchRoute("credit-history", {
|
|
3607
|
+
credit_system: options.creditSystemCode,
|
|
3608
|
+
limit: options.limit,
|
|
3609
|
+
cursor: pageParam
|
|
3610
|
+
}),
|
|
3611
|
+
initialPageParam: void 0,
|
|
3612
|
+
getNextPageParam: (page) => page.next_cursor ?? void 0
|
|
3613
|
+
},
|
|
3614
|
+
queryClient
|
|
3615
|
+
);
|
|
3616
|
+
return { ...query, operations: query.data?.pages.flatMap((page) => page.operations) ?? [] };
|
|
3617
|
+
}
|
|
3618
|
+
function useUsage(options = {}) {
|
|
3619
|
+
const { fetchRoute, queryClient, endpoint } = useBilling();
|
|
3620
|
+
const period = options.period ?? "current";
|
|
3621
|
+
return reactQuery.useQuery(
|
|
3622
|
+
{
|
|
3623
|
+
queryKey: ["nozle", endpoint, "usage", period, options.periods],
|
|
3624
|
+
queryFn: () => fetchRoute("usage", {
|
|
3625
|
+
period,
|
|
3626
|
+
periods: options.periods
|
|
3627
|
+
}),
|
|
3628
|
+
select: (data) => period === "past" ? data.periods ?? [] : data.usage ?? null
|
|
3629
|
+
},
|
|
3630
|
+
queryClient
|
|
3631
|
+
);
|
|
3632
|
+
}
|
|
3633
|
+
function useInvoices(options = {}) {
|
|
3634
|
+
const { fetchRoute, queryClient, endpoint } = useBilling();
|
|
3635
|
+
const page = options.page ?? 1;
|
|
3636
|
+
return reactQuery.useQuery(
|
|
3637
|
+
{
|
|
3638
|
+
queryKey: ["nozle", endpoint, "invoices", page],
|
|
3639
|
+
queryFn: () => fetchRoute("invoices", { page })
|
|
3640
|
+
},
|
|
3641
|
+
queryClient
|
|
3642
|
+
);
|
|
3643
|
+
}
|
|
3644
|
+
function useInvoiceDownload() {
|
|
3645
|
+
const { fetchRoute } = useBilling();
|
|
3646
|
+
return React2.useCallback(
|
|
3647
|
+
async (invoiceId) => (await fetchRoute("invoice-download", { id: invoiceId })).url,
|
|
3648
|
+
[fetchRoute]
|
|
3649
|
+
);
|
|
3650
|
+
}
|
|
3651
|
+
function useUpgradePreview(planCode) {
|
|
3652
|
+
const { fetchRoute, queryClient, endpoint } = useBilling();
|
|
3653
|
+
return reactQuery.useQuery(
|
|
3654
|
+
{
|
|
3655
|
+
queryKey: ["nozle", endpoint, "preview", planCode],
|
|
3656
|
+
queryFn: () => fetchRoute("preview", { plan: planCode }),
|
|
3657
|
+
enabled: Boolean(planCode),
|
|
3658
|
+
staleTime: 0
|
|
3659
|
+
},
|
|
3660
|
+
queryClient
|
|
3661
|
+
);
|
|
3662
|
+
}
|
|
3663
|
+
function useBillingRefresh() {
|
|
3664
|
+
const { queryClient } = useBillingContext();
|
|
3665
|
+
return React2.useCallback(() => queryClient.invalidateQueries({ queryKey: ["nozle"] }), [queryClient]);
|
|
2903
3666
|
}
|
|
2904
3667
|
function useCheckout2() {
|
|
2905
3668
|
const { createCheckout } = useBillingContext();
|
|
2906
|
-
const [isLoading, setIsLoading] =
|
|
2907
|
-
const [error, setError] =
|
|
2908
|
-
const [checkout, setCheckout] =
|
|
2909
|
-
const fetchClientSecret =
|
|
3669
|
+
const [isLoading, setIsLoading] = React2.useState(false);
|
|
3670
|
+
const [error, setError] = React2.useState(null);
|
|
3671
|
+
const [checkout, setCheckout] = React2.useState(null);
|
|
3672
|
+
const fetchClientSecret = React2.useCallback(
|
|
2910
3673
|
async (planCode, returnUrl) => {
|
|
2911
3674
|
setIsLoading(true);
|
|
2912
3675
|
setError(null);
|
|
@@ -2947,154 +3710,651 @@ function useCheckout2() {
|
|
|
2947
3710
|
);
|
|
2948
3711
|
return { fetchClientSecret, checkout, isLoading, error };
|
|
2949
3712
|
}
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
hl: v("--nozle-pricing-highlight", v("--nozle-primary", "#6366f1")),
|
|
2966
|
-
border: v("--nozle-pricing-border", v("--nozle-border", "#e5e7eb")),
|
|
2967
|
-
radius: v("--nozle-pricing-radius", v("--nozle-radius", "12px")),
|
|
2968
|
-
text: v("--nozle-foreground", "#111827"),
|
|
2969
|
-
muted: v("--nozle-muted-foreground", "#6b7280"),
|
|
2970
|
-
mutedBg: v("--nozle-muted", "#f3f4f6"),
|
|
2971
|
-
hlFg: v("--nozle-primary-foreground", "#ffffff")
|
|
3713
|
+
|
|
3714
|
+
// src/components/pricing/catalog.ts
|
|
3715
|
+
var PRICING_INTERVALS = [
|
|
3716
|
+
"weekly",
|
|
3717
|
+
"monthly",
|
|
3718
|
+
"quarterly",
|
|
3719
|
+
"semiannual",
|
|
3720
|
+
"yearly"
|
|
3721
|
+
];
|
|
3722
|
+
var INTERVAL_LABELS2 = {
|
|
3723
|
+
weekly: "Weekly",
|
|
3724
|
+
monthly: "Monthly",
|
|
3725
|
+
quarterly: "Quarterly",
|
|
3726
|
+
semiannual: "Every 6 months",
|
|
3727
|
+
yearly: "Annual"
|
|
2972
3728
|
};
|
|
2973
|
-
|
|
2974
|
-
|
|
3729
|
+
var BILLED_WORDS = {
|
|
3730
|
+
weekly: "weekly",
|
|
3731
|
+
monthly: "monthly",
|
|
3732
|
+
quarterly: "quarterly",
|
|
3733
|
+
semiannual: "every 6 months",
|
|
3734
|
+
yearly: "yearly"
|
|
3735
|
+
};
|
|
3736
|
+
var PERIODS_PER_YEAR = {
|
|
3737
|
+
weekly: 52n,
|
|
3738
|
+
monthly: 12n,
|
|
3739
|
+
quarterly: 4n,
|
|
3740
|
+
semiannual: 2n,
|
|
3741
|
+
yearly: 1n
|
|
3742
|
+
};
|
|
3743
|
+
function isPricingInterval(value) {
|
|
3744
|
+
return typeof value === "string" && PRICING_INTERVALS.includes(value);
|
|
2975
3745
|
}
|
|
2976
|
-
function
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
3746
|
+
function intervalRank(interval) {
|
|
3747
|
+
return PRICING_INTERVALS.indexOf(interval);
|
|
3748
|
+
}
|
|
3749
|
+
function normalizePricingInterval(value) {
|
|
3750
|
+
if (value === "annual") return "yearly";
|
|
3751
|
+
return isPricingInterval(value) ? value : null;
|
|
3752
|
+
}
|
|
3753
|
+
function hasPosition(value) {
|
|
3754
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
3755
|
+
}
|
|
3756
|
+
function comparePositions(a, b) {
|
|
3757
|
+
const aSet = hasPosition(a);
|
|
3758
|
+
const bSet = hasPosition(b);
|
|
3759
|
+
if (aSet && bSet) return a - b;
|
|
3760
|
+
if (aSet) return -1;
|
|
3761
|
+
if (bSet) return 1;
|
|
3762
|
+
return 0;
|
|
3763
|
+
}
|
|
3764
|
+
var DEFAULT_LOCALE = "en-US";
|
|
3765
|
+
function resolvePricingLocale(locale) {
|
|
3766
|
+
if (typeof locale !== "string" || locale.trim() === "") return DEFAULT_LOCALE;
|
|
3767
|
+
try {
|
|
3768
|
+
Intl.getCanonicalLocales(locale);
|
|
3769
|
+
return locale;
|
|
3770
|
+
} catch {
|
|
3771
|
+
return DEFAULT_LOCALE;
|
|
3772
|
+
}
|
|
3773
|
+
}
|
|
3774
|
+
function isLive(plan) {
|
|
3775
|
+
return plan.pending_deletion !== true;
|
|
3776
|
+
}
|
|
3777
|
+
function hasFamilies(catalog) {
|
|
3778
|
+
return Array.isArray(catalog.families);
|
|
3779
|
+
}
|
|
3780
|
+
function getPricingGroups(catalog) {
|
|
3781
|
+
const plans = catalog.plans ?? [];
|
|
3782
|
+
if (!hasFamilies(catalog)) {
|
|
3783
|
+
return plans.filter(isLive).map((plan) => ({ key: plan.code, family: null, plans: [plan] }));
|
|
3784
|
+
}
|
|
3785
|
+
const byCode = /* @__PURE__ */ new Map();
|
|
3786
|
+
for (const plan of plans) {
|
|
3787
|
+
if (isLive(plan) && !byCode.has(plan.code)) byCode.set(plan.code, plan);
|
|
3788
|
+
}
|
|
3789
|
+
return catalog.families.map((family, index) => ({ family, index })).filter(({ family }) => family.show_on_pricing !== false).sort(
|
|
3790
|
+
(a, b) => comparePositions(a.family.pricing_position, b.family.pricing_position) || a.index - b.index
|
|
3791
|
+
).map(({ family }) => {
|
|
3792
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3793
|
+
const familyPlans = [];
|
|
3794
|
+
for (const code of family.plan_codes ?? []) {
|
|
3795
|
+
const plan = byCode.get(code);
|
|
3796
|
+
if (plan && !seen.has(code)) {
|
|
3797
|
+
seen.add(code);
|
|
3798
|
+
familyPlans.push(plan);
|
|
3799
|
+
}
|
|
3800
|
+
}
|
|
3801
|
+
return { key: family.code, family, plans: familyPlans };
|
|
3802
|
+
}).filter((group) => group.plans.length > 0);
|
|
3803
|
+
}
|
|
3804
|
+
function toMinor(value) {
|
|
3805
|
+
const raw = String(value);
|
|
3806
|
+
if (!/^-?\d+$/.test(raw)) return null;
|
|
3807
|
+
if (typeof value === "number" && !Number.isSafeInteger(value)) return null;
|
|
3808
|
+
return BigInt(raw);
|
|
3809
|
+
}
|
|
3810
|
+
function divideRounded(numerator, denominator) {
|
|
3811
|
+
if (numerator < 0n) return -divideRounded(-numerator, denominator);
|
|
3812
|
+
return (2n * numerator + denominator) / (2n * denominator);
|
|
3813
|
+
}
|
|
3814
|
+
function perMonthMinor(amount, interval) {
|
|
3815
|
+
return divideRounded(amount * PERIODS_PER_YEAR[interval], 12n);
|
|
3816
|
+
}
|
|
3817
|
+
function fmt(amount, currency, locale) {
|
|
3818
|
+
return formatMinorAmount(String(amount), currency, locale, {
|
|
3819
|
+
trimZeroFraction: true
|
|
2995
3820
|
});
|
|
2996
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", justifyContent: "center", marginBottom: "2.5rem" }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: pill, children: [
|
|
2997
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => onChange(false), style: tab(!isAnnual), "aria-pressed": !isAnnual, children: "Monthly" }),
|
|
2998
|
-
/* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: () => onChange(true), style: tab(isAnnual), "aria-pressed": isAnnual, children: [
|
|
2999
|
-
"Annual",
|
|
3000
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: {
|
|
3001
|
-
marginLeft: "6px",
|
|
3002
|
-
fontSize: "0.7rem",
|
|
3003
|
-
fontWeight: 600,
|
|
3004
|
-
color: isAnnual ? C.hl : C.muted
|
|
3005
|
-
}, children: "Save 20%" })
|
|
3006
|
-
] })
|
|
3007
|
-
] }) });
|
|
3008
3821
|
}
|
|
3009
|
-
function
|
|
3010
|
-
plan
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
const
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3822
|
+
function savingsAgainstMonthly(plan, monthly) {
|
|
3823
|
+
if (!monthly || monthly === plan) return null;
|
|
3824
|
+
if (!isPricingInterval(plan.interval) || plan.interval === "monthly") return null;
|
|
3825
|
+
if (monthly.amount_currency !== plan.amount_currency) return null;
|
|
3826
|
+
const monthlyAmount = toMinor(monthly.amount_cents);
|
|
3827
|
+
const amount = toMinor(plan.amount_cents);
|
|
3828
|
+
if (monthlyAmount === null || amount === null) return null;
|
|
3829
|
+
const base = monthlyAmount * 12n;
|
|
3830
|
+
const cost = amount * PERIODS_PER_YEAR[plan.interval];
|
|
3831
|
+
if (base <= 0n || cost >= base) return null;
|
|
3832
|
+
const percent = Number(divideRounded(100n * (base - cost), base));
|
|
3833
|
+
return percent > 0 ? percent : null;
|
|
3834
|
+
}
|
|
3835
|
+
function monthlyPlanOf(group) {
|
|
3836
|
+
return group.family ? group.plans.find((plan) => plan.interval === "monthly") : void 0;
|
|
3837
|
+
}
|
|
3838
|
+
function getAvailableIntervals(catalog) {
|
|
3839
|
+
const present = /* @__PURE__ */ new Set();
|
|
3840
|
+
for (const group of getPricingGroups(catalog)) {
|
|
3841
|
+
for (const plan of group.plans) present.add(plan.interval);
|
|
3842
|
+
}
|
|
3843
|
+
return PRICING_INTERVALS.filter((interval) => present.has(interval));
|
|
3844
|
+
}
|
|
3845
|
+
function getPricingIntervalOptions(catalog) {
|
|
3846
|
+
const groups = getPricingGroups(catalog);
|
|
3847
|
+
return getAvailableIntervals(catalog).map((interval) => {
|
|
3848
|
+
let best = null;
|
|
3849
|
+
if (interval !== "monthly") {
|
|
3850
|
+
for (const group of groups) {
|
|
3851
|
+
const plan = group.plans.find((p) => p.interval === interval);
|
|
3852
|
+
const saving = plan ? savingsAgainstMonthly(plan, monthlyPlanOf(group)) : null;
|
|
3853
|
+
if (saving !== null && (best === null || saving > best)) best = saving;
|
|
3854
|
+
}
|
|
3026
3855
|
}
|
|
3027
|
-
|
|
3028
|
-
};
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3856
|
+
return { interval, label: INTERVAL_LABELS2[interval], savingsPercent: best };
|
|
3857
|
+
});
|
|
3858
|
+
}
|
|
3859
|
+
function getDefaultInterval(catalog, preferred) {
|
|
3860
|
+
const available = getAvailableIntervals(catalog);
|
|
3861
|
+
const wanted = normalizePricingInterval(preferred);
|
|
3862
|
+
if (wanted && available.includes(wanted)) return wanted;
|
|
3863
|
+
if (available.includes("monthly")) return "monthly";
|
|
3864
|
+
return available[0] ?? null;
|
|
3865
|
+
}
|
|
3866
|
+
function pickPlan(plans, interval) {
|
|
3867
|
+
if (interval === null) return plans[0];
|
|
3868
|
+
const exact = plans.find((plan) => plan.interval === interval);
|
|
3869
|
+
if (exact) return exact;
|
|
3870
|
+
const target = intervalRank(interval);
|
|
3871
|
+
let best = plans[0];
|
|
3872
|
+
let bestScore = Number.POSITIVE_INFINITY;
|
|
3873
|
+
for (const plan of plans) {
|
|
3874
|
+
const rank = intervalRank(plan.interval);
|
|
3875
|
+
if (rank < 0) continue;
|
|
3876
|
+
const score = Math.abs(rank - target) * 2 + (rank > target ? 1 : 0);
|
|
3877
|
+
if (score < bestScore) {
|
|
3878
|
+
best = plan;
|
|
3879
|
+
bestScore = score;
|
|
3880
|
+
}
|
|
3881
|
+
}
|
|
3882
|
+
return best;
|
|
3883
|
+
}
|
|
3884
|
+
function isTrueBoolean(privilege) {
|
|
3885
|
+
const { value } = privilege;
|
|
3886
|
+
if (value === true) return true;
|
|
3887
|
+
return privilege.value_type === "boolean" && typeof value === "string" && value.toLowerCase() === "true";
|
|
3888
|
+
}
|
|
3889
|
+
function formatPrivilegeValue(privilege, numberFormat) {
|
|
3890
|
+
const { value } = privilege;
|
|
3891
|
+
if (value === null || value === void 0) return null;
|
|
3892
|
+
if (isTrueBoolean(privilege)) return null;
|
|
3893
|
+
if (value === false) return "No";
|
|
3894
|
+
if (typeof value === "number") {
|
|
3895
|
+
return Number.isFinite(value) ? numberFormat.format(value) : null;
|
|
3896
|
+
}
|
|
3897
|
+
const text2 = String(value).trim();
|
|
3898
|
+
if (text2 === "") return null;
|
|
3899
|
+
if (privilege.value_type === "boolean" && text2.toLowerCase() === "false") {
|
|
3900
|
+
return "No";
|
|
3901
|
+
}
|
|
3902
|
+
if (privilege.value_type === "integer" && /^-?\d+$/.test(text2)) {
|
|
3903
|
+
const parsed = Number(text2);
|
|
3904
|
+
if (Number.isSafeInteger(parsed)) return numberFormat.format(parsed);
|
|
3905
|
+
}
|
|
3906
|
+
return text2;
|
|
3907
|
+
}
|
|
3908
|
+
function featureLabel(name, entitlement, numberFormat) {
|
|
3909
|
+
const values = (entitlement?.privileges ?? []).map((privilege) => ({
|
|
3910
|
+
privilege,
|
|
3911
|
+
text: formatPrivilegeValue(privilege, numberFormat)
|
|
3912
|
+
})).filter(
|
|
3913
|
+
(entry) => entry.text !== null
|
|
3914
|
+
);
|
|
3915
|
+
if (values.length === 1) return `${name}: ${values[0].text}`;
|
|
3916
|
+
if (values.length > 1) {
|
|
3917
|
+
const parts = values.map(({ privilege, text: text2 }) => {
|
|
3918
|
+
const privilegeName = privilege.name?.trim() || privilege.code;
|
|
3919
|
+
return `${privilegeName}: ${text2}`;
|
|
3920
|
+
});
|
|
3921
|
+
return `${name} (${parts.join(", ")})`;
|
|
3922
|
+
}
|
|
3923
|
+
const limit = entitlement?.usage_limit;
|
|
3924
|
+
if (typeof limit === "number" && Number.isFinite(limit)) {
|
|
3925
|
+
return `${name}: ${numberFormat.format(limit)}`;
|
|
3926
|
+
}
|
|
3927
|
+
return name;
|
|
3928
|
+
}
|
|
3929
|
+
function trimmed(value) {
|
|
3930
|
+
return typeof value === "string" ? value.trim() : "";
|
|
3931
|
+
}
|
|
3932
|
+
function familyFeatures(family, plan, locale) {
|
|
3933
|
+
const numberFormat = new Intl.NumberFormat(locale);
|
|
3934
|
+
const entitlements = /* @__PURE__ */ new Map();
|
|
3935
|
+
for (const entitlement of plan.entitlements ?? []) {
|
|
3936
|
+
if (!entitlements.has(entitlement.code)) {
|
|
3937
|
+
entitlements.set(entitlement.code, entitlement);
|
|
3938
|
+
}
|
|
3939
|
+
}
|
|
3940
|
+
return (family.features ?? []).filter((feature) => feature.show_on_pricing !== false).map((feature, index) => {
|
|
3941
|
+
const entitlement = entitlements.get(feature.code);
|
|
3942
|
+
const name = trimmed(feature.name) || trimmed(entitlement?.name) || feature.code;
|
|
3943
|
+
return { feature, entitlement, index, name };
|
|
3944
|
+
}).sort(
|
|
3945
|
+
(a, b) => comparePositions(a.feature.display_position, b.feature.display_position) || a.name.localeCompare(b.name, locale) || a.index - b.index
|
|
3946
|
+
).map(({ feature, entitlement, name }) => {
|
|
3947
|
+
const label = featureLabel(name, entitlement, numberFormat);
|
|
3948
|
+
const description = trimmed(feature.description);
|
|
3949
|
+
return feature.show_description === true && description ? { label, description } : { label };
|
|
3950
|
+
});
|
|
3951
|
+
}
|
|
3952
|
+
function planCardFeatures(plan) {
|
|
3953
|
+
return planFeatures(plan).map((label) => ({ label }));
|
|
3954
|
+
}
|
|
3955
|
+
function normalizeFeatures(items) {
|
|
3956
|
+
return items.map((item) => typeof item === "string" ? { label: item } : item).filter(
|
|
3957
|
+
(item) => Boolean(item) && typeof item.label === "string"
|
|
3958
|
+
);
|
|
3959
|
+
}
|
|
3960
|
+
function overrideFeatures(overrides, group, plan, cardIndex) {
|
|
3961
|
+
if (!overrides) return null;
|
|
3962
|
+
if (Array.isArray(overrides)) {
|
|
3963
|
+
const entry = overrides[cardIndex];
|
|
3964
|
+
return entry ? normalizeFeatures(entry) : null;
|
|
3965
|
+
}
|
|
3966
|
+
const keys = [group.key, plan.code, ...group.plans.map((p) => p.code)];
|
|
3967
|
+
for (const key of keys) {
|
|
3968
|
+
if (Object.prototype.hasOwnProperty.call(overrides, key)) {
|
|
3969
|
+
return normalizeFeatures(overrides[key] ?? []);
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3972
|
+
return null;
|
|
3973
|
+
}
|
|
3974
|
+
function yearlyMinor(plan) {
|
|
3975
|
+
const amount = toMinor(plan.amount_cents);
|
|
3976
|
+
if (amount === null || !isPricingInterval(plan.interval)) return null;
|
|
3977
|
+
return amount * PERIODS_PER_YEAR[plan.interval];
|
|
3978
|
+
}
|
|
3979
|
+
function planPriceStrings(plan, locale) {
|
|
3980
|
+
const amount = toMinor(plan.amount_cents);
|
|
3981
|
+
return {
|
|
3982
|
+
price: fmt(amount ?? plan.amount_cents, plan.amount_currency, locale),
|
|
3983
|
+
pricePeriod: plan.interval ? `/${intervalLabel(plan.interval)}` : "",
|
|
3984
|
+
billing: "",
|
|
3985
|
+
savingsPercent: null
|
|
3986
|
+
};
|
|
3987
|
+
}
|
|
3988
|
+
function priceStrings(plan, monthly, locale) {
|
|
3989
|
+
const amount = toMinor(plan.amount_cents);
|
|
3990
|
+
const currency = plan.amount_currency;
|
|
3991
|
+
if (!isPricingInterval(plan.interval) || amount === null) {
|
|
3992
|
+
return {
|
|
3993
|
+
price: fmt(amount ?? plan.amount_cents, currency, locale),
|
|
3994
|
+
pricePeriod: "",
|
|
3995
|
+
billing: plan.interval ? `Billed ${plan.interval}` : "",
|
|
3996
|
+
savingsPercent: null
|
|
3997
|
+
};
|
|
3998
|
+
}
|
|
3999
|
+
const price = fmt(perMonthMinor(amount, plan.interval), currency, locale);
|
|
4000
|
+
if (plan.interval === "monthly") {
|
|
4001
|
+
return { price, pricePeriod: "/mo", billing: "Billed monthly", savingsPercent: null };
|
|
4002
|
+
}
|
|
4003
|
+
const savingsPercent = savingsAgainstMonthly(plan, monthly);
|
|
4004
|
+
const billed = `Billed ${fmt(amount, currency, locale)} ${BILLED_WORDS[plan.interval]}`;
|
|
4005
|
+
return {
|
|
4006
|
+
price,
|
|
4007
|
+
pricePeriod: "/mo",
|
|
4008
|
+
billing: savingsPercent === null ? billed : `${billed} \xB7 save ${savingsPercent}%`,
|
|
4009
|
+
savingsPercent
|
|
4010
|
+
};
|
|
4011
|
+
}
|
|
4012
|
+
function getPricingCards(catalog, options = {}) {
|
|
4013
|
+
const locale = resolvePricingLocale(options.locale);
|
|
4014
|
+
const families = hasFamilies(catalog);
|
|
4015
|
+
const requested = normalizePricingInterval(options.interval ?? null);
|
|
4016
|
+
const interval = requested ?? getDefaultInterval(catalog);
|
|
4017
|
+
const contactSales = new Set(options.contactSales ?? []);
|
|
4018
|
+
const isContactSales = (group) => contactSales.has(group.key) || group.plans.some((plan) => contactSales.has(plan.code));
|
|
4019
|
+
let groups = getPricingGroups(catalog);
|
|
4020
|
+
if (!families) {
|
|
4021
|
+
if (options.sort) {
|
|
4022
|
+
const sort = options.sort;
|
|
4023
|
+
groups = groups.map((group, index) => ({ group, index })).sort((a, b) => sort(a.group.plans[0], b.group.plans[0]) || a.index - b.index).map(({ group }) => group);
|
|
4024
|
+
} else {
|
|
4025
|
+
const sales = groups.filter(isContactSales);
|
|
4026
|
+
const byPlan = new Map(groups.map((group) => [group.plans[0], group]));
|
|
4027
|
+
groups = [
|
|
4028
|
+
...sortPlansByPrice(groups.filter((group) => !isContactSales(group)).map((group) => group.plans[0])).map((plan) => byPlan.get(plan)),
|
|
4029
|
+
...sales
|
|
4030
|
+
];
|
|
4031
|
+
}
|
|
4032
|
+
if (requested) {
|
|
4033
|
+
groups = groups.filter(
|
|
4034
|
+
(group) => group.plans[0].interval === requested || isContactSales(group)
|
|
4035
|
+
);
|
|
4036
|
+
}
|
|
4037
|
+
}
|
|
4038
|
+
const highlight = options.highlight ?? null;
|
|
4039
|
+
const highlightedKey = highlight !== null ? groups.find(
|
|
4040
|
+
(group) => group.key === highlight || group.plans.some((plan) => plan.code === highlight)
|
|
4041
|
+
)?.key : groups.find((group) => group.family?.pricing_highlighted === true)?.key;
|
|
4042
|
+
const badge = (family) => options.badge !== void 0 ? options.badge === null ? null : trimmed(options.badge) || null : trimmed(family?.badge_text) || "Most Popular";
|
|
4043
|
+
return groups.map((group, cardIndex) => {
|
|
4044
|
+
const plan = pickPlan(group.plans, interval);
|
|
4045
|
+
const family = group.family;
|
|
4046
|
+
const name = trimmed(family?.name) || plan.name;
|
|
4047
|
+
const description = trimmed(family?.description) || trimmed(plan.description) || null;
|
|
4048
|
+
const highlighted = highlightedKey !== void 0 && highlightedKey === group.key;
|
|
4049
|
+
const features = overrideFeatures(options.features, group, plan, cardIndex) ?? (family ? familyFeatures(family, plan, locale) : planCardFeatures(plan));
|
|
4050
|
+
return {
|
|
4051
|
+
key: group.key,
|
|
4052
|
+
family,
|
|
4053
|
+
plans: group.plans,
|
|
4054
|
+
plan,
|
|
4055
|
+
isFallbackInterval: families && interval !== null && plan.interval !== interval,
|
|
4056
|
+
name,
|
|
4057
|
+
description,
|
|
4058
|
+
...family ? priceStrings(plan, monthlyPlanOf(group), locale) : planPriceStrings(plan, locale),
|
|
4059
|
+
features,
|
|
4060
|
+
highlighted,
|
|
4061
|
+
badge: highlighted ? badge(family) : null,
|
|
4062
|
+
cta: trimmed(family?.cta_text) || `Get ${name}`,
|
|
4063
|
+
enterprise: isContactSales(group)
|
|
4064
|
+
};
|
|
4065
|
+
});
|
|
4066
|
+
}
|
|
4067
|
+
function PanelPlanCard({
|
|
4068
|
+
frame,
|
|
4069
|
+
el,
|
|
4070
|
+
name,
|
|
4071
|
+
description,
|
|
4072
|
+
price,
|
|
4073
|
+
interval,
|
|
4074
|
+
priceNote,
|
|
4075
|
+
features,
|
|
4076
|
+
isCurrent,
|
|
4077
|
+
isHighlighted,
|
|
4078
|
+
filled: filledStyle,
|
|
4079
|
+
highlightLabel,
|
|
4080
|
+
children
|
|
4081
|
+
}) {
|
|
4082
|
+
const filled = isHighlighted && filledStyle;
|
|
4083
|
+
const text2 = filled ? T.highlightForeground : T.foreground;
|
|
4084
|
+
const muted5 = filled ? `color-mix(in srgb, ${T.highlightForeground} 75%, ${T.highlight})` : T.mutedForeground;
|
|
4085
|
+
const edge = isHighlighted ? T.highlight : isCurrent ? T.foreground : T.border;
|
|
4086
|
+
const label = isCurrent ? "Active" : isHighlighted ? highlightLabel : null;
|
|
4087
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4088
|
+
"div",
|
|
4089
|
+
{
|
|
4090
|
+
...frame({
|
|
4091
|
+
display: "flex",
|
|
4092
|
+
flexDirection: "column",
|
|
4093
|
+
overflow: "hidden",
|
|
4094
|
+
...text.body,
|
|
4095
|
+
background: filled ? T.highlight : T.card,
|
|
4096
|
+
color: text2,
|
|
4097
|
+
border: border(edge),
|
|
4098
|
+
borderRadius: T.radius,
|
|
4099
|
+
boxShadow: T.shadow
|
|
4100
|
+
}),
|
|
4101
|
+
children: [
|
|
4102
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
4103
|
+
"div",
|
|
4104
|
+
{
|
|
4105
|
+
...el("planCard__header", {
|
|
4106
|
+
display: "flex",
|
|
4107
|
+
alignItems: "center",
|
|
4108
|
+
justifyContent: "space-between",
|
|
4109
|
+
gap: "0.75rem",
|
|
4110
|
+
padding: "0.875rem 1.25rem",
|
|
4111
|
+
borderBottom: border(filled ? `color-mix(in srgb, ${T.highlightForeground} 30%, ${T.highlight})` : T.border)
|
|
4112
|
+
}),
|
|
4113
|
+
children: [
|
|
4114
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { ...el("planCard__name", { ...text.heading, color: text2, margin: 0 }), children: name }),
|
|
4115
|
+
label && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4116
|
+
"span",
|
|
4117
|
+
{
|
|
4118
|
+
...el("planCard__badge", {
|
|
4119
|
+
...text.label,
|
|
4120
|
+
whiteSpace: "nowrap",
|
|
4121
|
+
// The current plan's label takes the colour of its button.
|
|
4122
|
+
color: filled ? text2 : isCurrent ? T.mutedForeground : T.highlight
|
|
4123
|
+
}),
|
|
4124
|
+
children: label
|
|
4125
|
+
}
|
|
4126
|
+
)
|
|
4127
|
+
]
|
|
4128
|
+
}
|
|
4129
|
+
),
|
|
4130
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "1rem", padding: "1.25rem", flex: 1 }, children: [
|
|
4131
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
4132
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { ...el("planCard__price", { ...text.metric, color: text2, margin: 0 }), children: [
|
|
4133
|
+
price,
|
|
4134
|
+
interval && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { ...text.meta, color: muted5 }, children: [
|
|
4135
|
+
" / ",
|
|
4136
|
+
interval
|
|
4137
|
+
] })
|
|
4138
|
+
] }),
|
|
4139
|
+
priceNote && /* @__PURE__ */ jsxRuntime.jsx("p", { "data-testid": "pricing-billing", style: { ...text.meta, color: muted5, margin: "0.25rem 0 0" }, children: priceNote }),
|
|
4140
|
+
description && /* @__PURE__ */ jsxRuntime.jsx("p", { ...el("planCard__description", { ...text.secondary, color: muted5, margin: "0.25rem 0 0" }), children: description })
|
|
4141
|
+
] }),
|
|
4142
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4143
|
+
"ul",
|
|
4144
|
+
{
|
|
4145
|
+
...el("planCard__features", {
|
|
4146
|
+
listStyle: "none",
|
|
4147
|
+
margin: 0,
|
|
4148
|
+
padding: 0,
|
|
4149
|
+
display: "flex",
|
|
4150
|
+
flexDirection: "column",
|
|
4151
|
+
gap: "0.375rem",
|
|
4152
|
+
flex: 1,
|
|
4153
|
+
...text.secondary,
|
|
4154
|
+
color: muted5
|
|
4155
|
+
}),
|
|
4156
|
+
children: features.map((feature, index) => {
|
|
4157
|
+
const item = typeof feature === "string" ? { label: feature } : feature;
|
|
4158
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("li", { ...el("planCard__feature"), children: [
|
|
4159
|
+
item.label,
|
|
4160
|
+
item.description && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { display: "block", ...text.meta, color: muted5 }, children: item.description })
|
|
4161
|
+
] }, `${index}:${item.label}`);
|
|
4162
|
+
})
|
|
4163
|
+
}
|
|
4164
|
+
),
|
|
4165
|
+
children
|
|
4166
|
+
] })
|
|
4167
|
+
]
|
|
4168
|
+
}
|
|
4169
|
+
);
|
|
4170
|
+
}
|
|
4171
|
+
function Check() {
|
|
4172
|
+
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" }) });
|
|
4173
|
+
}
|
|
4174
|
+
function IntervalSwitch({
|
|
4175
|
+
options,
|
|
4176
|
+
selected,
|
|
4177
|
+
onChange,
|
|
4178
|
+
annualDiscountLabel,
|
|
4179
|
+
el
|
|
4180
|
+
}) {
|
|
4181
|
+
const pill = {
|
|
4182
|
+
display: "inline-flex",
|
|
4183
|
+
flexWrap: "wrap",
|
|
4184
|
+
justifyContent: "center",
|
|
4185
|
+
borderRadius: "9999px",
|
|
4186
|
+
padding: "4px",
|
|
4187
|
+
background: T.muted,
|
|
4188
|
+
gap: "2px"
|
|
4189
|
+
};
|
|
4190
|
+
const tab = (active) => ({
|
|
4191
|
+
padding: "8px 20px",
|
|
4192
|
+
borderRadius: "9999px",
|
|
4193
|
+
border: "none",
|
|
4194
|
+
background: active ? T.card : "transparent",
|
|
4195
|
+
...text.action,
|
|
4196
|
+
color: active ? T.foreground : T.mutedForeground,
|
|
4197
|
+
cursor: "pointer",
|
|
4198
|
+
boxShadow: active ? T.shadow : "none",
|
|
4199
|
+
transition: "all 0.15s ease"
|
|
4200
|
+
});
|
|
4201
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", justifyContent: "center", marginBottom: "2.5rem" }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { role: "group", "aria-label": "Billing interval", ...el("pricingTable__toggle", pill), children: options.map((option) => {
|
|
4202
|
+
const active = option.interval === selected;
|
|
4203
|
+
const note = option.interval === "yearly" && annualDiscountLabel ? annualDiscountLabel : option.interval !== "monthly" && option.savingsPercent !== null ? `Save ${option.savingsPercent}%` : null;
|
|
4204
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4205
|
+
"button",
|
|
4206
|
+
{
|
|
4207
|
+
type: "button",
|
|
4208
|
+
"data-interval": option.interval,
|
|
4209
|
+
onClick: () => onChange(option.interval),
|
|
4210
|
+
...el("pricingTable__toggleButton", tab(active)),
|
|
4211
|
+
"aria-pressed": active,
|
|
4212
|
+
"aria-label": note ? `${option.label}, ${note.replace(/^Save /, "save ")}` : void 0,
|
|
4213
|
+
children: [
|
|
4214
|
+
option.label,
|
|
4215
|
+
note && /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
|
|
4216
|
+
marginLeft: "6px",
|
|
4217
|
+
...text.meta,
|
|
4218
|
+
fontWeight: 600,
|
|
4219
|
+
color: active ? T.primary : T.mutedForeground
|
|
4220
|
+
}, children: note })
|
|
4221
|
+
]
|
|
4222
|
+
},
|
|
4223
|
+
option.interval
|
|
4224
|
+
);
|
|
4225
|
+
}) }) });
|
|
4226
|
+
}
|
|
4227
|
+
function Card({
|
|
4228
|
+
card: card2,
|
|
4229
|
+
isCurrent,
|
|
4230
|
+
filled: filledStyle,
|
|
4231
|
+
enterpriseEmail,
|
|
4232
|
+
onSelect,
|
|
4233
|
+
el,
|
|
4234
|
+
panel,
|
|
4235
|
+
isUpgrade
|
|
4236
|
+
}) {
|
|
4237
|
+
const [hover, setHover] = React2.useState(false);
|
|
4238
|
+
const { plan, features } = card2;
|
|
4239
|
+
const isHighlighted = card2.highlighted;
|
|
4240
|
+
const isEnt = card2.enterprise;
|
|
4241
|
+
const filled = isHighlighted && filledStyle;
|
|
4242
|
+
const text2 = filled ? T.highlightForeground : T.foreground;
|
|
4243
|
+
const muted5 = filled ? `color-mix(in srgb, ${T.highlightForeground} 75%, ${T.highlight})` : T.mutedForeground;
|
|
4244
|
+
const accent = filled ? T.highlightForeground : T.primary;
|
|
4245
|
+
const badgeLabel = isCurrent ? "Current Plan" : card2.badge;
|
|
4246
|
+
const handleClick = () => {
|
|
4247
|
+
if (isCurrent) return;
|
|
4248
|
+
if (isEnt && enterpriseEmail) {
|
|
4249
|
+
window.location.href = `mailto:${enterpriseEmail}?subject=Enterprise Plan Inquiry`;
|
|
4250
|
+
return;
|
|
4251
|
+
}
|
|
4252
|
+
onSelect?.();
|
|
4253
|
+
};
|
|
4254
|
+
const ctaText = isCurrent ? "Current Plan" : card2.family?.cta_text?.trim() ? card2.cta : isEnt ? "Contact Sales" : card2.cta;
|
|
4255
|
+
const emphasised = isUpgrade ?? isHighlighted;
|
|
4256
|
+
const cta = onSelect && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4257
|
+
CTAButton,
|
|
4258
|
+
{
|
|
4259
|
+
onClick: handleClick,
|
|
4260
|
+
disabled: isCurrent,
|
|
4261
|
+
variant: isCurrent ? "current" : isEnt ? "outline" : !emphasised ? "secondary" : filled ? "inverse" : isHighlighted ? "highlight" : "primary",
|
|
4262
|
+
el,
|
|
4263
|
+
children: ctaText
|
|
4264
|
+
}
|
|
4265
|
+
);
|
|
4266
|
+
const dataAttributes = {
|
|
4267
|
+
"data-card-key": card2.key,
|
|
4268
|
+
"data-family-code": card2.family?.code,
|
|
4269
|
+
"data-plan-code": plan.code,
|
|
4270
|
+
"data-interval": plan.interval,
|
|
4271
|
+
"data-highlighted": isHighlighted ? "true" : void 0
|
|
4272
|
+
};
|
|
4273
|
+
if (panel) {
|
|
4274
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4275
|
+
PanelPlanCard,
|
|
4276
|
+
{
|
|
4277
|
+
frame: (style) => ({ ...el("planCard", style), ...dataAttributes }),
|
|
4278
|
+
el,
|
|
4279
|
+
name: card2.name,
|
|
4280
|
+
description: card2.description,
|
|
4281
|
+
price: isEnt ? "Custom" : card2.price,
|
|
4282
|
+
interval: isEnt || !card2.pricePeriod ? void 0 : card2.pricePeriod.replace(/^\//, ""),
|
|
4283
|
+
priceNote: isEnt ? null : card2.billing,
|
|
4284
|
+
features,
|
|
4285
|
+
isCurrent,
|
|
4286
|
+
isHighlighted,
|
|
4287
|
+
filled: filledStyle,
|
|
4288
|
+
highlightLabel: card2.badge,
|
|
4289
|
+
children: cta
|
|
4290
|
+
}
|
|
4291
|
+
);
|
|
4292
|
+
}
|
|
4293
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4294
|
+
"div",
|
|
4295
|
+
{
|
|
4296
|
+
...dataAttributes,
|
|
4297
|
+
onMouseEnter: () => setHover(true),
|
|
4298
|
+
onMouseLeave: () => setHover(false),
|
|
4299
|
+
...el("planCard", {
|
|
4300
|
+
position: "relative",
|
|
4301
|
+
display: "flex",
|
|
4302
|
+
flexDirection: "column",
|
|
4303
|
+
background: filled ? T.highlight : T.card,
|
|
4304
|
+
color: text2,
|
|
4305
|
+
border: border(isHighlighted ? T.highlight : T.border),
|
|
4306
|
+
borderTopWidth: isHighlighted && !filled ? `calc(${T.borderWidth} + 2px)` : T.borderWidth,
|
|
4307
|
+
borderRadius: T.radius,
|
|
4308
|
+
padding: "2rem",
|
|
4309
|
+
transition: "box-shadow 0.2s ease, transform 0.2s ease",
|
|
4310
|
+
boxShadow: T.shadow,
|
|
4311
|
+
transform: hover && !isCurrent ? "translateY(-2px)" : "none"
|
|
4312
|
+
}),
|
|
4313
|
+
children: [
|
|
4314
|
+
isHighlighted && badgeLabel && /* @__PURE__ */ jsxRuntime.jsx("div", { ...el("planCard__badge", {
|
|
4315
|
+
position: "absolute",
|
|
4316
|
+
top: "-1px",
|
|
4317
|
+
left: "50%",
|
|
4318
|
+
transform: "translate(-50%, -50%)",
|
|
4319
|
+
background: filled ? T.highlightForeground : T.highlight,
|
|
4320
|
+
border: filled ? border(T.highlight) : void 0,
|
|
4321
|
+
...text.label,
|
|
4322
|
+
color: filled ? T.highlight : T.highlightForeground,
|
|
4323
|
+
padding: "4px 12px",
|
|
4324
|
+
borderRadius: "9999px",
|
|
4325
|
+
whiteSpace: "nowrap"
|
|
4326
|
+
}), children: badgeLabel }),
|
|
4327
|
+
isCurrent && !isHighlighted && /* @__PURE__ */ jsxRuntime.jsx("div", { ...el("planCard__badge", {
|
|
4328
|
+
position: "absolute",
|
|
4329
|
+
top: "0",
|
|
3066
4330
|
left: "50%",
|
|
3067
4331
|
transform: "translate(-50%, -50%)",
|
|
3068
|
-
background:
|
|
3069
|
-
|
|
3070
|
-
fontSize: "0.7rem",
|
|
3071
|
-
fontWeight: 700,
|
|
4332
|
+
background: T.muted,
|
|
4333
|
+
...text.label,
|
|
3072
4334
|
padding: "4px 12px",
|
|
3073
4335
|
borderRadius: "9999px",
|
|
3074
|
-
letterSpacing: "0.025em",
|
|
3075
|
-
textTransform: "uppercase",
|
|
3076
4336
|
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 })
|
|
4337
|
+
}), children: "Current Plan" }),
|
|
4338
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: "1.25rem", marginTop: isHighlighted && badgeLabel || isCurrent ? "0.5rem" : 0 }, children: [
|
|
4339
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { ...el("planCard__name", {
|
|
4340
|
+
...text.heading,
|
|
4341
|
+
color: text2,
|
|
4342
|
+
margin: 0
|
|
4343
|
+
}), children: card2.name }),
|
|
4344
|
+
card2.description && /* @__PURE__ */ jsxRuntime.jsx("p", { ...el("planCard__description", {
|
|
4345
|
+
...text.secondary,
|
|
4346
|
+
color: muted5,
|
|
4347
|
+
margin: "6px 0 0"
|
|
4348
|
+
}), children: card2.description })
|
|
3092
4349
|
] }),
|
|
3093
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", {
|
|
3094
|
-
/* @__PURE__ */ jsxRuntime.
|
|
3095
|
-
|
|
4350
|
+
/* @__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(jsxRuntime.Fragment, { children: [
|
|
4351
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "baseline", gap: "2px" }, children: [
|
|
4352
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { "data-testid": "pricing-price", style: { ...text.metric, color: text2 }, children: card2.price }),
|
|
4353
|
+
card2.pricePeriod && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...text.meta, color: muted5, marginLeft: "4px" }, children: card2.pricePeriod })
|
|
4354
|
+
] }),
|
|
4355
|
+
card2.billing && /* @__PURE__ */ jsxRuntime.jsx("p", { "data-testid": "pricing-billing", style: { ...text.meta, color: muted5, margin: "8px 0 0" }, children: card2.billing })
|
|
3096
4356
|
] }) }),
|
|
3097
|
-
features
|
|
4357
|
+
features.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("ul", { ...el("planCard__features", {
|
|
3098
4358
|
listStyle: "none",
|
|
3099
4359
|
padding: 0,
|
|
3100
4360
|
margin: "0 0 2rem",
|
|
@@ -3102,27 +4362,21 @@ function Card({
|
|
|
3102
4362
|
flexDirection: "column",
|
|
3103
4363
|
gap: "0.75rem",
|
|
3104
4364
|
flex: 1
|
|
3105
|
-
}, children: features.map((feat) => /* @__PURE__ */ jsxRuntime.jsxs("li", {
|
|
4365
|
+
}), children: features.map((feat, index) => /* @__PURE__ */ jsxRuntime.jsxs("li", { ...el("planCard__feature", {
|
|
3106
4366
|
display: "flex",
|
|
3107
4367
|
alignItems: "flex-start",
|
|
3108
4368
|
gap: "10px",
|
|
3109
|
-
|
|
3110
|
-
color:
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
/* @__PURE__ */ jsxRuntime.
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
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
|
-
)
|
|
4369
|
+
...text.body,
|
|
4370
|
+
color: text2
|
|
4371
|
+
}), children: [
|
|
4372
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: accent, marginTop: "2px" }, children: /* @__PURE__ */ jsxRuntime.jsx(Check, {}) }),
|
|
4373
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { style: { display: "flex", flexDirection: "column" }, children: [
|
|
4374
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: feat.label }),
|
|
4375
|
+
feat.description && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...text.meta, color: muted5, marginTop: "2px" }, children: feat.description })
|
|
4376
|
+
] })
|
|
4377
|
+
] }, `${index}:${feat.label}`)) }),
|
|
4378
|
+
features.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1 } }),
|
|
4379
|
+
cta
|
|
3126
4380
|
]
|
|
3127
4381
|
}
|
|
3128
4382
|
);
|
|
@@ -3131,124 +4385,94 @@ function CTAButton({
|
|
|
3131
4385
|
children,
|
|
3132
4386
|
onClick,
|
|
3133
4387
|
disabled,
|
|
3134
|
-
variant
|
|
4388
|
+
variant,
|
|
4389
|
+
el
|
|
3135
4390
|
}) {
|
|
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
|
-
};
|
|
4391
|
+
const [hover, setHover] = React2.useState(false);
|
|
4392
|
+
const look = variant === "current" ? { ...buttonStyle("secondary"), background: T.muted, color: T.mutedForeground, 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
4393
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3169
4394
|
"button",
|
|
3170
4395
|
{
|
|
4396
|
+
type: "button",
|
|
3171
4397
|
onClick: disabled ? void 0 : onClick,
|
|
3172
4398
|
disabled,
|
|
3173
4399
|
onMouseEnter: () => setHover(true),
|
|
3174
4400
|
onMouseLeave: () => setHover(false),
|
|
3175
|
-
|
|
4401
|
+
...el("planCard__cta", {
|
|
4402
|
+
...look,
|
|
4403
|
+
width: "100%",
|
|
4404
|
+
filter: hover && !disabled ? "brightness(0.95)" : void 0
|
|
4405
|
+
}),
|
|
3176
4406
|
children
|
|
3177
4407
|
}
|
|
3178
4408
|
);
|
|
3179
4409
|
}
|
|
3180
|
-
function Skeleton() {
|
|
3181
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
|
|
4410
|
+
function Skeleton({ rootProps }) {
|
|
4411
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { ...rootProps, "aria-busy": "true", style: {
|
|
4412
|
+
...rootProps.style,
|
|
3182
4413
|
display: "grid",
|
|
3183
4414
|
gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
|
|
3184
4415
|
gap: "1.5rem"
|
|
3185
4416
|
}, children: [0, 1, 2].map((i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
|
|
3186
|
-
background:
|
|
3187
|
-
border:
|
|
3188
|
-
borderRadius:
|
|
4417
|
+
background: T.card,
|
|
4418
|
+
border: border(),
|
|
4419
|
+
borderRadius: T.radius,
|
|
3189
4420
|
padding: "2rem",
|
|
3190
4421
|
height: "380px"
|
|
3191
4422
|
}, 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:
|
|
4423
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 20, width: "40%", borderRadius: 6, background: T.skeleton, marginBottom: 12 } }),
|
|
4424
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 40, width: "50%", borderRadius: 6, background: T.skeleton, marginBottom: 24 } }),
|
|
4425
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 14, width: "80%", borderRadius: 6, background: T.skeleton, marginBottom: 10 } }),
|
|
4426
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 14, width: "70%", borderRadius: 6, background: T.skeleton, marginBottom: 10 } }),
|
|
4427
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 14, width: "60%", borderRadius: 6, background: T.skeleton } })
|
|
3197
4428
|
] }, i)) });
|
|
3198
4429
|
}
|
|
3199
4430
|
function PricingTable({
|
|
3200
4431
|
currentPlanCode,
|
|
3201
4432
|
plans: propPlans,
|
|
4433
|
+
families: propFamilies,
|
|
3202
4434
|
features,
|
|
4435
|
+
sort,
|
|
3203
4436
|
onSelect,
|
|
3204
4437
|
returnUrl,
|
|
3205
4438
|
onStripeClientSecret,
|
|
3206
4439
|
onCheckoutResult,
|
|
3207
4440
|
onCheckoutError,
|
|
3208
4441
|
showToggle = true,
|
|
4442
|
+
defaultInterval,
|
|
3209
4443
|
highlightPlan,
|
|
4444
|
+
highlightStyle = "outline",
|
|
4445
|
+
highlightLabel,
|
|
3210
4446
|
className,
|
|
3211
|
-
|
|
4447
|
+
contactSalesPlans,
|
|
4448
|
+
enterpriseEmail,
|
|
4449
|
+
annualDiscountLabel,
|
|
4450
|
+
locale: localeProp,
|
|
4451
|
+
appearance
|
|
3212
4452
|
}) {
|
|
4453
|
+
const { root, el, planCardLayout } = useAppearance(appearance);
|
|
4454
|
+
const locale = resolvePricingLocale(localeProp);
|
|
3213
4455
|
const billing = useOptionalBillingContext();
|
|
3214
4456
|
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(() => {
|
|
4457
|
+
const fetched = reactQuery.useQuery(
|
|
4458
|
+
{ ...catalogQuery(client), enabled: !propPlans && Boolean(client) },
|
|
4459
|
+
billing?.queryClient ?? idleQueryClient
|
|
4460
|
+
);
|
|
4461
|
+
const catalog = propPlans ? { plans: propPlans, families: Array.isArray(propFamilies) ? propFamilies : null } : { plans: fetched.data?.plans ?? [], families: fetched.data?.families ?? null };
|
|
4462
|
+
const loading = !propPlans && Boolean(client) && fetched.isPending;
|
|
4463
|
+
const loadError = propPlans ? null : !client ? "BillingProvider with a publishableKey is required to load plans" : fetched.error ? `Plans could not be loaded: ${fetched.error.message}` : null;
|
|
4464
|
+
const [currentPlan, setCurrentPlan] = React2.useState(currentPlanCode ?? null);
|
|
4465
|
+
const [selectedInterval, setSelectedInterval] = React2.useState(null);
|
|
4466
|
+
const [processing, setProcessing] = React2.useState(false);
|
|
4467
|
+
const [checkoutError, setCheckoutError] = React2.useState(null);
|
|
4468
|
+
React2.useEffect(() => {
|
|
3246
4469
|
if (currentPlanCode !== void 0) setCurrentPlan(currentPlanCode);
|
|
3247
4470
|
}, [currentPlanCode]);
|
|
3248
|
-
|
|
4471
|
+
const withFamilies = Array.isArray(catalog.families);
|
|
4472
|
+
async function selectPlan(plan, family) {
|
|
3249
4473
|
setCheckoutError(null);
|
|
3250
4474
|
if (onSelect) {
|
|
3251
|
-
onSelect(plan);
|
|
4475
|
+
onSelect(plan, family);
|
|
3252
4476
|
return;
|
|
3253
4477
|
}
|
|
3254
4478
|
try {
|
|
@@ -3274,124 +4498,143 @@ function PricingTable({
|
|
|
3274
4498
|
onCheckoutError?.(error);
|
|
3275
4499
|
}
|
|
3276
4500
|
}
|
|
3277
|
-
const
|
|
3278
|
-
const
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
4501
|
+
const available = getAvailableIntervals(catalog);
|
|
4502
|
+
const interval = selectedInterval !== null && available.includes(selectedInterval) ? selectedInterval : getDefaultInterval(catalog, defaultInterval);
|
|
4503
|
+
const switchVisible = showToggle && available.length > 1 && interval !== null;
|
|
4504
|
+
const cards = getPricingCards(catalog, {
|
|
4505
|
+
interval: withFamilies || switchVisible ? interval : null,
|
|
4506
|
+
locale,
|
|
4507
|
+
highlight: highlightPlan,
|
|
4508
|
+
badge: highlightLabel,
|
|
4509
|
+
features,
|
|
4510
|
+
contactSales: contactSalesPlans,
|
|
4511
|
+
sort
|
|
4512
|
+
});
|
|
4513
|
+
const current = catalog.plans.find((p) => p.code === currentPlan);
|
|
4514
|
+
const currentYearly = current ? yearlyMinor(current) : null;
|
|
4515
|
+
const isUpgrade = (plan) => {
|
|
4516
|
+
if (!current || current.amount_currency !== plan.amount_currency) return void 0;
|
|
4517
|
+
const yearly = yearlyMinor(plan);
|
|
4518
|
+
return currentYearly !== null && yearly !== null ? yearly > currentYearly : plan.amount_cents > current.amount_cents;
|
|
4519
|
+
};
|
|
4520
|
+
const rootProps = root("pricingTable", text.body, className);
|
|
4521
|
+
if (loading) return /* @__PURE__ */ jsxRuntime.jsx(Skeleton, { rootProps });
|
|
4522
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ...rootProps, children: [
|
|
4523
|
+
switchVisible && interval !== null && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4524
|
+
IntervalSwitch,
|
|
4525
|
+
{
|
|
4526
|
+
options: getPricingIntervalOptions(catalog),
|
|
4527
|
+
selected: interval,
|
|
4528
|
+
onChange: setSelectedInterval,
|
|
4529
|
+
annualDiscountLabel,
|
|
4530
|
+
el
|
|
4531
|
+
}
|
|
4532
|
+
),
|
|
3286
4533
|
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", {
|
|
4534
|
+
(checkoutError ?? loadError) && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", style: { color: T.dangerText }, children: checkoutError ?? loadError }),
|
|
4535
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { ...el("pricingTable__grid", {
|
|
3289
4536
|
display: "grid",
|
|
3290
4537
|
gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
|
|
3291
4538
|
gap: "1.5rem"
|
|
3292
|
-
}, children:
|
|
4539
|
+
}), children: cards.map((card2) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3293
4540
|
Card,
|
|
3294
4541
|
{
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
isEnt: isEnterprise(plan),
|
|
4542
|
+
card: card2,
|
|
4543
|
+
isCurrent: currentPlan === card2.plan.code,
|
|
4544
|
+
isUpgrade: isUpgrade(card2.plan),
|
|
4545
|
+
filled: highlightStyle === "filled",
|
|
3300
4546
|
enterpriseEmail,
|
|
3301
|
-
onSelect: () => void selectPlan(plan)
|
|
4547
|
+
onSelect: () => void selectPlan(card2.plan, card2.family),
|
|
4548
|
+
el,
|
|
4549
|
+
panel: planCardLayout === "panel"
|
|
3302
4550
|
},
|
|
3303
|
-
|
|
3304
|
-
)) })
|
|
4551
|
+
card2.key
|
|
4552
|
+
)) }),
|
|
4553
|
+
/* @__PURE__ */ jsxRuntime.jsx(NozleBranding, {})
|
|
3305
4554
|
] });
|
|
3306
4555
|
}
|
|
3307
4556
|
function PlanCard({
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
monthlyPrice,
|
|
3311
|
-
annualPrice,
|
|
3312
|
-
features,
|
|
3313
|
-
description,
|
|
3314
|
-
isAnnual,
|
|
4557
|
+
plan,
|
|
4558
|
+
features: featureOverride,
|
|
3315
4559
|
isCurrent,
|
|
3316
4560
|
returnUrl,
|
|
3317
|
-
onSelect
|
|
4561
|
+
onSelect,
|
|
4562
|
+
appearance
|
|
3318
4563
|
}) {
|
|
3319
|
-
const
|
|
3320
|
-
const
|
|
4564
|
+
const { root, el, planCardLayout } = useAppearance(appearance);
|
|
4565
|
+
const { code: id, name, description } = plan;
|
|
4566
|
+
const price = formatPlanPrice(plan.amount_cents, plan.amount_currency);
|
|
4567
|
+
const interval = intervalLabel(plan.interval);
|
|
4568
|
+
const features = featureOverride ?? planFeatures(plan);
|
|
4569
|
+
const cta = onSelect && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4570
|
+
"button",
|
|
4571
|
+
{
|
|
4572
|
+
type: "button",
|
|
4573
|
+
onClick: () => onSelect?.({
|
|
4574
|
+
planCode: id,
|
|
4575
|
+
returnUrl: returnUrl ?? window.location.href
|
|
4576
|
+
}),
|
|
4577
|
+
...el("planCard__cta", {
|
|
4578
|
+
...buttonStyle(isCurrent ? "secondary" : "primary"),
|
|
4579
|
+
...isCurrent && { background: T.muted, color: T.mutedForeground, cursor: "default" },
|
|
4580
|
+
width: "100%"
|
|
4581
|
+
}),
|
|
4582
|
+
disabled: isCurrent,
|
|
4583
|
+
"aria-label": isCurrent ? `Current plan: ${name}` : `Select ${name}`,
|
|
4584
|
+
children: isCurrent ? "Current Plan" : "Get Started"
|
|
4585
|
+
}
|
|
4586
|
+
);
|
|
4587
|
+
if (planCardLayout === "panel") {
|
|
4588
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4589
|
+
PanelPlanCard,
|
|
4590
|
+
{
|
|
4591
|
+
frame: (style) => ({ "data-testid": `plan-card-${id}`, "aria-current": isCurrent ? "true" : void 0, ...root("planCard", style) }),
|
|
4592
|
+
el,
|
|
4593
|
+
name,
|
|
4594
|
+
description,
|
|
4595
|
+
price,
|
|
4596
|
+
interval,
|
|
4597
|
+
features,
|
|
4598
|
+
isCurrent: Boolean(isCurrent),
|
|
4599
|
+
isHighlighted: false,
|
|
4600
|
+
filled: false,
|
|
4601
|
+
children: cta
|
|
4602
|
+
}
|
|
4603
|
+
);
|
|
4604
|
+
}
|
|
3321
4605
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3322
4606
|
"div",
|
|
3323
4607
|
{
|
|
3324
4608
|
"data-testid": `plan-card-${id}`,
|
|
3325
4609
|
"aria-current": isCurrent ? "true" : void 0,
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
4610
|
+
...root("planCard", {
|
|
4611
|
+
...text.body,
|
|
4612
|
+
background: T.card,
|
|
4613
|
+
border: border(isCurrent ? T.primary : T.border),
|
|
4614
|
+
outline: isCurrent ? `1px solid ${T.primary}` : void 0,
|
|
4615
|
+
borderRadius: T.radius,
|
|
4616
|
+
boxShadow: T.shadow,
|
|
3330
4617
|
padding: "1.5rem",
|
|
3331
4618
|
display: "flex",
|
|
3332
4619
|
flexDirection: "column",
|
|
3333
4620
|
gap: "1rem"
|
|
3334
|
-
},
|
|
3335
|
-
className: isCurrent ? "border-primary" : "",
|
|
4621
|
+
}),
|
|
3336
4622
|
children: [
|
|
3337
4623
|
/* @__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
|
-
)
|
|
4624
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { ...el("planCard__name", { ...text.heading, margin: 0 }), children: name }),
|
|
4625
|
+
description && /* @__PURE__ */ jsxRuntime.jsx("p", { ...el("planCard__description", { ...text.secondary, margin: "0.25rem 0 0" }), children: description })
|
|
3361
4626
|
] }),
|
|
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
|
-
)
|
|
4627
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { ...el("planCard__price"), children: [
|
|
4628
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: text.metric, children: price }),
|
|
4629
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { style: { ...text.meta, marginLeft: "0.25rem" }, children: [
|
|
4630
|
+
"/",
|
|
4631
|
+
interval
|
|
4632
|
+
] })
|
|
3390
4633
|
] }),
|
|
3391
4634
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3392
4635
|
"ul",
|
|
3393
4636
|
{
|
|
3394
|
-
|
|
4637
|
+
...el("planCard__features", {
|
|
3395
4638
|
listStyle: "none",
|
|
3396
4639
|
padding: 0,
|
|
3397
4640
|
margin: 0,
|
|
@@ -3399,160 +4642,51 @@ function PlanCard({
|
|
|
3399
4642
|
flexDirection: "column",
|
|
3400
4643
|
gap: "0.5rem",
|
|
3401
4644
|
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
|
-
},
|
|
4645
|
+
}),
|
|
4646
|
+
children: features.map((feature) => /* @__PURE__ */ jsxRuntime.jsxs("li", { ...el("planCard__feature", { display: "flex", alignItems: "center", gap: "0.5rem", ...text.body }), children: [
|
|
4647
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: T.primary }, "aria-hidden": "true", children: "\u2713" }),
|
|
3425
4648
|
feature
|
|
3426
|
-
))
|
|
4649
|
+
] }, feature))
|
|
3427
4650
|
}
|
|
3428
4651
|
),
|
|
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
|
-
)
|
|
4652
|
+
cta
|
|
3451
4653
|
]
|
|
3452
4654
|
}
|
|
3453
4655
|
);
|
|
3454
4656
|
}
|
|
3455
4657
|
function PlanComparison({
|
|
3456
|
-
plans,
|
|
3457
|
-
features
|
|
4658
|
+
plans: catalogPlans,
|
|
4659
|
+
features: featureOverride,
|
|
4660
|
+
sort,
|
|
4661
|
+
appearance
|
|
3458
4662
|
}) {
|
|
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
|
-
);
|
|
4663
|
+
const { root, el } = useAppearance(appearance);
|
|
4664
|
+
const plans = sort ? [...catalogPlans].sort(sort) : sortPlansByPrice(catalogPlans);
|
|
4665
|
+
const features = featureOverride ?? comparisonRows(plans);
|
|
4666
|
+
const cell = (style) => el("planComparison__cell", style);
|
|
4667
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ...root("planComparison", { minWidth: 0 }), children: [
|
|
4668
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: table.frame, children: /* @__PURE__ */ jsxRuntime.jsxs("table", { ...el("planComparison__table", table.table), children: [
|
|
4669
|
+
/* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
|
|
4670
|
+
/* @__PURE__ */ jsxRuntime.jsx("th", { ...cell(table.head()), children: "Feature" }),
|
|
4671
|
+
plans.map((plan) => /* @__PURE__ */ jsxRuntime.jsxs("th", { ...cell({ ...table.head(), textAlign: "center", padding: "10px 20px" }), children: [
|
|
4672
|
+
plan.name,
|
|
4673
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { ...text.meta, letterSpacing: "normal", textTransform: "none" }, children: [
|
|
4674
|
+
formatPlanPrice(plan.amount_cents, plan.amount_currency),
|
|
4675
|
+
" / ",
|
|
4676
|
+
intervalLabel(plan.interval)
|
|
4677
|
+
] })
|
|
4678
|
+
] }, plan.code))
|
|
4679
|
+
] }) }),
|
|
4680
|
+
/* @__PURE__ */ jsxRuntime.jsx("tbody", { children: features.map((feature, index) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { ...el("planComparison__row"), children: [
|
|
4681
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { ...cell(table.cell(false, index === features.length - 1)), children: feature.label }),
|
|
4682
|
+
plans.map((plan) => {
|
|
4683
|
+
const value = feature.values[plan.code];
|
|
4684
|
+
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);
|
|
4685
|
+
})
|
|
4686
|
+
] }, feature.key)) })
|
|
4687
|
+
] }) }),
|
|
4688
|
+
/* @__PURE__ */ jsxRuntime.jsx(NozleBranding, {})
|
|
4689
|
+
] });
|
|
3556
4690
|
}
|
|
3557
4691
|
function UpgradeButton({
|
|
3558
4692
|
planCode,
|
|
@@ -3568,26 +4702,18 @@ function UpgradeButton({
|
|
|
3568
4702
|
onCheckoutStarted,
|
|
3569
4703
|
onDowngradeScheduled,
|
|
3570
4704
|
onUpgraded,
|
|
3571
|
-
onError
|
|
4705
|
+
onError,
|
|
4706
|
+
appearance
|
|
3572
4707
|
}) {
|
|
3573
|
-
const [isOpen, setIsOpen] =
|
|
4708
|
+
const [isOpen, setIsOpen] = React2.useState(false);
|
|
4709
|
+
const { root } = useAppearance(appearance);
|
|
3574
4710
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3575
4711
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3576
4712
|
"button",
|
|
3577
4713
|
{
|
|
3578
4714
|
type: "button",
|
|
3579
4715
|
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
|
-
},
|
|
4716
|
+
...root("upgradeButton", { ...buttonStyle(), ...style }, className),
|
|
3591
4717
|
children: label
|
|
3592
4718
|
}
|
|
3593
4719
|
),
|
|
@@ -3595,6 +4721,7 @@ function UpgradeButton({
|
|
|
3595
4721
|
UpgradeModal,
|
|
3596
4722
|
{
|
|
3597
4723
|
isOpen,
|
|
4724
|
+
appearance,
|
|
3598
4725
|
planCode,
|
|
3599
4726
|
returnUrl,
|
|
3600
4727
|
subscriptionId,
|
|
@@ -3653,11 +4780,13 @@ function PlanGate({
|
|
|
3653
4780
|
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
|
|
3654
4781
|
}
|
|
3655
4782
|
function UpgradePrompt({
|
|
3656
|
-
text = "Upgrade to access this feature",
|
|
4783
|
+
text: text2 = "Upgrade to access this feature",
|
|
3657
4784
|
planName: planName2,
|
|
3658
|
-
onUpgrade
|
|
4785
|
+
onUpgrade,
|
|
4786
|
+
appearance
|
|
3659
4787
|
}) {
|
|
3660
|
-
const
|
|
4788
|
+
const { root } = useAppearance(appearance);
|
|
4789
|
+
const displayText = planName2 ? `Upgrade to ${planName2} to access this feature` : text2;
|
|
3661
4790
|
function handleUpgrade() {
|
|
3662
4791
|
if (onUpgrade) {
|
|
3663
4792
|
onUpgrade();
|
|
@@ -3668,40 +4797,23 @@ function UpgradePrompt({
|
|
|
3668
4797
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3669
4798
|
"div",
|
|
3670
4799
|
{
|
|
3671
|
-
|
|
4800
|
+
...root("upgradePrompt", {
|
|
3672
4801
|
display: "flex",
|
|
3673
4802
|
flexDirection: "column",
|
|
3674
4803
|
alignItems: "center",
|
|
3675
4804
|
gap: "0.75rem",
|
|
3676
4805
|
padding: "1rem",
|
|
3677
|
-
textAlign: "center"
|
|
3678
|
-
|
|
4806
|
+
textAlign: "center",
|
|
4807
|
+
...text.body
|
|
4808
|
+
}),
|
|
3679
4809
|
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
|
-
),
|
|
4810
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { style: { margin: 0 }, children: displayText }),
|
|
3691
4811
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3692
4812
|
"button",
|
|
3693
4813
|
{
|
|
4814
|
+
type: "button",
|
|
3694
4815
|
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
|
-
},
|
|
4816
|
+
style: { ...buttonStyle(), padding: "0.5rem 1.25rem" },
|
|
3705
4817
|
children: "Upgrade"
|
|
3706
4818
|
}
|
|
3707
4819
|
)
|
|
@@ -3714,12 +4826,14 @@ function LockedOverlay({
|
|
|
3714
4826
|
upgradeText,
|
|
3715
4827
|
upgradePlanName,
|
|
3716
4828
|
onUpgrade,
|
|
3717
|
-
children
|
|
4829
|
+
children,
|
|
4830
|
+
appearance
|
|
3718
4831
|
}) {
|
|
4832
|
+
const { root, el } = useAppearance(appearance);
|
|
3719
4833
|
if (!locked) {
|
|
3720
4834
|
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
|
|
3721
4835
|
}
|
|
3722
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", {
|
|
4836
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ...root("lockedOverlay", { position: "relative", display: "inline-block" }), children: [
|
|
3723
4837
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3724
4838
|
"div",
|
|
3725
4839
|
{
|
|
@@ -3735,16 +4849,15 @@ function LockedOverlay({
|
|
|
3735
4849
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3736
4850
|
"div",
|
|
3737
4851
|
{
|
|
3738
|
-
|
|
4852
|
+
...el("lockedOverlay__panel", {
|
|
3739
4853
|
position: "absolute",
|
|
3740
4854
|
inset: 0,
|
|
3741
4855
|
display: "flex",
|
|
3742
4856
|
alignItems: "center",
|
|
3743
4857
|
justifyContent: "center",
|
|
3744
|
-
background:
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
},
|
|
4858
|
+
background: `color-mix(in srgb, ${T.background} 92%, transparent)`,
|
|
4859
|
+
borderRadius: T.radius
|
|
4860
|
+
}),
|
|
3748
4861
|
role: "dialog",
|
|
3749
4862
|
"aria-label": "Feature locked \u2014 upgrade required",
|
|
3750
4863
|
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -3752,7 +4865,8 @@ function LockedOverlay({
|
|
|
3752
4865
|
{
|
|
3753
4866
|
text: upgradeText,
|
|
3754
4867
|
planName: upgradePlanName,
|
|
3755
|
-
onUpgrade
|
|
4868
|
+
onUpgrade,
|
|
4869
|
+
appearance
|
|
3756
4870
|
}
|
|
3757
4871
|
)
|
|
3758
4872
|
}
|
|
@@ -3760,69 +4874,110 @@ function LockedOverlay({
|
|
|
3760
4874
|
] });
|
|
3761
4875
|
}
|
|
3762
4876
|
function getUsageMeterColor(percentage) {
|
|
3763
|
-
if (percentage >= 95) return
|
|
3764
|
-
if (percentage >= 80) return
|
|
3765
|
-
return
|
|
4877
|
+
if (percentage >= 95) return T.danger;
|
|
4878
|
+
if (percentage >= 80) return T.warning;
|
|
4879
|
+
return T.primary;
|
|
3766
4880
|
}
|
|
3767
4881
|
function UsageMeter({
|
|
3768
4882
|
used,
|
|
3769
4883
|
limit,
|
|
3770
4884
|
label,
|
|
3771
|
-
showText = true
|
|
4885
|
+
showText = true,
|
|
4886
|
+
appearance
|
|
3772
4887
|
}) {
|
|
3773
|
-
const
|
|
4888
|
+
const { root, el } = useAppearance(appearance);
|
|
4889
|
+
const unlimited = limit == null;
|
|
4890
|
+
const percentage = !unlimited && limit > 0 ? Math.min(100, used / limit * 100) : 0;
|
|
3774
4891
|
const color = getUsageMeterColor(percentage);
|
|
3775
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
4892
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4893
|
+
"div",
|
|
4894
|
+
{
|
|
4895
|
+
...root("usageMeter", {
|
|
4896
|
+
width: "100%",
|
|
4897
|
+
display: "flex",
|
|
4898
|
+
flexDirection: "column",
|
|
4899
|
+
gap: "0.25rem",
|
|
4900
|
+
...text.body
|
|
4901
|
+
}),
|
|
4902
|
+
children: [
|
|
4903
|
+
label && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
|
|
4904
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { ...el("usageMeter__label"), children: label }),
|
|
4905
|
+
showText && /* @__PURE__ */ jsxRuntime.jsxs("span", { ...el("usageMeter__value", { color: percentage >= 95 ? T.dangerText : percentage >= 80 ? T.warningText : T.foreground }), children: [
|
|
4906
|
+
used.toLocaleString(),
|
|
4907
|
+
" / ",
|
|
4908
|
+
unlimited ? "Unlimited" : limit.toLocaleString()
|
|
4909
|
+
] })
|
|
4910
|
+
] }),
|
|
4911
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3790
4912
|
"div",
|
|
3791
4913
|
{
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
}
|
|
4914
|
+
...el("usageMeter__track", {
|
|
4915
|
+
height: "0.5rem",
|
|
4916
|
+
borderRadius: "9999px",
|
|
4917
|
+
overflow: "hidden",
|
|
4918
|
+
background: T.muted
|
|
4919
|
+
}),
|
|
4920
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4921
|
+
"div",
|
|
4922
|
+
{
|
|
4923
|
+
...el("usageMeter__fill", {
|
|
4924
|
+
height: "100%",
|
|
4925
|
+
borderRadius: "9999px",
|
|
4926
|
+
width: `${percentage}%`,
|
|
4927
|
+
background: color,
|
|
4928
|
+
transition: "width 0.3s ease"
|
|
4929
|
+
})
|
|
4930
|
+
}
|
|
4931
|
+
)
|
|
3798
4932
|
}
|
|
3799
4933
|
)
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
4934
|
+
]
|
|
4935
|
+
}
|
|
4936
|
+
);
|
|
4937
|
+
}
|
|
4938
|
+
function usageItems(entitlements, results) {
|
|
4939
|
+
return entitlements.flatMap((entitlement) => {
|
|
4940
|
+
const result = results[entitlement.code];
|
|
4941
|
+
if (entitlement.feature_type !== "metered" || !result) return [];
|
|
4942
|
+
return [{ key: entitlement.code, label: entitlement.name, used: result.used, limit: result.limit ?? null }];
|
|
4943
|
+
});
|
|
3803
4944
|
}
|
|
3804
4945
|
function UsageDashboard({
|
|
3805
4946
|
features = [],
|
|
3806
|
-
loading = false
|
|
4947
|
+
loading = false,
|
|
4948
|
+
error = null,
|
|
4949
|
+
emptyMessage = "No usage to show yet.",
|
|
4950
|
+
appearance
|
|
3807
4951
|
}) {
|
|
4952
|
+
const { root } = useAppearance(appearance);
|
|
4953
|
+
const props = root("usageDashboard", {
|
|
4954
|
+
...surface,
|
|
4955
|
+
margin: 0,
|
|
4956
|
+
display: "flex",
|
|
4957
|
+
flexDirection: "column",
|
|
4958
|
+
gap: loading ? "0.75rem" : "1rem",
|
|
4959
|
+
// Errors and the empty message take the same colours as in the other components.
|
|
4960
|
+
...error ? { color: T.dangerText } : features.length === 0 && !loading && text.secondary
|
|
4961
|
+
});
|
|
3808
4962
|
if (loading) {
|
|
3809
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", {
|
|
3810
|
-
"div",
|
|
3811
|
-
{
|
|
3812
|
-
className: "h-8 rounded",
|
|
3813
|
-
style: { background: "var(--nozle-border, var(--border))" }
|
|
3814
|
-
},
|
|
3815
|
-
i
|
|
3816
|
-
)) });
|
|
4963
|
+
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)) });
|
|
3817
4964
|
}
|
|
3818
|
-
|
|
4965
|
+
if (error) {
|
|
4966
|
+
return /* @__PURE__ */ jsxRuntime.jsx("p", { ...props, role: "alert", children: typeof error === "string" ? error : error.message });
|
|
4967
|
+
}
|
|
4968
|
+
if (features.length === 0) {
|
|
4969
|
+
return /* @__PURE__ */ jsxRuntime.jsx("p", { ...props, role: "status", children: emptyMessage });
|
|
4970
|
+
}
|
|
4971
|
+
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
4972
|
}
|
|
3820
4973
|
function UsageAlert({
|
|
3821
4974
|
features,
|
|
3822
4975
|
threshold = 80,
|
|
3823
|
-
upgradeHref = "/plans"
|
|
4976
|
+
upgradeHref = "/plans",
|
|
4977
|
+
appearance
|
|
3824
4978
|
}) {
|
|
3825
|
-
const
|
|
4979
|
+
const { root, el } = useAppearance(appearance);
|
|
4980
|
+
const [dismissed, setDismissed] = React2.useState(/* @__PURE__ */ new Set());
|
|
3826
4981
|
const alertFeatures = features.filter(
|
|
3827
4982
|
(f) => f.percentage >= threshold && !dismissed.has(f.key)
|
|
3828
4983
|
);
|
|
@@ -3832,15 +4987,19 @@ function UsageAlert({
|
|
|
3832
4987
|
const handleDismiss = (key) => {
|
|
3833
4988
|
setDismissed((prev) => new Set(prev).add(key));
|
|
3834
4989
|
};
|
|
3835
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", {
|
|
4990
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { ...root("usageAlert", { display: "flex", flexDirection: "column", gap: "0.5rem", ...text.body }), children: alertFeatures.map((f) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3836
4991
|
"div",
|
|
3837
4992
|
{
|
|
3838
4993
|
role: "alert",
|
|
3839
|
-
className: "flex items-center justify-between rounded-md px-4 py-3 text-sm",
|
|
3840
4994
|
style: {
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
4995
|
+
display: "flex",
|
|
4996
|
+
alignItems: "center",
|
|
4997
|
+
justifyContent: "space-between",
|
|
4998
|
+
borderRadius: `calc(${T.radius} / 2)`,
|
|
4999
|
+
padding: "0.75rem 1rem",
|
|
5000
|
+
background: T.warningSoft,
|
|
5001
|
+
color: T.warningText,
|
|
5002
|
+
border: border(T.warningBorder)
|
|
3844
5003
|
},
|
|
3845
5004
|
children: [
|
|
3846
5005
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
@@ -3863,8 +5022,8 @@ function UsageAlert({
|
|
|
3863
5022
|
{
|
|
3864
5023
|
href: upgradeHref,
|
|
3865
5024
|
style: {
|
|
5025
|
+
...text.action,
|
|
3866
5026
|
color: "inherit",
|
|
3867
|
-
fontWeight: 600,
|
|
3868
5027
|
textDecoration: "underline"
|
|
3869
5028
|
},
|
|
3870
5029
|
children: "Upgrade"
|
|
@@ -3874,16 +5033,17 @@ function UsageAlert({
|
|
|
3874
5033
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3875
5034
|
"button",
|
|
3876
5035
|
{
|
|
5036
|
+
type: "button",
|
|
3877
5037
|
onClick: () => handleDismiss(f.key),
|
|
3878
5038
|
"aria-label": `Dismiss ${f.label} alert`,
|
|
3879
|
-
|
|
5039
|
+
...el("usageAlert__dismiss", {
|
|
3880
5040
|
background: "none",
|
|
3881
5041
|
border: "none",
|
|
3882
5042
|
cursor: "pointer",
|
|
3883
5043
|
padding: "0 0 0 0.75rem",
|
|
3884
|
-
|
|
5044
|
+
...text.action,
|
|
3885
5045
|
color: "inherit"
|
|
3886
|
-
},
|
|
5046
|
+
}),
|
|
3887
5047
|
children: "\xD7"
|
|
3888
5048
|
}
|
|
3889
5049
|
)
|
|
@@ -3892,44 +5052,247 @@ function UsageAlert({
|
|
|
3892
5052
|
f.key
|
|
3893
5053
|
)) });
|
|
3894
5054
|
}
|
|
3895
|
-
var
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
5055
|
+
var STATUS_LABELS = {
|
|
5056
|
+
active: "Active",
|
|
5057
|
+
pending: "Pending",
|
|
5058
|
+
incomplete: "Awaiting payment",
|
|
5059
|
+
canceled: "Canceled",
|
|
5060
|
+
terminated: "Ended"
|
|
5061
|
+
};
|
|
5062
|
+
function statusColors(status, ending) {
|
|
5063
|
+
if (status === "active" && !ending) return { background: T.successSoft, color: T.successText };
|
|
5064
|
+
if (status === "active" || status === "pending" || status === "incomplete") {
|
|
5065
|
+
return { background: T.warningSoft, color: T.warningText };
|
|
5066
|
+
}
|
|
5067
|
+
return { background: T.muted, color: T.mutedForeground };
|
|
5068
|
+
}
|
|
5069
|
+
function day(value, locale) {
|
|
5070
|
+
const parsed = new Date(value);
|
|
5071
|
+
return Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleDateString(locale, { dateStyle: "medium" });
|
|
5072
|
+
}
|
|
5073
|
+
function currentPlanMeters(entitlements, usage) {
|
|
5074
|
+
const units = /* @__PURE__ */ new Map();
|
|
5075
|
+
for (const charge of usage?.charges_usage ?? []) {
|
|
5076
|
+
units.set(charge.feature.code, (units.get(charge.feature.code) ?? 0) + (Number(charge.units) || 0));
|
|
5077
|
+
}
|
|
5078
|
+
return (entitlements ?? []).filter((entitlement) => entitlement.feature_type === "metered").map((entitlement) => ({
|
|
5079
|
+
key: entitlement.code,
|
|
5080
|
+
label: entitlement.name,
|
|
5081
|
+
used: units.get(entitlement.code) ?? 0,
|
|
5082
|
+
limit: entitlement.usage_limit ?? null
|
|
5083
|
+
}));
|
|
5084
|
+
}
|
|
5085
|
+
function CurrentPlan(props) {
|
|
5086
|
+
return props.subscription === void 0 ? /* @__PURE__ */ jsxRuntime.jsx(CurrentPlanFromBackend, { ...props }) : /* @__PURE__ */ jsxRuntime.jsx(CurrentPlanView, { ...props });
|
|
5087
|
+
}
|
|
5088
|
+
function CurrentPlanFromBackend(props) {
|
|
5089
|
+
const subscription = useSubscription();
|
|
5090
|
+
const plans = usePlans();
|
|
5091
|
+
const usage = useUsage();
|
|
5092
|
+
const planCode = subscription.data?.subscription?.plan_code;
|
|
5093
|
+
const meters = props.usage ?? currentPlanMeters(plans.plans.find((plan) => plan.code === planCode)?.entitlements, usage.data);
|
|
5094
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
5095
|
+
CurrentPlanView,
|
|
5096
|
+
{
|
|
5097
|
+
...props,
|
|
5098
|
+
subscription: subscription.data ?? null,
|
|
5099
|
+
usage: meters,
|
|
5100
|
+
isLoading: props.isLoading || subscription.isPending,
|
|
5101
|
+
error: props.error ?? subscription.error
|
|
5102
|
+
}
|
|
5103
|
+
);
|
|
5104
|
+
}
|
|
5105
|
+
function CurrentPlanView({
|
|
5106
|
+
subscription,
|
|
5107
|
+
usage = [],
|
|
5108
|
+
details = [],
|
|
5109
|
+
actions,
|
|
5110
|
+
warningPercent = 80,
|
|
5111
|
+
title = "Current plan",
|
|
5112
|
+
isLoading = false,
|
|
5113
|
+
error = null,
|
|
5114
|
+
emptyMessage = "No active plan.",
|
|
5115
|
+
pastDueMessage = "A payment is overdue. Pay the open invoice to keep your plan active.",
|
|
5116
|
+
locale = "en-US",
|
|
5117
|
+
appearance
|
|
5118
|
+
}) {
|
|
5119
|
+
const { root, el } = useAppearance(appearance);
|
|
5120
|
+
const frame = { ...surface, padding: 0, overflow: "hidden" };
|
|
5121
|
+
const header = /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5122
|
+
"div",
|
|
5123
|
+
{
|
|
5124
|
+
style: {
|
|
5125
|
+
display: "flex",
|
|
5126
|
+
alignItems: "center",
|
|
5127
|
+
justifyContent: "space-between",
|
|
5128
|
+
gap: 12,
|
|
5129
|
+
padding: "0.875rem 1.25rem",
|
|
5130
|
+
borderBottom: border()
|
|
5131
|
+
},
|
|
5132
|
+
children: [
|
|
5133
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: text.heading, children: title }),
|
|
5134
|
+
actions
|
|
5135
|
+
]
|
|
5136
|
+
}
|
|
5137
|
+
);
|
|
5138
|
+
const body = (children) => /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "1.25rem", display: "grid", gap: "1rem" }, children });
|
|
5139
|
+
if (isLoading) {
|
|
5140
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("section", { ...root("currentPlan", frame), "aria-busy": "true", children: [
|
|
5141
|
+
header,
|
|
5142
|
+
body(/* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 72, borderRadius: `calc(${T.radius} / 2)`, background: T.skeleton } }))
|
|
5143
|
+
] });
|
|
3915
5144
|
}
|
|
5145
|
+
if (error) {
|
|
5146
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("section", { ...root("currentPlan", frame), children: [
|
|
5147
|
+
header,
|
|
5148
|
+
body(/* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", style: { margin: 0, color: T.dangerText }, children: typeof error === "string" ? error : error.message }))
|
|
5149
|
+
] });
|
|
5150
|
+
}
|
|
5151
|
+
const current = subscription?.subscription;
|
|
5152
|
+
if (!current) {
|
|
5153
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("section", { ...root("currentPlan", frame), children: [
|
|
5154
|
+
header,
|
|
5155
|
+
body(/* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", style: { ...text.secondary, margin: 0 }, children: emptyMessage }))
|
|
5156
|
+
] });
|
|
5157
|
+
}
|
|
5158
|
+
const pending = subscription?.pending_change;
|
|
5159
|
+
const ending = Boolean(current.ending_at);
|
|
5160
|
+
const near = warningPercent === null ? [] : usage.filter((meter) => meter.limit != null && meter.limit > 0 && meter.used / meter.limit * 100 >= warningPercent);
|
|
5161
|
+
const statusLabel = ending && current.ending_at ? `Ends ${day(current.ending_at, locale)}` : STATUS_LABELS[current.status] ?? current.status;
|
|
5162
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("section", { ...root("currentPlan", frame), "aria-label": title, children: [
|
|
5163
|
+
header,
|
|
5164
|
+
body(
|
|
5165
|
+
/* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
5166
|
+
subscription?.past_due && /* @__PURE__ */ jsxRuntime.jsx(
|
|
5167
|
+
"p",
|
|
5168
|
+
{
|
|
5169
|
+
role: "alert",
|
|
5170
|
+
...el("currentPlan__notice", {
|
|
5171
|
+
margin: 0,
|
|
5172
|
+
padding: "0.625rem 0.875rem",
|
|
5173
|
+
...text.body,
|
|
5174
|
+
borderRadius: `calc(${T.radius} / 2)`,
|
|
5175
|
+
background: T.dangerSoft,
|
|
5176
|
+
color: T.dangerText,
|
|
5177
|
+
border: border(T.dangerBorder)
|
|
5178
|
+
}),
|
|
5179
|
+
children: pastDueMessage
|
|
5180
|
+
}
|
|
5181
|
+
),
|
|
5182
|
+
near.map((meter) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5183
|
+
"p",
|
|
5184
|
+
{
|
|
5185
|
+
role: "status",
|
|
5186
|
+
...el("currentPlan__notice", {
|
|
5187
|
+
margin: 0,
|
|
5188
|
+
padding: "0.625rem 0.875rem",
|
|
5189
|
+
...text.body,
|
|
5190
|
+
borderRadius: `calc(${T.radius} / 2)`,
|
|
5191
|
+
background: T.warningSoft,
|
|
5192
|
+
color: T.warningText,
|
|
5193
|
+
border: border(T.warningBorder)
|
|
5194
|
+
}),
|
|
5195
|
+
children: [
|
|
5196
|
+
"You have used ",
|
|
5197
|
+
Math.round(meter.used / meter.limit * 100),
|
|
5198
|
+
"% of the ",
|
|
5199
|
+
meter.label,
|
|
5200
|
+
" included in",
|
|
5201
|
+
" ",
|
|
5202
|
+
current.plan.name,
|
|
5203
|
+
"."
|
|
5204
|
+
]
|
|
5205
|
+
},
|
|
5206
|
+
meter.key
|
|
5207
|
+
)),
|
|
5208
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexWrap: "wrap", alignItems: "center", gap: "0.625rem" }, children: [
|
|
5209
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { ...el("currentPlan__name", text.heading), children: current.plan.name }),
|
|
5210
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5211
|
+
"span",
|
|
5212
|
+
{
|
|
5213
|
+
...el("currentPlan__status", {
|
|
5214
|
+
...text.label,
|
|
5215
|
+
...statusColors(current.status, ending),
|
|
5216
|
+
padding: "0.125rem 0.625rem",
|
|
5217
|
+
borderRadius: 9999
|
|
5218
|
+
}),
|
|
5219
|
+
children: statusLabel
|
|
5220
|
+
}
|
|
5221
|
+
),
|
|
5222
|
+
pending && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5223
|
+
"span",
|
|
5224
|
+
{
|
|
5225
|
+
...el("currentPlan__scheduled", {
|
|
5226
|
+
...text.label,
|
|
5227
|
+
padding: "0.125rem 0.625rem",
|
|
5228
|
+
borderRadius: 9999,
|
|
5229
|
+
border: border()
|
|
5230
|
+
}),
|
|
5231
|
+
children: [
|
|
5232
|
+
"Changes to ",
|
|
5233
|
+
pending.name,
|
|
5234
|
+
" on ",
|
|
5235
|
+
day(pending.effective_at, locale)
|
|
5236
|
+
]
|
|
5237
|
+
}
|
|
5238
|
+
),
|
|
5239
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { ...el("currentPlan__price", { ...text.secondary, marginLeft: "auto" }), children: [
|
|
5240
|
+
formatPlanPrice(Number(current.plan.amount_cents), current.plan.currency),
|
|
5241
|
+
" / ",
|
|
5242
|
+
intervalLabel(current.plan.interval)
|
|
5243
|
+
] })
|
|
5244
|
+
] }),
|
|
5245
|
+
usage.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { ...el("currentPlan__usage", { display: "grid", gap: "0.875rem" }), children: usage.map((meter) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
5246
|
+
UsageMeter,
|
|
5247
|
+
{
|
|
5248
|
+
label: `${meter.label} used this period`,
|
|
5249
|
+
used: meter.used,
|
|
5250
|
+
limit: meter.limit,
|
|
5251
|
+
appearance
|
|
5252
|
+
},
|
|
5253
|
+
meter.key
|
|
5254
|
+
)) }),
|
|
5255
|
+
details.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
5256
|
+
"dl",
|
|
5257
|
+
{
|
|
5258
|
+
...el("currentPlan__details", {
|
|
5259
|
+
margin: 0,
|
|
5260
|
+
display: "grid",
|
|
5261
|
+
gridTemplateColumns: "repeat(auto-fit, minmax(160px, 1fr))",
|
|
5262
|
+
gap: "0.75rem"
|
|
5263
|
+
}),
|
|
5264
|
+
children: details.map((detail) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
5265
|
+
/* @__PURE__ */ jsxRuntime.jsx("dt", { style: text.meta, children: detail.label }),
|
|
5266
|
+
/* @__PURE__ */ jsxRuntime.jsx("dd", { style: { margin: "0.125rem 0 0", fontWeight: 600 }, children: detail.value })
|
|
5267
|
+
] }, detail.label))
|
|
5268
|
+
}
|
|
5269
|
+
)
|
|
5270
|
+
] })
|
|
5271
|
+
),
|
|
5272
|
+
/* @__PURE__ */ jsxRuntime.jsx(NozleBranding, {})
|
|
5273
|
+
] });
|
|
5274
|
+
}
|
|
5275
|
+
var TIER_STYLES = {
|
|
5276
|
+
free: { background: T.muted, color: T.mutedForeground, border: border() },
|
|
5277
|
+
starter: { background: T.primarySoft, color: T.primary, border: border(`color-mix(in srgb, ${T.primary} 30%, transparent)`) },
|
|
5278
|
+
pro: { background: T.primary, color: T.primaryForeground, border: border("transparent") },
|
|
5279
|
+
enterprise: { background: T.accent, color: T.accentForeground, border: border("transparent") }
|
|
3916
5280
|
};
|
|
3917
|
-
function PlanBadge({ plan, tier = "free" }) {
|
|
5281
|
+
function PlanBadge({ plan, tier = "free", appearance }) {
|
|
5282
|
+
const { root } = useAppearance(appearance);
|
|
3918
5283
|
const style = TIER_STYLES[tier] ?? TIER_STYLES.free;
|
|
3919
5284
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3920
5285
|
"span",
|
|
3921
5286
|
{
|
|
3922
|
-
|
|
5287
|
+
...root("planBadge", {
|
|
5288
|
+
...text.label,
|
|
3923
5289
|
...style,
|
|
3924
5290
|
display: "inline-flex",
|
|
3925
5291
|
alignItems: "center",
|
|
3926
5292
|
padding: "0.125rem 0.5rem",
|
|
3927
5293
|
borderRadius: "9999px",
|
|
3928
|
-
fontSize: "0.75rem",
|
|
3929
|
-
fontWeight: 600,
|
|
3930
|
-
lineHeight: "1.25rem",
|
|
3931
5294
|
whiteSpace: "nowrap"
|
|
3932
|
-
},
|
|
5295
|
+
}),
|
|
3933
5296
|
children: plan
|
|
3934
5297
|
}
|
|
3935
5298
|
);
|
|
@@ -3946,8 +5309,10 @@ var BRAND_LABELS = {
|
|
|
3946
5309
|
function PaymentMethodDisplay({
|
|
3947
5310
|
paymentMethod,
|
|
3948
5311
|
onUpdatePaymentMethod,
|
|
3949
|
-
updateHref = "/billing/update-payment"
|
|
5312
|
+
updateHref = "/billing/update-payment",
|
|
5313
|
+
appearance
|
|
3950
5314
|
}) {
|
|
5315
|
+
const { root } = useAppearance(appearance);
|
|
3951
5316
|
const handleUpdate = () => {
|
|
3952
5317
|
if (onUpdatePaymentMethod) {
|
|
3953
5318
|
onUpdatePaymentMethod();
|
|
@@ -3955,7 +5320,7 @@ function PaymentMethodDisplay({
|
|
|
3955
5320
|
window.location.href = updateHref;
|
|
3956
5321
|
}
|
|
3957
5322
|
};
|
|
3958
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", {
|
|
5323
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ...root("paymentMethod", { ...surface, display: "flex", flexDirection: "column", gap: "0.75rem" }), children: [
|
|
3959
5324
|
paymentMethod ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3960
5325
|
"div",
|
|
3961
5326
|
{
|
|
@@ -3964,87 +5329,256 @@ function PaymentMethodDisplay({
|
|
|
3964
5329
|
alignItems: "center",
|
|
3965
5330
|
gap: "0.75rem",
|
|
3966
5331
|
padding: "0.75rem 1rem",
|
|
3967
|
-
border:
|
|
3968
|
-
borderRadius:
|
|
3969
|
-
fontSize: "0.875rem"
|
|
5332
|
+
border: border(),
|
|
5333
|
+
borderRadius: `calc(${T.radius} * 0.6)`
|
|
3970
5334
|
},
|
|
3971
5335
|
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
|
-
)
|
|
5336
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 600, color: T.foreground }, children: BRAND_LABELS[paymentMethod.brand.toLowerCase()] ?? paymentMethod.brand }),
|
|
5337
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { style: text.secondary, children: [
|
|
5338
|
+
"\u2022\u2022\u2022\u2022 ",
|
|
5339
|
+
paymentMethod.last4
|
|
5340
|
+
] }),
|
|
5341
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { style: text.meta, children: [
|
|
5342
|
+
"Expires ",
|
|
5343
|
+
String(paymentMethod.expMonth).padStart(2, "0"),
|
|
5344
|
+
"/",
|
|
5345
|
+
String(paymentMethod.expYear).slice(-2)
|
|
5346
|
+
] })
|
|
4009
5347
|
]
|
|
4010
5348
|
}
|
|
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
|
-
),
|
|
5349
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("p", { style: { ...text.secondary, margin: 0 }, children: "No payment method on file." }),
|
|
4021
5350
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4022
5351
|
"button",
|
|
4023
5352
|
{
|
|
5353
|
+
type: "button",
|
|
4024
5354
|
onClick: handleUpdate,
|
|
4025
5355
|
style: {
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
color: "var(--nozle-primary, var(--primary))",
|
|
5356
|
+
...buttonStyle("outline"),
|
|
5357
|
+
alignSelf: "flex-start",
|
|
4029
5358
|
padding: "0.375rem 0.875rem",
|
|
4030
|
-
borderRadius:
|
|
4031
|
-
fontSize: "0.875rem",
|
|
4032
|
-
fontWeight: 500,
|
|
4033
|
-
cursor: "pointer"
|
|
5359
|
+
borderRadius: buttonRadius(`calc(${T.radius} / 2)`)
|
|
4034
5360
|
},
|
|
4035
5361
|
children: "Update Payment Method"
|
|
4036
5362
|
}
|
|
4037
5363
|
)
|
|
4038
5364
|
] });
|
|
4039
5365
|
}
|
|
5366
|
+
var SOURCE_LABELS = {
|
|
5367
|
+
subscription_grant: "Plan grant",
|
|
5368
|
+
top_up: "Top-up",
|
|
5369
|
+
manual_grant: "Granted",
|
|
5370
|
+
adjustment: "Adjustment",
|
|
5371
|
+
allocated_top_up: "Allocated top-up"
|
|
5372
|
+
};
|
|
5373
|
+
var muted = T.mutedForeground;
|
|
5374
|
+
function date2(value) {
|
|
5375
|
+
const parsed = new Date(value);
|
|
5376
|
+
return Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleDateString("en-US", { dateStyle: "medium" });
|
|
5377
|
+
}
|
|
5378
|
+
function CreditBalance({
|
|
5379
|
+
balance,
|
|
5380
|
+
isLoading = false,
|
|
5381
|
+
error = null,
|
|
5382
|
+
emptyMessage = "No credit balance yet.",
|
|
5383
|
+
appearance
|
|
5384
|
+
}) {
|
|
5385
|
+
const { root, el } = useAppearance(appearance);
|
|
5386
|
+
if (isLoading) return /* @__PURE__ */ jsxRuntime.jsx("div", { ...root("creditBalance", { ...surface, height: 96, background: T.skeleton }), role: "status", "aria-label": "Loading credit balance" });
|
|
5387
|
+
if (error) return /* @__PURE__ */ jsxRuntime.jsx("p", { ...root("creditBalance", { ...surface, color: T.dangerText }), role: "alert", children: typeof error === "string" ? error : error.message });
|
|
5388
|
+
if (!balance) return /* @__PURE__ */ jsxRuntime.jsx("p", { ...root("creditBalance", { ...surface, color: muted }), role: "status", children: emptyMessage });
|
|
5389
|
+
const sources = (balance.sources ?? []).filter((source) => source.remaining !== "0");
|
|
5390
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("section", { "aria-label": balance.credit_system_name, ...root("creditBalance", surface), children: [
|
|
5391
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { style: { ...text.heading, margin: 0 }, children: balance.credit_system_name }),
|
|
5392
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { style: { margin: "0.25rem 0 0" }, children: [
|
|
5393
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { ...el("creditBalance__amount", text.metric), children: formatDecimalString(balance.available) }),
|
|
5394
|
+
" ",
|
|
5395
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { style: text.secondary, children: [
|
|
5396
|
+
creditUnitLabel(balance.available, balance.unit_name),
|
|
5397
|
+
" available"
|
|
5398
|
+
] })
|
|
5399
|
+
] }),
|
|
5400
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { style: { ...text.meta, margin: "0.25rem 0 0" }, children: [
|
|
5401
|
+
"As of ",
|
|
5402
|
+
date2(balance.as_of)
|
|
5403
|
+
] }),
|
|
5404
|
+
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: [
|
|
5405
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: SOURCE_LABELS[source.type] ?? source.type }),
|
|
5406
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { style: text.secondary, children: [
|
|
5407
|
+
formatDecimalString(source.remaining),
|
|
5408
|
+
source.expires_at ? ` \xB7 expires ${date2(source.expires_at)}` : ""
|
|
5409
|
+
] })
|
|
5410
|
+
] }, source.id)) })
|
|
5411
|
+
] });
|
|
5412
|
+
}
|
|
5413
|
+
var TYPE_LABELS = {
|
|
5414
|
+
consume: "Used",
|
|
5415
|
+
grant: "Granted",
|
|
5416
|
+
adjustment: "Adjustment",
|
|
5417
|
+
expire: "Expired",
|
|
5418
|
+
revoke: "Revoked",
|
|
5419
|
+
refund: "Refunded",
|
|
5420
|
+
transfer: "Transfer"
|
|
5421
|
+
};
|
|
5422
|
+
var muted2 = T.mutedForeground;
|
|
5423
|
+
function when(value) {
|
|
5424
|
+
const parsed = new Date(value);
|
|
5425
|
+
return Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short" });
|
|
5426
|
+
}
|
|
5427
|
+
function CreditHistory({
|
|
5428
|
+
operations = [],
|
|
5429
|
+
isLoading = false,
|
|
5430
|
+
error = null,
|
|
5431
|
+
emptyMessage = "No credit activity yet.",
|
|
5432
|
+
hasMore = false,
|
|
5433
|
+
onLoadMore,
|
|
5434
|
+
appearance
|
|
5435
|
+
}) {
|
|
5436
|
+
const { root, el } = useAppearance(appearance);
|
|
5437
|
+
if (error) return /* @__PURE__ */ jsxRuntime.jsx("p", { ...root("creditHistory", { ...surface, color: T.dangerText }), role: "alert", children: typeof error === "string" ? error : error.message });
|
|
5438
|
+
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" });
|
|
5439
|
+
if (operations.length === 0) return /* @__PURE__ */ jsxRuntime.jsx("p", { ...root("creditHistory", { ...surface, color: muted2 }), role: "status", children: emptyMessage });
|
|
5440
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ...root("creditHistory", { ...text.body, minWidth: 0 }), children: [
|
|
5441
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: table.frame, children: /* @__PURE__ */ jsxRuntime.jsxs("table", { ...el("creditHistory__table", table.table), children: [
|
|
5442
|
+
/* @__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)) }) }),
|
|
5443
|
+
/* @__PURE__ */ jsxRuntime.jsx("tbody", { children: operations.map((operation, index) => {
|
|
5444
|
+
const last = index === operations.length - 1;
|
|
5445
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("tr", { ...el("creditHistory__row"), children: [
|
|
5446
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { style: table.cell(false, last), children: when(operation.occurred_at) }),
|
|
5447
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { style: table.cell(false, last), children: TYPE_LABELS[operation.type] ?? operation.type }),
|
|
5448
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { style: table.cell(false, last), children: operation.feature_code ?? "\u2014" }),
|
|
5449
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { style: table.cell(true, last), children: formatDecimalString(operation.credit_amount) }),
|
|
5450
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { style: { ...table.cell(false, last), color: operation.status === "succeeded" ? void 0 : muted2 }, children: operation.status })
|
|
5451
|
+
] }, operation.id);
|
|
5452
|
+
}) })
|
|
5453
|
+
] }) }),
|
|
5454
|
+
hasMore && onLoadMore && /* @__PURE__ */ jsxRuntime.jsx(
|
|
5455
|
+
"button",
|
|
5456
|
+
{
|
|
5457
|
+
type: "button",
|
|
5458
|
+
onClick: onLoadMore,
|
|
5459
|
+
disabled: isLoading,
|
|
5460
|
+
style: {
|
|
5461
|
+
...buttonStyle("secondary", isLoading),
|
|
5462
|
+
marginTop: "0.75rem",
|
|
5463
|
+
padding: "0.5rem 0.875rem",
|
|
5464
|
+
borderRadius: buttonRadius(`calc(${T.radius} / 2)`),
|
|
5465
|
+
opacity: 1
|
|
5466
|
+
},
|
|
5467
|
+
children: isLoading ? "Loading\u2026" : "Load more"
|
|
5468
|
+
}
|
|
5469
|
+
)
|
|
5470
|
+
] });
|
|
5471
|
+
}
|
|
5472
|
+
|
|
5473
|
+
// src/components/usage/usage-data.ts
|
|
5474
|
+
function formatAmount(amountCents, currency) {
|
|
5475
|
+
const amount = amountCents / 100;
|
|
5476
|
+
try {
|
|
5477
|
+
return new Intl.NumberFormat("en-US", { style: "currency", currency }).format(amount);
|
|
5478
|
+
} catch {
|
|
5479
|
+
return `${currency} ${amount.toFixed(2)}`;
|
|
5480
|
+
}
|
|
5481
|
+
}
|
|
5482
|
+
function formatDay(value) {
|
|
5483
|
+
const parsed = new Date(value);
|
|
5484
|
+
return Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleDateString("en-US", { dateStyle: "medium", timeZone: "UTC" });
|
|
5485
|
+
}
|
|
5486
|
+
var muted3 = T.mutedForeground;
|
|
5487
|
+
function UsageBreakdown({
|
|
5488
|
+
usage,
|
|
5489
|
+
isLoading = false,
|
|
5490
|
+
error = null,
|
|
5491
|
+
emptyMessage = "No usage in this period yet.",
|
|
5492
|
+
appearance
|
|
5493
|
+
}) {
|
|
5494
|
+
const { root, el } = useAppearance(appearance);
|
|
5495
|
+
if (isLoading) return /* @__PURE__ */ jsxRuntime.jsx("div", { ...root("usageBreakdown", { ...surface, height: 120, background: T.skeleton }), role: "status", "aria-label": "Loading usage" });
|
|
5496
|
+
if (error) return /* @__PURE__ */ jsxRuntime.jsx("p", { ...root("usageBreakdown", { ...surface, color: T.dangerText }), role: "alert", children: typeof error === "string" ? error : error.message });
|
|
5497
|
+
if (!usage || usage.charges_usage.length === 0) return /* @__PURE__ */ jsxRuntime.jsx("p", { ...root("usageBreakdown", { ...surface, color: muted3 }), role: "status", children: emptyMessage });
|
|
5498
|
+
const rows = [...usage.charges_usage].sort((a, b) => b.amount_cents - a.amount_cents);
|
|
5499
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("section", { ...root("usageBreakdown", { ...text.body, minWidth: 0 }), children: [
|
|
5500
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { style: { ...text.secondary, margin: "0 0 0.5rem" }, children: [
|
|
5501
|
+
formatDay(usage.from_datetime),
|
|
5502
|
+
" to ",
|
|
5503
|
+
formatDay(usage.to_datetime)
|
|
5504
|
+
] }),
|
|
5505
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: table.frame, children: /* @__PURE__ */ jsxRuntime.jsxs("table", { style: table.table, children: [
|
|
5506
|
+
/* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
|
|
5507
|
+
/* @__PURE__ */ jsxRuntime.jsx("th", { style: table.head(), children: "Feature" }),
|
|
5508
|
+
/* @__PURE__ */ jsxRuntime.jsx("th", { style: table.head(true), children: "Units" }),
|
|
5509
|
+
/* @__PURE__ */ jsxRuntime.jsx("th", { style: table.head(true), children: "Amount" })
|
|
5510
|
+
] }) }),
|
|
5511
|
+
/* @__PURE__ */ jsxRuntime.jsx("tbody", { children: rows.map((row, index) => {
|
|
5512
|
+
const last = index === rows.length - 1;
|
|
5513
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("tr", { ...el("usageBreakdown__row"), children: [
|
|
5514
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { style: table.cell(false, last), children: row.charge.invoice_display_name || row.feature.name }),
|
|
5515
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { style: table.cell(true, last), children: formatDecimalString(row.units, 6) }),
|
|
5516
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { style: table.cell(true, last), children: formatAmount(row.amount_cents, row.amount_currency) })
|
|
5517
|
+
] }, row.charge.nozle_id);
|
|
5518
|
+
}) }),
|
|
5519
|
+
/* @__PURE__ */ jsxRuntime.jsx("tfoot", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
|
|
5520
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { style: table.foot(), colSpan: 2, children: "Total" }),
|
|
5521
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { style: table.foot(true), children: formatAmount(usage.amount_cents, usage.currency) })
|
|
5522
|
+
] }) })
|
|
5523
|
+
] }) })
|
|
5524
|
+
] });
|
|
5525
|
+
}
|
|
5526
|
+
var muted4 = T.mutedForeground;
|
|
5527
|
+
var BAR_AREA = 120;
|
|
5528
|
+
function unitsFor(period, featureCode) {
|
|
5529
|
+
return period.charges_usage.filter((charge) => charge.feature.code === featureCode).reduce((sum, charge) => sum + (Number(charge.units) || 0), 0);
|
|
5530
|
+
}
|
|
5531
|
+
function UsageTrend({
|
|
5532
|
+
periods,
|
|
5533
|
+
featureCode,
|
|
5534
|
+
isLoading = false,
|
|
5535
|
+
error = null,
|
|
5536
|
+
emptyMessage = "No past usage yet.",
|
|
5537
|
+
appearance
|
|
5538
|
+
}) {
|
|
5539
|
+
const { root, el } = useAppearance(appearance);
|
|
5540
|
+
if (isLoading) return /* @__PURE__ */ jsxRuntime.jsx("div", { ...root("usageTrend", { ...surface, height: 160, background: T.skeleton }), role: "status", "aria-label": "Loading usage trend" });
|
|
5541
|
+
if (error) return /* @__PURE__ */ jsxRuntime.jsx("p", { ...root("usageTrend", { ...surface, color: T.dangerText }), role: "alert", children: typeof error === "string" ? error : error.message });
|
|
5542
|
+
if (!periods || periods.length === 0) return /* @__PURE__ */ jsxRuntime.jsx("p", { ...root("usageTrend", { ...surface, color: muted4 }), role: "status", children: emptyMessage });
|
|
5543
|
+
const ordered = [...periods].sort((a, b) => Date.parse(a.from_datetime) - Date.parse(b.from_datetime));
|
|
5544
|
+
const points = ordered.map((period) => ({
|
|
5545
|
+
key: period.from_datetime,
|
|
5546
|
+
label: formatDay(period.from_datetime),
|
|
5547
|
+
value: featureCode ? unitsFor(period, featureCode) : period.amount_cents,
|
|
5548
|
+
text: featureCode ? `${unitsFor(period, featureCode).toLocaleString("en-US")} ${featureCode}` : formatAmount(period.amount_cents, period.currency)
|
|
5549
|
+
}));
|
|
5550
|
+
const max = Math.max(...points.map((point) => point.value), 0);
|
|
5551
|
+
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: [
|
|
5552
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { children: point.text }),
|
|
5553
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5554
|
+
"div",
|
|
5555
|
+
{
|
|
5556
|
+
role: "img",
|
|
5557
|
+
"aria-label": `${point.label}: ${point.text}`,
|
|
5558
|
+
title: `${point.label}: ${point.text}`,
|
|
5559
|
+
...el("usageTrend__bar", {
|
|
5560
|
+
height: max > 0 ? Math.max(2, point.value / max * BAR_AREA) : 2,
|
|
5561
|
+
marginTop: 4,
|
|
5562
|
+
borderRadius: `min(4px, ${T.radius}) min(4px, ${T.radius}) 0 0`,
|
|
5563
|
+
background: T.primary
|
|
5564
|
+
})
|
|
5565
|
+
}
|
|
5566
|
+
),
|
|
5567
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { marginTop: 4 }, children: point.label })
|
|
5568
|
+
] }, point.key)) }) });
|
|
5569
|
+
}
|
|
4040
5570
|
|
|
4041
5571
|
exports.BillingContext = BillingContext;
|
|
4042
5572
|
exports.BillingPortal = BillingPortal;
|
|
4043
5573
|
exports.BillingPortalError = BillingPortalError;
|
|
4044
5574
|
exports.BillingProvider = BillingProvider;
|
|
5575
|
+
exports.BillingRequestError = BillingRequestError;
|
|
4045
5576
|
exports.CancellationControl = CancellationControl;
|
|
4046
5577
|
exports.Checkout = Checkout;
|
|
4047
5578
|
exports.CheckoutButton = CheckoutButton;
|
|
5579
|
+
exports.CreditBalance = CreditBalance;
|
|
5580
|
+
exports.CreditHistory = CreditHistory;
|
|
5581
|
+
exports.CurrentPlan = CurrentPlan;
|
|
4048
5582
|
exports.FeatureGate = FeatureGate;
|
|
4049
5583
|
exports.LockedOverlay = LockedOverlay;
|
|
4050
5584
|
exports.PaymentMethodDisplay = PaymentMethodDisplay;
|
|
@@ -4053,20 +5587,43 @@ exports.PlanCard = PlanCard;
|
|
|
4053
5587
|
exports.PlanChangeControl = PlanChangeControl;
|
|
4054
5588
|
exports.PlanComparison = PlanComparison;
|
|
4055
5589
|
exports.PlanGate = PlanGate;
|
|
5590
|
+
exports.PoweredByNozle = PoweredByNozle;
|
|
4056
5591
|
exports.PricingTable = PricingTable;
|
|
4057
5592
|
exports.UpgradeButton = UpgradeButton;
|
|
4058
5593
|
exports.UpgradeModal = UpgradeModal;
|
|
4059
5594
|
exports.UpgradePrompt = UpgradePrompt;
|
|
4060
5595
|
exports.UsageAlert = UsageAlert;
|
|
5596
|
+
exports.UsageBreakdown = UsageBreakdown;
|
|
4061
5597
|
exports.UsageDashboard = UsageDashboard;
|
|
4062
5598
|
exports.UsageGate = UsageGate;
|
|
4063
5599
|
exports.UsageMeter = UsageMeter;
|
|
5600
|
+
exports.UsageTrend = UsageTrend;
|
|
5601
|
+
exports.comparisonRows = comparisonRows;
|
|
5602
|
+
exports.currentPlanMeters = currentPlanMeters;
|
|
5603
|
+
exports.getAvailableIntervals = getAvailableIntervals;
|
|
5604
|
+
exports.getDefaultInterval = getDefaultInterval;
|
|
5605
|
+
exports.getPricingCards = getPricingCards;
|
|
5606
|
+
exports.getPricingIntervalOptions = getPricingIntervalOptions;
|
|
4064
5607
|
exports.getUsageMeterColor = getUsageMeterColor;
|
|
5608
|
+
exports.planFeatures = planFeatures;
|
|
5609
|
+
exports.sortPlansByPrice = sortPlansByPrice;
|
|
5610
|
+
exports.usageItems = usageItems;
|
|
5611
|
+
exports.useAppearance = useAppearance;
|
|
4065
5612
|
exports.useBillingContext = useBillingContext;
|
|
5613
|
+
exports.useBillingRefresh = useBillingRefresh;
|
|
4066
5614
|
exports.useCheckout = useCheckout;
|
|
4067
5615
|
exports.useCheckoutSession = useCheckout2;
|
|
5616
|
+
exports.useCreditBalance = useCreditBalance;
|
|
5617
|
+
exports.useCreditHistory = useCreditHistory;
|
|
5618
|
+
exports.useEntitlement = useEntitlement;
|
|
5619
|
+
exports.useInvoiceDownload = useInvoiceDownload;
|
|
5620
|
+
exports.useInvoices = useInvoices;
|
|
5621
|
+
exports.useNozleBranding = useNozleBranding;
|
|
4068
5622
|
exports.useNozleClient = useNozleClient;
|
|
4069
5623
|
exports.useOptionalBillingContext = useOptionalBillingContext;
|
|
4070
5624
|
exports.usePlans = usePlans;
|
|
5625
|
+
exports.useSubscription = useSubscription;
|
|
5626
|
+
exports.useUpgradePreview = useUpgradePreview;
|
|
5627
|
+
exports.useUsage = useUsage;
|
|
4071
5628
|
//# sourceMappingURL=index.cjs.map
|
|
4072
5629
|
//# sourceMappingURL=index.cjs.map
|