@nozle-js/react 0.5.3 → 0.7.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 +34 -304
- package/dist/index.cjs +400 -1909
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +122 -295
- package/dist/index.d.ts +122 -295
- package/dist/index.js +400 -1899
- package/dist/index.js.map +1 -1
- package/package.json +11 -7
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var React = require('react');
|
|
4
|
-
var centrifuge = require('centrifuge');
|
|
5
4
|
var jsxRuntime = require('react/jsx-runtime');
|
|
6
5
|
var reactStripeJs = require('@stripe/react-stripe-js');
|
|
7
6
|
var stripeJs = require('@stripe/stripe-js');
|
|
@@ -11,297 +10,244 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
|
11
10
|
var React__default = /*#__PURE__*/_interopDefault(React);
|
|
12
11
|
|
|
13
12
|
// src/provider.tsx
|
|
14
|
-
function createClient(
|
|
13
|
+
function createClient(publishableKey, baseUrl) {
|
|
15
14
|
const base = baseUrl.replace(/\/+$/, "");
|
|
16
|
-
async function apiFetch(path, init) {
|
|
17
|
-
return fetch(`${base}${path}`, {
|
|
18
|
-
...init,
|
|
19
|
-
headers: {
|
|
20
|
-
Authorization: `Bearer ${apiKey}`,
|
|
21
|
-
"Content-Type": "application/json",
|
|
22
|
-
...init?.headers
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
15
|
return {
|
|
27
|
-
|
|
16
|
+
publishableKey,
|
|
28
17
|
baseUrl: base,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
18
|
+
catalogFetch(path, init) {
|
|
19
|
+
return fetch(`${base}${path}`, {
|
|
20
|
+
...init,
|
|
21
|
+
headers: {
|
|
22
|
+
Authorization: `Bearer ${publishableKey}`,
|
|
23
|
+
"Content-Type": "application/json",
|
|
24
|
+
...init?.headers
|
|
25
|
+
}
|
|
26
|
+
});
|
|
34
27
|
}
|
|
35
28
|
};
|
|
36
29
|
}
|
|
37
|
-
var BillingContext = React.createContext(
|
|
38
|
-
client: null,
|
|
39
|
-
workspaceId: "",
|
|
40
|
-
centrifugoUrl: "",
|
|
41
|
-
centrifugoToken: null
|
|
42
|
-
});
|
|
30
|
+
var BillingContext = React.createContext(null);
|
|
43
31
|
function BillingProvider({
|
|
44
|
-
|
|
32
|
+
publishableKey,
|
|
33
|
+
createCheckout,
|
|
45
34
|
baseUrl = "https://api.nozle.app",
|
|
46
|
-
workspaceId = "",
|
|
47
|
-
centrifugoUrl,
|
|
48
35
|
children
|
|
49
36
|
}) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
React.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
setCentrifugoToken(data.token);
|
|
62
|
-
}
|
|
63
|
-
} catch {
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
void fetchCentrifugoToken();
|
|
67
|
-
return () => {
|
|
68
|
-
cancelled = true;
|
|
69
|
-
};
|
|
70
|
-
}, [client]);
|
|
71
|
-
const contextValue = {
|
|
72
|
-
client,
|
|
73
|
-
workspaceId,
|
|
74
|
-
centrifugoUrl: resolvedCentrifugoUrl,
|
|
75
|
-
centrifugoToken
|
|
76
|
-
};
|
|
37
|
+
if (!publishableKey.startsWith("pk_")) {
|
|
38
|
+
throw new Error("BillingProvider publishableKey must be a publishable key (pk_)");
|
|
39
|
+
}
|
|
40
|
+
const client = React.useMemo(
|
|
41
|
+
() => createClient(publishableKey, baseUrl),
|
|
42
|
+
[publishableKey, baseUrl]
|
|
43
|
+
);
|
|
44
|
+
const contextValue = React.useMemo(
|
|
45
|
+
() => ({ client, createCheckout }),
|
|
46
|
+
[client, createCheckout]
|
|
47
|
+
);
|
|
77
48
|
return React__default.default.createElement(BillingContext.Provider, { value: contextValue }, children);
|
|
78
49
|
}
|
|
79
|
-
function
|
|
50
|
+
function useOptionalBillingContext() {
|
|
80
51
|
return React.useContext(BillingContext);
|
|
81
52
|
}
|
|
53
|
+
function useBillingContext() {
|
|
54
|
+
const context = useOptionalBillingContext();
|
|
55
|
+
if (!context) {
|
|
56
|
+
throw new Error("useBillingContext must be used within a BillingProvider");
|
|
57
|
+
}
|
|
58
|
+
return context;
|
|
59
|
+
}
|
|
82
60
|
function useNozleClient() {
|
|
83
|
-
|
|
84
|
-
if (!client) throw new Error("useNozleClient must be used within <BillingProvider>");
|
|
85
|
-
return client;
|
|
61
|
+
return useBillingContext().client;
|
|
86
62
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
loading: true,
|
|
92
|
-
error: null
|
|
93
|
-
};
|
|
94
|
-
function useCan(customerId, featureKey) {
|
|
95
|
-
const { client, workspaceId, centrifugoUrl, centrifugoToken } = useBillingContext();
|
|
96
|
-
const [state, setState] = React.useState(initialState);
|
|
63
|
+
function usePlans() {
|
|
64
|
+
const { client } = useBillingContext();
|
|
65
|
+
const [plans, setPlans] = React.useState([]);
|
|
66
|
+
const [isLoading, setIsLoading] = React.useState(true);
|
|
97
67
|
React.useEffect(() => {
|
|
98
|
-
if (!client) {
|
|
99
|
-
setState({
|
|
100
|
-
...initialState,
|
|
101
|
-
loading: false,
|
|
102
|
-
error: "No client in context"
|
|
103
|
-
});
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
68
|
let cancelled = false;
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
try {
|
|
110
|
-
const result = await client.can(customerId, featureKey);
|
|
111
|
-
if (!cancelled) {
|
|
112
|
-
setState({
|
|
113
|
-
allowed: result.allowed,
|
|
114
|
-
remaining: result.remaining,
|
|
115
|
-
limit: result.limit,
|
|
116
|
-
loading: false,
|
|
117
|
-
error: result.error ?? null
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
} catch (err) {
|
|
121
|
-
if (!cancelled) {
|
|
122
|
-
const message = err instanceof Error ? err.message : "Unknown error";
|
|
123
|
-
setState({
|
|
124
|
-
allowed: false,
|
|
125
|
-
remaining: null,
|
|
126
|
-
limit: null,
|
|
127
|
-
loading: false,
|
|
128
|
-
error: message
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
void checkEntitlement();
|
|
134
|
-
return () => {
|
|
135
|
-
cancelled = true;
|
|
136
|
-
};
|
|
137
|
-
}, [client, customerId, featureKey]);
|
|
138
|
-
React.useEffect(() => {
|
|
139
|
-
if (!centrifugoToken || !centrifugoUrl || !workspaceId) {
|
|
69
|
+
if (!client) {
|
|
70
|
+
setIsLoading(false);
|
|
140
71
|
return;
|
|
141
72
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const data = ctx.data;
|
|
149
|
-
const pubCustomerId = data.customerId ?? data.customer_id;
|
|
150
|
-
const pubFeatureKey = data.featureKey ?? data.feature_key;
|
|
151
|
-
if (pubCustomerId === customerId && pubFeatureKey === featureKey) {
|
|
152
|
-
setState((prev) => ({
|
|
153
|
-
...prev,
|
|
154
|
-
allowed: data.allowed ?? prev.allowed,
|
|
155
|
-
remaining: data.remaining !== void 0 ? data.remaining : prev.remaining,
|
|
156
|
-
limit: data.limit !== void 0 ? data.limit : prev.limit
|
|
157
|
-
}));
|
|
158
|
-
}
|
|
73
|
+
void client.catalogFetch("/api/v1/plans").then(async (response) => {
|
|
74
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
75
|
+
const data = await response.json();
|
|
76
|
+
if (!cancelled) setPlans(data.plans ?? []);
|
|
77
|
+
}).finally(() => {
|
|
78
|
+
if (!cancelled) setIsLoading(false);
|
|
159
79
|
});
|
|
160
|
-
sub.subscribe();
|
|
161
|
-
cf.connect();
|
|
162
80
|
return () => {
|
|
163
|
-
|
|
164
|
-
cf.disconnect();
|
|
81
|
+
cancelled = true;
|
|
165
82
|
};
|
|
166
|
-
}, [
|
|
167
|
-
return
|
|
83
|
+
}, [client]);
|
|
84
|
+
return { plans, isLoading };
|
|
168
85
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
async
|
|
86
|
+
|
|
87
|
+
// src/components/billing/checkout-navigation.ts
|
|
88
|
+
function navigateToCheckout(url, location = window.location) {
|
|
89
|
+
location.assign(url);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/hooks/use-checkout.ts
|
|
93
|
+
function useCheckout() {
|
|
94
|
+
const { createCheckout } = useBillingContext();
|
|
95
|
+
const [isLoading, setIsLoading] = React.useState(false);
|
|
96
|
+
const [error, setError] = React.useState(null);
|
|
97
|
+
const [checkout, setCheckout] = React.useState(null);
|
|
98
|
+
const fetchClientSecret = React.useCallback(
|
|
99
|
+
async (planCode, returnUrl) => {
|
|
100
|
+
setIsLoading(true);
|
|
101
|
+
setError(null);
|
|
183
102
|
try {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const baseUrl = clientInternal.baseUrl ?? "https://api.nozle.app";
|
|
187
|
-
const timeout = clientInternal.timeout ?? 5e3;
|
|
188
|
-
const controller = new AbortController();
|
|
189
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
190
|
-
const response = await fetch(
|
|
191
|
-
`${baseUrl}/api/v1/usage/${customerId}/${metricKey}`,
|
|
192
|
-
{
|
|
193
|
-
method: "GET",
|
|
194
|
-
headers: {
|
|
195
|
-
Authorization: `Bearer ${apiKey}`
|
|
196
|
-
},
|
|
197
|
-
signal: controller.signal
|
|
198
|
-
}
|
|
199
|
-
);
|
|
200
|
-
clearTimeout(timeoutId);
|
|
201
|
-
if (!response.ok) {
|
|
202
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
103
|
+
if (!createCheckout) {
|
|
104
|
+
throw new Error("BillingProvider createCheckout callback is required for checkout");
|
|
203
105
|
}
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
106
|
+
const result = await createCheckout({
|
|
107
|
+
planCode,
|
|
108
|
+
returnUrl: returnUrl ?? window.location.href
|
|
109
|
+
});
|
|
110
|
+
setCheckout(result);
|
|
111
|
+
if ("client_secret" in result || "clientSecret" in result) {
|
|
112
|
+
const clientSecret = result.client_secret ?? result.clientSecret;
|
|
113
|
+
if (clientSecret) return clientSecret;
|
|
114
|
+
}
|
|
115
|
+
if ("url" in result && result.url) {
|
|
116
|
+
navigateToCheckout(result.url);
|
|
117
|
+
return null;
|
|
211
118
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const message = err instanceof Error ? err.message : "Unknown error";
|
|
215
|
-
setState({ value: null, loading: false, error: message });
|
|
119
|
+
if ("type" in result && (result.type === "completed" || result.type === "scheduled")) {
|
|
120
|
+
return null;
|
|
216
121
|
}
|
|
122
|
+
throw new Error("Checkout response did not include a supported result type");
|
|
123
|
+
} catch (cause) {
|
|
124
|
+
const checkoutError = cause instanceof Error ? cause : new Error(String(cause));
|
|
125
|
+
setError(checkoutError);
|
|
126
|
+
throw checkoutError;
|
|
127
|
+
} finally {
|
|
128
|
+
setIsLoading(false);
|
|
217
129
|
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
};
|
|
223
|
-
}, [client, customerId, metricKey]);
|
|
224
|
-
return state;
|
|
130
|
+
},
|
|
131
|
+
[createCheckout]
|
|
132
|
+
);
|
|
133
|
+
return { fetchClientSecret, checkout, isLoading, error };
|
|
225
134
|
}
|
|
226
|
-
function
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
loading: true,
|
|
231
|
-
error: null
|
|
232
|
-
});
|
|
233
|
-
React.useEffect(() => {
|
|
234
|
-
if (!client) {
|
|
235
|
-
setState({
|
|
236
|
-
balance: null,
|
|
237
|
-
loading: false,
|
|
238
|
-
error: "No client in context"
|
|
239
|
-
});
|
|
135
|
+
function loadRazorpayScript() {
|
|
136
|
+
return new Promise((resolve, reject) => {
|
|
137
|
+
if (document.getElementById("razorpay-js")) {
|
|
138
|
+
resolve();
|
|
240
139
|
return;
|
|
241
140
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const controller = new AbortController();
|
|
250
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
251
|
-
const response = await fetch(
|
|
252
|
-
`${baseUrl}/api/v1/credits/${customerId}/balance`,
|
|
253
|
-
{
|
|
254
|
-
method: "GET",
|
|
255
|
-
headers: {
|
|
256
|
-
Authorization: `Bearer ${apiKey}`
|
|
257
|
-
},
|
|
258
|
-
signal: controller.signal
|
|
259
|
-
}
|
|
260
|
-
);
|
|
261
|
-
clearTimeout(timeoutId);
|
|
262
|
-
if (!response.ok) {
|
|
263
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
264
|
-
}
|
|
265
|
-
const data = await response.json();
|
|
266
|
-
if (!cancelled) {
|
|
267
|
-
setState({
|
|
268
|
-
balance: data.balance ?? data.amount ?? null,
|
|
269
|
-
loading: false,
|
|
270
|
-
error: null
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
} catch (err) {
|
|
274
|
-
if (!cancelled) {
|
|
275
|
-
const message = err instanceof Error ? err.message : "Unknown error";
|
|
276
|
-
setState({ balance: null, loading: false, error: message });
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
void fetchCredits();
|
|
281
|
-
return () => {
|
|
282
|
-
cancelled = true;
|
|
283
|
-
};
|
|
284
|
-
}, [client, customerId]);
|
|
285
|
-
return state;
|
|
141
|
+
const script = document.createElement("script");
|
|
142
|
+
script.id = "razorpay-js";
|
|
143
|
+
script.src = "https://checkout.razorpay.com/v1/checkout.js";
|
|
144
|
+
script.onload = () => resolve();
|
|
145
|
+
script.onerror = () => reject(new Error("Failed to load Razorpay.js"));
|
|
146
|
+
document.body.appendChild(script);
|
|
147
|
+
});
|
|
286
148
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
);
|
|
290
|
-
|
|
291
|
-
const ctx = React.useContext(BillingPortalContext);
|
|
292
|
-
if (!ctx) {
|
|
293
|
-
throw new Error(
|
|
294
|
-
"useBillingPortal must be used inside BillingPortalProvider"
|
|
295
|
-
);
|
|
149
|
+
async function handleCheckoutResult(result, options) {
|
|
150
|
+
if ("type" in result && result.type === "scheduled") {
|
|
151
|
+
options.onScheduled?.(result);
|
|
152
|
+
return;
|
|
296
153
|
}
|
|
297
|
-
|
|
154
|
+
if ("type" in result && result.type === "completed") {
|
|
155
|
+
options.onComplete?.(result);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if ("type" in result && result.type === "razorpay") {
|
|
159
|
+
if (!options.razorpayKeyId) throw new Error("razorpayKeyId is required for Razorpay checkout");
|
|
160
|
+
await loadRazorpayScript();
|
|
161
|
+
const checkout = new window.Razorpay({
|
|
162
|
+
key: options.razorpayKeyId,
|
|
163
|
+
order_id: result.orderId,
|
|
164
|
+
handler: (response) => options.onSuccess?.(response.razorpay_payment_id)
|
|
165
|
+
});
|
|
166
|
+
checkout.open();
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if ("url" in result && result.url) {
|
|
170
|
+
window.location.assign(result.url);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if ("type" in result && result.type === "stripe") {
|
|
174
|
+
const clientSecret = result.clientSecret ?? result.client_secret;
|
|
175
|
+
if (clientSecret && options.onStripeClientSecret) {
|
|
176
|
+
options.onStripeClientSecret(clientSecret);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (clientSecret) throw new Error("onStripeClientSecret is required for embedded Stripe checkout");
|
|
180
|
+
throw new Error("Stripe checkout did not include a URL or client secret");
|
|
181
|
+
}
|
|
182
|
+
throw new Error("Unknown checkout response format");
|
|
298
183
|
}
|
|
299
|
-
function
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
184
|
+
function CheckoutButton({
|
|
185
|
+
planCode,
|
|
186
|
+
returnUrl,
|
|
187
|
+
label = "Get Started",
|
|
188
|
+
className,
|
|
189
|
+
style,
|
|
190
|
+
onError,
|
|
191
|
+
razorpayKeyId,
|
|
192
|
+
onStripeClientSecret,
|
|
193
|
+
onSuccess,
|
|
194
|
+
onComplete,
|
|
195
|
+
onScheduled
|
|
303
196
|
}) {
|
|
304
|
-
|
|
197
|
+
const { createCheckout } = useBillingContext();
|
|
198
|
+
const [loading, setLoading] = React.useState(false);
|
|
199
|
+
const [error, setError] = React.useState(null);
|
|
200
|
+
async function handleClick() {
|
|
201
|
+
setLoading(true);
|
|
202
|
+
setError(null);
|
|
203
|
+
try {
|
|
204
|
+
if (!createCheckout) {
|
|
205
|
+
throw new Error("BillingProvider createCheckout callback is required for checkout");
|
|
206
|
+
}
|
|
207
|
+
const result = await createCheckout({
|
|
208
|
+
planCode,
|
|
209
|
+
returnUrl: returnUrl ?? window.location.href
|
|
210
|
+
});
|
|
211
|
+
await handleCheckoutResult(result, {
|
|
212
|
+
razorpayKeyId,
|
|
213
|
+
onStripeClientSecret,
|
|
214
|
+
onSuccess,
|
|
215
|
+
onComplete,
|
|
216
|
+
onScheduled
|
|
217
|
+
});
|
|
218
|
+
} catch (cause) {
|
|
219
|
+
const checkoutError = cause instanceof Error ? cause : new Error("Checkout failed");
|
|
220
|
+
setError(checkoutError);
|
|
221
|
+
onError?.(checkoutError);
|
|
222
|
+
} finally {
|
|
223
|
+
setLoading(false);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
227
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
228
|
+
"button",
|
|
229
|
+
{
|
|
230
|
+
type: "button",
|
|
231
|
+
onClick: () => void handleClick(),
|
|
232
|
+
disabled: loading,
|
|
233
|
+
className,
|
|
234
|
+
style: {
|
|
235
|
+
padding: "0.75rem 1.5rem",
|
|
236
|
+
borderRadius: "var(--nozle-radius, 0.5rem)",
|
|
237
|
+
border: "none",
|
|
238
|
+
background: "var(--nozle-primary, var(--primary))",
|
|
239
|
+
color: "var(--nozle-primary-foreground, var(--primary-foreground))",
|
|
240
|
+
cursor: loading ? "not-allowed" : "pointer",
|
|
241
|
+
fontWeight: 500,
|
|
242
|
+
opacity: loading ? 0.7 : 1,
|
|
243
|
+
...style
|
|
244
|
+
},
|
|
245
|
+
"aria-busy": loading,
|
|
246
|
+
children: loading ? "Loading..." : label
|
|
247
|
+
}
|
|
248
|
+
),
|
|
249
|
+
error && /* @__PURE__ */ jsxRuntime.jsx("span", { role: "alert", children: error.message })
|
|
250
|
+
] });
|
|
305
251
|
}
|
|
306
252
|
var SYMBOLS = { USD: "$", EUR: "\u20AC", GBP: "\xA3", JPY: "\xA5" };
|
|
307
253
|
function fmtPrice(cents, cur) {
|
|
@@ -553,28 +499,38 @@ function Skeleton() {
|
|
|
553
499
|
] }, i)) });
|
|
554
500
|
}
|
|
555
501
|
function PricingTable({
|
|
556
|
-
customerId,
|
|
557
502
|
currentPlanCode,
|
|
558
503
|
plans: propPlans,
|
|
559
504
|
features,
|
|
560
505
|
onSelect,
|
|
506
|
+
returnUrl,
|
|
507
|
+
onStripeClientSecret,
|
|
508
|
+
onCheckoutResult,
|
|
509
|
+
onCheckoutError,
|
|
561
510
|
showToggle = true,
|
|
562
511
|
highlightPlan,
|
|
563
512
|
className,
|
|
564
513
|
enterpriseEmail
|
|
565
514
|
}) {
|
|
566
|
-
const
|
|
515
|
+
const billing = useOptionalBillingContext();
|
|
516
|
+
const client = billing?.client;
|
|
567
517
|
const [plans, setPlans] = React.useState(propPlans ?? []);
|
|
568
518
|
const [currentPlan, setCurrentPlan] = React.useState(currentPlanCode ?? null);
|
|
569
519
|
const [isAnnual, setIsAnnual] = React.useState(false);
|
|
570
520
|
const [loading, setLoading] = React.useState(!propPlans);
|
|
521
|
+
const [checkoutError, setCheckoutError] = React.useState(null);
|
|
571
522
|
React.useEffect(() => {
|
|
572
523
|
if (propPlans) {
|
|
573
524
|
setPlans(propPlans);
|
|
574
525
|
return;
|
|
575
526
|
}
|
|
527
|
+
if (!client) {
|
|
528
|
+
setLoading(false);
|
|
529
|
+
setCheckoutError("BillingProvider with a publishableKey is required to load plans");
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
576
532
|
let cancelled = false;
|
|
577
|
-
client.
|
|
533
|
+
client.catalogFetch("/api/v1/plans").then(async (res) => {
|
|
578
534
|
if (!cancelled && res.ok) {
|
|
579
535
|
const data = await res.json();
|
|
580
536
|
setPlans(data.plans || []);
|
|
@@ -587,23 +543,30 @@ function PricingTable({
|
|
|
587
543
|
};
|
|
588
544
|
}, [client, propPlans]);
|
|
589
545
|
React.useEffect(() => {
|
|
590
|
-
if (currentPlanCode !== void 0)
|
|
591
|
-
|
|
546
|
+
if (currentPlanCode !== void 0) setCurrentPlan(currentPlanCode);
|
|
547
|
+
}, [currentPlanCode]);
|
|
548
|
+
async function selectPlan(plan) {
|
|
549
|
+
setCheckoutError(null);
|
|
550
|
+
if (onSelect) {
|
|
551
|
+
onSelect(plan);
|
|
592
552
|
return;
|
|
593
553
|
}
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
if (!cancelled && res.ok) {
|
|
598
|
-
const data = await res.json();
|
|
599
|
-
setCurrentPlan(data.plan ?? data.planName ?? null);
|
|
554
|
+
try {
|
|
555
|
+
if (!billing?.createCheckout) {
|
|
556
|
+
throw new Error("BillingProvider createCheckout callback is required for checkout");
|
|
600
557
|
}
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
558
|
+
const result = await billing.createCheckout({
|
|
559
|
+
planCode: plan.code,
|
|
560
|
+
returnUrl: returnUrl ?? window.location.href
|
|
561
|
+
});
|
|
562
|
+
onCheckoutResult?.(result);
|
|
563
|
+
await handleCheckoutResult(result, { onStripeClientSecret });
|
|
564
|
+
} catch (cause) {
|
|
565
|
+
const error = cause instanceof Error ? cause : new Error("Checkout failed");
|
|
566
|
+
setCheckoutError(error.message);
|
|
567
|
+
onCheckoutError?.(error);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
607
570
|
const hasAnnual = plans.some((p) => p.interval === "yearly");
|
|
608
571
|
const filtered = showToggle && hasAnnual ? plans.filter((p) => p.interval === (isAnnual ? "yearly" : "monthly") || isEnterprise(p)) : plans;
|
|
609
572
|
if (loading) return /* @__PURE__ */ jsxRuntime.jsx(Skeleton, {});
|
|
@@ -613,6 +576,7 @@ function PricingTable({
|
|
|
613
576
|
background: C.bg
|
|
614
577
|
}, children: [
|
|
615
578
|
showToggle && hasAnnual && /* @__PURE__ */ jsxRuntime.jsx(Toggle, { isAnnual, onChange: setIsAnnual }),
|
|
579
|
+
checkoutError && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", children: checkoutError }),
|
|
616
580
|
/* @__PURE__ */ jsxRuntime.jsx("div", { style: {
|
|
617
581
|
display: "grid",
|
|
618
582
|
gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
|
|
@@ -626,7 +590,7 @@ function PricingTable({
|
|
|
626
590
|
isHighlighted: highlightPlan === plan.code,
|
|
627
591
|
isEnt: isEnterprise(plan),
|
|
628
592
|
enterpriseEmail,
|
|
629
|
-
onSelect:
|
|
593
|
+
onSelect: () => void selectPlan(plan)
|
|
630
594
|
},
|
|
631
595
|
plan.code
|
|
632
596
|
)) })
|
|
@@ -641,6 +605,7 @@ function PlanCard({
|
|
|
641
605
|
description,
|
|
642
606
|
isAnnual,
|
|
643
607
|
isCurrent,
|
|
608
|
+
returnUrl,
|
|
644
609
|
onSelect
|
|
645
610
|
}) {
|
|
646
611
|
const price = isAnnual ? annualPrice : monthlyPrice;
|
|
@@ -756,7 +721,10 @@ function PlanCard({
|
|
|
756
721
|
onSelect && /* @__PURE__ */ jsxRuntime.jsx(
|
|
757
722
|
"button",
|
|
758
723
|
{
|
|
759
|
-
onClick: onSelect
|
|
724
|
+
onClick: () => onSelect?.({
|
|
725
|
+
planCode: id,
|
|
726
|
+
returnUrl: returnUrl ?? window.location.href
|
|
727
|
+
}),
|
|
760
728
|
style: {
|
|
761
729
|
background: isCurrent ? "var(--nozle-muted, var(--muted))" : "var(--nozle-primary, var(--primary))",
|
|
762
730
|
color: isCurrent ? "var(--nozle-muted-foreground, var(--muted-foreground))" : "var(--nozle-primary-foreground, var(--primary-foreground))",
|
|
@@ -878,113 +846,8 @@ function PlanComparison({
|
|
|
878
846
|
}
|
|
879
847
|
);
|
|
880
848
|
}
|
|
881
|
-
function loadRazorpayScript() {
|
|
882
|
-
return new Promise((resolve, reject) => {
|
|
883
|
-
if (document.getElementById("razorpay-js")) {
|
|
884
|
-
resolve();
|
|
885
|
-
return;
|
|
886
|
-
}
|
|
887
|
-
const script = document.createElement("script");
|
|
888
|
-
script.id = "razorpay-js";
|
|
889
|
-
script.src = "https://checkout.razorpay.com/v1/checkout.js";
|
|
890
|
-
script.onload = () => resolve();
|
|
891
|
-
script.onerror = () => reject(new Error("Failed to load Razorpay.js"));
|
|
892
|
-
document.body.appendChild(script);
|
|
893
|
-
});
|
|
894
|
-
}
|
|
895
|
-
function CheckoutButton({
|
|
896
|
-
planId,
|
|
897
|
-
label = "Get Started",
|
|
898
|
-
apiBaseUrl = "https://api.nozle.app",
|
|
899
|
-
className,
|
|
900
|
-
style,
|
|
901
|
-
onError,
|
|
902
|
-
razorpayKeyId,
|
|
903
|
-
onStripeClientSecret,
|
|
904
|
-
onSuccess
|
|
905
|
-
}) {
|
|
906
|
-
const { customerId, apiKey } = useBillingPortal();
|
|
907
|
-
const [loading, setLoading] = React.useState(false);
|
|
908
|
-
async function handleClick() {
|
|
909
|
-
setLoading(true);
|
|
910
|
-
try {
|
|
911
|
-
const response = await fetch(`${apiBaseUrl}/api/v1/checkout`, {
|
|
912
|
-
method: "POST",
|
|
913
|
-
headers: {
|
|
914
|
-
"Content-Type": "application/json",
|
|
915
|
-
Authorization: `Bearer ${apiKey}`
|
|
916
|
-
},
|
|
917
|
-
body: JSON.stringify({ planId, customerId })
|
|
918
|
-
});
|
|
919
|
-
if (!response.ok) {
|
|
920
|
-
throw new Error(`HTTP ${response.status}: Checkout failed`);
|
|
921
|
-
}
|
|
922
|
-
const data = await response.json();
|
|
923
|
-
if ("type" in data && data.type === "razorpay") {
|
|
924
|
-
if (!razorpayKeyId) {
|
|
925
|
-
onError?.(
|
|
926
|
-
new Error("razorpayKeyId prop required for Razorpay checkout")
|
|
927
|
-
);
|
|
928
|
-
return;
|
|
929
|
-
}
|
|
930
|
-
await loadRazorpayScript();
|
|
931
|
-
const rzp = new window.Razorpay({
|
|
932
|
-
key: razorpayKeyId,
|
|
933
|
-
order_id: data.orderId,
|
|
934
|
-
handler: (r) => {
|
|
935
|
-
onSuccess?.(r.razorpay_payment_id);
|
|
936
|
-
},
|
|
937
|
-
modal: {
|
|
938
|
-
ondismiss: () => setLoading(false)
|
|
939
|
-
}
|
|
940
|
-
});
|
|
941
|
-
rzp.open();
|
|
942
|
-
} else if ("type" in data && data.type === "stripe") {
|
|
943
|
-
if (data.url) {
|
|
944
|
-
window.location.href = data.url;
|
|
945
|
-
} else if (data.clientSecret) {
|
|
946
|
-
onStripeClientSecret?.(data.clientSecret);
|
|
947
|
-
} else {
|
|
948
|
-
onError?.(
|
|
949
|
-
new Error("Stripe checkout: no url or clientSecret in response")
|
|
950
|
-
);
|
|
951
|
-
}
|
|
952
|
-
} else if ("url" in data && data.url) {
|
|
953
|
-
window.location.href = data.url;
|
|
954
|
-
} else {
|
|
955
|
-
onError?.(new Error("Unknown checkout response format"));
|
|
956
|
-
}
|
|
957
|
-
} catch (err) {
|
|
958
|
-
const error = err instanceof Error ? err : new Error("Checkout failed");
|
|
959
|
-
onError?.(error);
|
|
960
|
-
} finally {
|
|
961
|
-
setLoading(false);
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
965
|
-
"button",
|
|
966
|
-
{
|
|
967
|
-
onClick: () => void handleClick(),
|
|
968
|
-
disabled: loading,
|
|
969
|
-
className,
|
|
970
|
-
style: {
|
|
971
|
-
padding: "0.75rem 1.5rem",
|
|
972
|
-
borderRadius: "var(--nozle-radius, 0.5rem)",
|
|
973
|
-
border: "none",
|
|
974
|
-
background: "var(--nozle-primary, var(--primary))",
|
|
975
|
-
color: "var(--nozle-primary-foreground, var(--primary-foreground))",
|
|
976
|
-
cursor: loading ? "not-allowed" : "pointer",
|
|
977
|
-
fontWeight: 500,
|
|
978
|
-
opacity: loading ? 0.7 : 1,
|
|
979
|
-
...style
|
|
980
|
-
},
|
|
981
|
-
"aria-busy": loading,
|
|
982
|
-
children: loading ? "Loading..." : label
|
|
983
|
-
}
|
|
984
|
-
);
|
|
985
|
-
}
|
|
986
849
|
var CheckoutContext = React.createContext(null);
|
|
987
|
-
function
|
|
850
|
+
function useCheckout2() {
|
|
988
851
|
const ctx = React.useContext(CheckoutContext);
|
|
989
852
|
if (ctx === null) {
|
|
990
853
|
throw new Error("useCheckout must be used inside a <Checkout> component");
|
|
@@ -1015,7 +878,8 @@ function CheckoutInner({
|
|
|
1015
878
|
onError,
|
|
1016
879
|
onReady,
|
|
1017
880
|
className,
|
|
1018
|
-
style
|
|
881
|
+
style,
|
|
882
|
+
children
|
|
1019
883
|
}) {
|
|
1020
884
|
const stripe = reactStripeJs.useStripe();
|
|
1021
885
|
const elements = reactStripeJs.useElements();
|
|
@@ -1065,7 +929,7 @@ function CheckoutInner({
|
|
|
1065
929
|
}
|
|
1066
930
|
),
|
|
1067
931
|
error !== null && /* @__PURE__ */ jsxRuntime.jsx("p", { style: { color: "var(--nozle-destructive, #dc2626)", margin: "0.5rem 0 0" }, children: error.message }),
|
|
1068
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
932
|
+
children ?? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1069
933
|
"button",
|
|
1070
934
|
{
|
|
1071
935
|
type: "button",
|
|
@@ -1095,16 +959,25 @@ function Checkout({
|
|
|
1095
959
|
stripeAccount,
|
|
1096
960
|
submitLabel,
|
|
1097
961
|
onSuccess,
|
|
962
|
+
onComplete,
|
|
1098
963
|
onError,
|
|
1099
964
|
onReady,
|
|
1100
965
|
className,
|
|
1101
|
-
style
|
|
966
|
+
style,
|
|
967
|
+
children
|
|
1102
968
|
}) {
|
|
1103
969
|
const stripePromise = React.useMemo(
|
|
1104
970
|
() => stripeJs.loadStripe(publishableKey, stripeAccount ? { stripeAccount } : void 0),
|
|
1105
971
|
[publishableKey, stripeAccount]
|
|
1106
972
|
);
|
|
1107
973
|
const appearance = React.useMemo(() => buildStripeAppearance(), []);
|
|
974
|
+
const embeddedOptions = React.useMemo(
|
|
975
|
+
() => ({ clientSecret, onComplete }),
|
|
976
|
+
[clientSecret, onComplete]
|
|
977
|
+
);
|
|
978
|
+
if (clientSecret.startsWith("cs_")) {
|
|
979
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className, style, children: /* @__PURE__ */ jsxRuntime.jsx(reactStripeJs.EmbeddedCheckoutProvider, { stripe: stripePromise, options: embeddedOptions, children: /* @__PURE__ */ jsxRuntime.jsx(reactStripeJs.EmbeddedCheckout, {}) }) });
|
|
980
|
+
}
|
|
1108
981
|
const options = { clientSecret, appearance };
|
|
1109
982
|
return /* @__PURE__ */ jsxRuntime.jsx(reactStripeJs.Elements, { stripe: stripePromise, options, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1110
983
|
CheckoutInner,
|
|
@@ -1114,278 +987,150 @@ function Checkout({
|
|
|
1114
987
|
onError,
|
|
1115
988
|
onReady,
|
|
1116
989
|
className,
|
|
1117
|
-
style
|
|
990
|
+
style,
|
|
991
|
+
children
|
|
1118
992
|
}
|
|
1119
993
|
) }, clientSecret);
|
|
1120
994
|
}
|
|
1121
995
|
function UpgradeModal({
|
|
1122
996
|
isOpen,
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
997
|
+
planCode,
|
|
998
|
+
returnUrl,
|
|
999
|
+
preview,
|
|
1000
|
+
onStripeClientSecret,
|
|
1001
|
+
onCheckoutStarted,
|
|
1002
|
+
onCompleted,
|
|
1003
|
+
onScheduled,
|
|
1127
1004
|
onConfirm,
|
|
1128
|
-
onCancel
|
|
1005
|
+
onCancel,
|
|
1006
|
+
onError
|
|
1129
1007
|
}) {
|
|
1130
|
-
const
|
|
1131
|
-
const [loading, setLoading] = React.useState(false);
|
|
1008
|
+
const { createCheckout } = useBillingContext();
|
|
1132
1009
|
const [error, setError] = React.useState(null);
|
|
1133
1010
|
const [confirming, setConfirming] = React.useState(false);
|
|
1134
|
-
React.useEffect(() => {
|
|
1135
|
-
if (!isOpen || !targetPlanId || !customerId) {
|
|
1136
|
-
return;
|
|
1137
|
-
}
|
|
1138
|
-
let cancelled = false;
|
|
1139
|
-
setLoading(true);
|
|
1140
|
-
setError(null);
|
|
1141
|
-
setPreview(null);
|
|
1142
|
-
async function fetchPreview() {
|
|
1143
|
-
try {
|
|
1144
|
-
const response = await fetch(
|
|
1145
|
-
`${apiBaseUrl}/api/v1/subscriptions/preview`,
|
|
1146
|
-
{
|
|
1147
|
-
method: "POST",
|
|
1148
|
-
headers: {
|
|
1149
|
-
"Content-Type": "application/json",
|
|
1150
|
-
Authorization: `Bearer ${apiKey}`
|
|
1151
|
-
},
|
|
1152
|
-
body: JSON.stringify({ targetPlanId, customerId })
|
|
1153
|
-
}
|
|
1154
|
-
);
|
|
1155
|
-
if (!response.ok) {
|
|
1156
|
-
throw new Error(
|
|
1157
|
-
`HTTP ${response.status}: Failed to load proration preview`
|
|
1158
|
-
);
|
|
1159
|
-
}
|
|
1160
|
-
const data = await response.json();
|
|
1161
|
-
if (!cancelled) {
|
|
1162
|
-
setPreview(data);
|
|
1163
|
-
}
|
|
1164
|
-
} catch (err) {
|
|
1165
|
-
if (!cancelled) {
|
|
1166
|
-
const message = err instanceof Error ? err.message : "Failed to load preview";
|
|
1167
|
-
setError(message);
|
|
1168
|
-
}
|
|
1169
|
-
} finally {
|
|
1170
|
-
if (!cancelled) {
|
|
1171
|
-
setLoading(false);
|
|
1172
|
-
}
|
|
1173
|
-
}
|
|
1174
|
-
}
|
|
1175
|
-
void fetchPreview();
|
|
1176
|
-
return () => {
|
|
1177
|
-
cancelled = true;
|
|
1178
|
-
};
|
|
1179
|
-
}, [isOpen, targetPlanId, customerId, apiBaseUrl, apiKey]);
|
|
1180
1011
|
async function handleConfirm() {
|
|
1181
1012
|
setConfirming(true);
|
|
1013
|
+
setError(null);
|
|
1182
1014
|
try {
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
{
|
|
1186
|
-
method: "POST",
|
|
1187
|
-
headers: {
|
|
1188
|
-
"Content-Type": "application/json",
|
|
1189
|
-
Authorization: `Bearer ${apiKey}`
|
|
1190
|
-
},
|
|
1191
|
-
body: JSON.stringify({ targetPlanId, customerId })
|
|
1192
|
-
}
|
|
1193
|
-
);
|
|
1194
|
-
if (!response.ok) {
|
|
1195
|
-
throw new Error(`HTTP ${response.status}: Upgrade failed`);
|
|
1015
|
+
if (!createCheckout) {
|
|
1016
|
+
throw new Error("BillingProvider createCheckout callback is required for checkout");
|
|
1196
1017
|
}
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1018
|
+
const result = await createCheckout({
|
|
1019
|
+
planCode,
|
|
1020
|
+
returnUrl: returnUrl ?? window.location.href
|
|
1021
|
+
});
|
|
1022
|
+
await handleCheckoutResult(result, {
|
|
1023
|
+
onStripeClientSecret: (clientSecret) => {
|
|
1024
|
+
onStripeClientSecret?.(clientSecret);
|
|
1025
|
+
onCheckoutStarted?.();
|
|
1026
|
+
},
|
|
1027
|
+
onComplete: () => onCompleted?.(),
|
|
1028
|
+
onScheduled: () => onScheduled?.()
|
|
1029
|
+
});
|
|
1030
|
+
onConfirm?.();
|
|
1031
|
+
} catch (cause) {
|
|
1032
|
+
const checkoutError = cause instanceof Error ? cause : new Error("Checkout failed");
|
|
1033
|
+
setError(checkoutError.message);
|
|
1034
|
+
onError?.(checkoutError);
|
|
1035
|
+
} finally {
|
|
1202
1036
|
setConfirming(false);
|
|
1203
1037
|
}
|
|
1204
1038
|
}
|
|
1205
|
-
if (!isOpen)
|
|
1206
|
-
return null;
|
|
1207
|
-
}
|
|
1208
|
-
const overlayStyle = {
|
|
1209
|
-
position: "fixed",
|
|
1210
|
-
inset: 0,
|
|
1211
|
-
background: "oklch(0 0 0 / 0.5)",
|
|
1212
|
-
display: "flex",
|
|
1213
|
-
alignItems: "center",
|
|
1214
|
-
justifyContent: "center",
|
|
1215
|
-
zIndex: 1e3
|
|
1216
|
-
};
|
|
1217
|
-
const modalStyle = {
|
|
1218
|
-
background: "var(--nozle-background, var(--background))",
|
|
1219
|
-
color: "var(--nozle-foreground, var(--foreground))",
|
|
1220
|
-
border: "1px solid var(--nozle-border, var(--border))",
|
|
1221
|
-
borderRadius: "var(--nozle-radius, 0.5rem)",
|
|
1222
|
-
padding: "2rem",
|
|
1223
|
-
maxWidth: "28rem",
|
|
1224
|
-
width: "100%",
|
|
1225
|
-
boxShadow: "var(--nozle-shadow, 0 4px 6px -1px rgb(0 0 0 / 0.1))"
|
|
1226
|
-
};
|
|
1039
|
+
if (!isOpen) return null;
|
|
1227
1040
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1228
1041
|
"div",
|
|
1229
1042
|
{
|
|
1230
|
-
style:
|
|
1043
|
+
style: {
|
|
1044
|
+
position: "fixed",
|
|
1045
|
+
inset: 0,
|
|
1046
|
+
background: "oklch(0 0 0 / 0.5)",
|
|
1047
|
+
display: "flex",
|
|
1048
|
+
alignItems: "center",
|
|
1049
|
+
justifyContent: "center",
|
|
1050
|
+
zIndex: 1e3
|
|
1051
|
+
},
|
|
1231
1052
|
role: "dialog",
|
|
1232
1053
|
"aria-modal": "true",
|
|
1233
|
-
"aria-label": "
|
|
1234
|
-
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
{
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
children: "
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1254
|
-
"div",
|
|
1255
|
-
{
|
|
1256
|
-
style: {
|
|
1257
|
-
background: "var(--nozle-muted, var(--muted))",
|
|
1258
|
-
borderRadius: "var(--nozle-radius, 0.5rem)",
|
|
1259
|
-
padding: "1rem",
|
|
1260
|
-
display: "flex",
|
|
1261
|
-
flexDirection: "column",
|
|
1262
|
-
gap: "0.5rem"
|
|
1263
|
-
},
|
|
1264
|
-
children: [
|
|
1265
|
-
preview.credit > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1266
|
-
"div",
|
|
1267
|
-
{
|
|
1268
|
-
style: { display: "flex", justifyContent: "space-between" },
|
|
1269
|
-
children: [
|
|
1270
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Credit from current plan" }),
|
|
1271
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { style: { color: "oklch(0.6 0.15 150)" }, children: [
|
|
1272
|
-
"-$",
|
|
1273
|
-
preview.credit.toFixed(2)
|
|
1274
|
-
] })
|
|
1275
|
-
]
|
|
1276
|
-
}
|
|
1277
|
-
),
|
|
1278
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
|
|
1279
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "New plan charge" }),
|
|
1280
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
1281
|
-
"$",
|
|
1282
|
-
preview.debit.toFixed(2)
|
|
1283
|
-
] })
|
|
1284
|
-
] }),
|
|
1285
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1286
|
-
"div",
|
|
1287
|
-
{
|
|
1288
|
-
style: {
|
|
1289
|
-
display: "flex",
|
|
1290
|
-
justifyContent: "space-between",
|
|
1291
|
-
fontWeight: 600,
|
|
1292
|
-
borderTop: "1px solid var(--nozle-border, var(--border))",
|
|
1293
|
-
paddingTop: "0.5rem",
|
|
1294
|
-
marginTop: "0.25rem"
|
|
1295
|
-
},
|
|
1296
|
-
children: [
|
|
1297
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "You'll be charged today" }),
|
|
1298
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
1299
|
-
"$",
|
|
1300
|
-
preview.net.toFixed(2)
|
|
1301
|
-
] })
|
|
1302
|
-
]
|
|
1303
|
-
}
|
|
1304
|
-
)
|
|
1305
|
-
]
|
|
1306
|
-
}
|
|
1307
|
-
),
|
|
1308
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1309
|
-
"p",
|
|
1310
|
-
{
|
|
1311
|
-
style: {
|
|
1312
|
-
fontSize: "0.875rem",
|
|
1313
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))",
|
|
1314
|
-
marginTop: "0.75rem"
|
|
1315
|
-
},
|
|
1316
|
-
children: [
|
|
1317
|
-
"Your next billing date is",
|
|
1318
|
-
" ",
|
|
1319
|
-
new Date(preview.nextBillingDate).toLocaleDateString(),
|
|
1320
|
-
"."
|
|
1321
|
-
]
|
|
1322
|
-
}
|
|
1323
|
-
)
|
|
1324
|
-
] }),
|
|
1325
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1326
|
-
"div",
|
|
1327
|
-
{
|
|
1328
|
-
style: {
|
|
1329
|
-
display: "flex",
|
|
1330
|
-
gap: "0.75rem",
|
|
1331
|
-
justifyContent: "flex-end"
|
|
1332
|
-
},
|
|
1333
|
-
children: [
|
|
1334
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1335
|
-
"button",
|
|
1054
|
+
"aria-label": "Change plan",
|
|
1055
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1056
|
+
"div",
|
|
1057
|
+
{
|
|
1058
|
+
style: {
|
|
1059
|
+
background: "var(--nozle-background, var(--background))",
|
|
1060
|
+
color: "var(--nozle-foreground, var(--foreground))",
|
|
1061
|
+
border: "1px solid var(--nozle-border, var(--border))",
|
|
1062
|
+
borderRadius: "var(--nozle-radius, 0.5rem)",
|
|
1063
|
+
padding: "2rem",
|
|
1064
|
+
maxWidth: "28rem",
|
|
1065
|
+
width: "100%",
|
|
1066
|
+
boxShadow: "var(--nozle-shadow, 0 4px 6px -1px rgb(0 0 0 / 0.1))"
|
|
1067
|
+
},
|
|
1068
|
+
children: [
|
|
1069
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { style: { fontSize: "1.25rem", fontWeight: 600, margin: "0 0 1rem" }, children: "Confirm plan change" }),
|
|
1070
|
+
error && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", style: { color: "oklch(0.6 0.2 25)" }, children: error }),
|
|
1071
|
+
preview && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: "1.5rem" }, children: [
|
|
1072
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1073
|
+
"div",
|
|
1336
1074
|
{
|
|
1337
|
-
onClick: onCancel,
|
|
1338
1075
|
style: {
|
|
1339
|
-
|
|
1076
|
+
background: "var(--nozle-muted, var(--muted))",
|
|
1340
1077
|
borderRadius: "var(--nozle-radius, 0.5rem)",
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1078
|
+
padding: "1rem",
|
|
1079
|
+
display: "flex",
|
|
1080
|
+
flexDirection: "column",
|
|
1081
|
+
gap: "0.5rem"
|
|
1345
1082
|
},
|
|
1346
|
-
|
|
1347
|
-
|
|
1083
|
+
children: [
|
|
1084
|
+
preview.credit > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1085
|
+
"Credits applied: -$",
|
|
1086
|
+
preview.credit.toFixed(2)
|
|
1087
|
+
] }),
|
|
1088
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1089
|
+
"New plan charge: $",
|
|
1090
|
+
preview.debit.toFixed(2)
|
|
1091
|
+
] }),
|
|
1092
|
+
/* @__PURE__ */ jsxRuntime.jsxs("strong", { children: [
|
|
1093
|
+
"Due today: $",
|
|
1094
|
+
preview.net.toFixed(2)
|
|
1095
|
+
] })
|
|
1096
|
+
]
|
|
1348
1097
|
}
|
|
1349
1098
|
),
|
|
1350
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1351
|
-
"
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
},
|
|
1364
|
-
disabled: confirming || loading || !!error,
|
|
1365
|
-
children: confirming ? "Upgrading..." : "Confirm Upgrade"
|
|
1366
|
-
}
|
|
1367
|
-
)
|
|
1368
|
-
]
|
|
1369
|
-
}
|
|
1370
|
-
)
|
|
1371
|
-
] })
|
|
1099
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { style: { fontSize: "0.875rem" }, children: [
|
|
1100
|
+
"Next billing date: ",
|
|
1101
|
+
new Date(preview.nextBillingDate).toLocaleDateString(),
|
|
1102
|
+
"."
|
|
1103
|
+
] })
|
|
1104
|
+
] }),
|
|
1105
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: "0.75rem", justifyContent: "flex-end" }, children: [
|
|
1106
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: onCancel, disabled: confirming, children: "Cancel" }),
|
|
1107
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => void handleConfirm(), disabled: confirming, children: confirming ? "Starting checkout..." : "Confirm" })
|
|
1108
|
+
] })
|
|
1109
|
+
]
|
|
1110
|
+
}
|
|
1111
|
+
)
|
|
1372
1112
|
}
|
|
1373
1113
|
);
|
|
1374
1114
|
}
|
|
1375
1115
|
function UpgradeButton({
|
|
1376
|
-
|
|
1116
|
+
planCode,
|
|
1117
|
+
returnUrl,
|
|
1118
|
+
preview,
|
|
1377
1119
|
label = "Upgrade",
|
|
1378
|
-
apiBaseUrl = "https://api.nozle.app",
|
|
1379
1120
|
className,
|
|
1380
1121
|
style,
|
|
1381
|
-
|
|
1122
|
+
onStripeClientSecret,
|
|
1123
|
+
onCheckoutStarted,
|
|
1124
|
+
onDowngradeScheduled,
|
|
1125
|
+
onUpgraded,
|
|
1126
|
+
onError
|
|
1382
1127
|
}) {
|
|
1383
|
-
const { customerId, apiKey } = useBillingPortal();
|
|
1384
1128
|
const [isOpen, setIsOpen] = React.useState(false);
|
|
1385
1129
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1386
1130
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1387
1131
|
"button",
|
|
1388
1132
|
{
|
|
1133
|
+
type: "button",
|
|
1389
1134
|
onClick: () => setIsOpen(true),
|
|
1390
1135
|
className,
|
|
1391
1136
|
style: {
|
|
@@ -1405,275 +1150,36 @@ function UpgradeButton({
|
|
|
1405
1150
|
UpgradeModal,
|
|
1406
1151
|
{
|
|
1407
1152
|
isOpen,
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1153
|
+
planCode,
|
|
1154
|
+
returnUrl,
|
|
1155
|
+
preview,
|
|
1156
|
+
onStripeClientSecret,
|
|
1157
|
+
onCheckoutStarted: () => {
|
|
1413
1158
|
setIsOpen(false);
|
|
1414
|
-
|
|
1159
|
+
onCheckoutStarted?.();
|
|
1415
1160
|
},
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
] });
|
|
1420
|
-
}
|
|
1421
|
-
var PRESET_AMOUNTS = [10, 25, 50, 100, 250];
|
|
1422
|
-
function CreditTopUpButton({
|
|
1423
|
-
label = "Add Credits",
|
|
1424
|
-
apiBaseUrl = "https://api.nozle.app",
|
|
1425
|
-
className,
|
|
1426
|
-
style,
|
|
1427
|
-
onSuccess,
|
|
1428
|
-
onError
|
|
1429
|
-
}) {
|
|
1430
|
-
const { customerId, apiKey } = useBillingPortal();
|
|
1431
|
-
const [isOpen, setIsOpen] = React.useState(false);
|
|
1432
|
-
const [selectedAmount, setSelectedAmount] = React.useState(25);
|
|
1433
|
-
const [customAmount, setCustomAmount] = React.useState("");
|
|
1434
|
-
const [useCustom, setUseCustom] = React.useState(false);
|
|
1435
|
-
const [loading, setLoading] = React.useState(false);
|
|
1436
|
-
const [error, setError] = React.useState(null);
|
|
1437
|
-
const finalAmount = useCustom ? parseFloat(customAmount) || 0 : selectedAmount;
|
|
1438
|
-
async function handlePurchase() {
|
|
1439
|
-
if (finalAmount <= 0) {
|
|
1440
|
-
setError("Please enter a valid amount");
|
|
1441
|
-
return;
|
|
1442
|
-
}
|
|
1443
|
-
setLoading(true);
|
|
1444
|
-
setError(null);
|
|
1445
|
-
try {
|
|
1446
|
-
const response = await fetch(`${apiBaseUrl}/api/v1/credits/purchase`, {
|
|
1447
|
-
method: "POST",
|
|
1448
|
-
headers: {
|
|
1449
|
-
"Content-Type": "application/json",
|
|
1450
|
-
Authorization: `Bearer ${apiKey}`
|
|
1161
|
+
onCompleted: () => {
|
|
1162
|
+
setIsOpen(false);
|
|
1163
|
+
onUpgraded?.();
|
|
1451
1164
|
},
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
throw new Error(`HTTP ${response.status}: Credit purchase failed`);
|
|
1456
|
-
}
|
|
1457
|
-
setIsOpen(false);
|
|
1458
|
-
onSuccess?.(finalAmount);
|
|
1459
|
-
} catch (err) {
|
|
1460
|
-
const purchaseError = err instanceof Error ? err : new Error("Credit purchase failed");
|
|
1461
|
-
setError(purchaseError.message);
|
|
1462
|
-
onError?.(purchaseError);
|
|
1463
|
-
} finally {
|
|
1464
|
-
setLoading(false);
|
|
1465
|
-
}
|
|
1466
|
-
}
|
|
1467
|
-
const overlayStyle = {
|
|
1468
|
-
position: "fixed",
|
|
1469
|
-
inset: 0,
|
|
1470
|
-
background: "oklch(0 0 0 / 0.5)",
|
|
1471
|
-
display: "flex",
|
|
1472
|
-
alignItems: "center",
|
|
1473
|
-
justifyContent: "center",
|
|
1474
|
-
zIndex: 1e3
|
|
1475
|
-
};
|
|
1476
|
-
const dialogStyle = {
|
|
1477
|
-
background: "var(--nozle-background, var(--background))",
|
|
1478
|
-
color: "var(--nozle-foreground, var(--foreground))",
|
|
1479
|
-
border: "1px solid var(--nozle-border, var(--border))",
|
|
1480
|
-
borderRadius: "var(--nozle-radius, 0.5rem)",
|
|
1481
|
-
padding: "2rem",
|
|
1482
|
-
maxWidth: "24rem",
|
|
1483
|
-
width: "100%"
|
|
1484
|
-
};
|
|
1485
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1486
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1487
|
-
"button",
|
|
1488
|
-
{
|
|
1489
|
-
onClick: () => setIsOpen(true),
|
|
1490
|
-
className,
|
|
1491
|
-
style: {
|
|
1492
|
-
padding: "0.75rem 1.5rem",
|
|
1493
|
-
borderRadius: "var(--nozle-radius, 0.5rem)",
|
|
1494
|
-
border: "none",
|
|
1495
|
-
background: "var(--nozle-primary, var(--primary))",
|
|
1496
|
-
color: "var(--nozle-primary-foreground, var(--primary-foreground))",
|
|
1497
|
-
cursor: "pointer",
|
|
1498
|
-
fontWeight: 500,
|
|
1499
|
-
...style
|
|
1165
|
+
onScheduled: () => {
|
|
1166
|
+
setIsOpen(false);
|
|
1167
|
+
onDowngradeScheduled?.();
|
|
1500
1168
|
},
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
),
|
|
1504
|
-
isOpen && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1505
|
-
"div",
|
|
1506
|
-
{
|
|
1507
|
-
style: overlayStyle,
|
|
1508
|
-
role: "dialog",
|
|
1509
|
-
"aria-modal": "true",
|
|
1510
|
-
"aria-label": "Add credits",
|
|
1511
|
-
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: dialogStyle, children: [
|
|
1512
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1513
|
-
"h2",
|
|
1514
|
-
{
|
|
1515
|
-
style: {
|
|
1516
|
-
fontSize: "1.25rem",
|
|
1517
|
-
fontWeight: 600,
|
|
1518
|
-
margin: "0 0 1.25rem"
|
|
1519
|
-
},
|
|
1520
|
-
children: "Add Credits"
|
|
1521
|
-
}
|
|
1522
|
-
),
|
|
1523
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1524
|
-
"div",
|
|
1525
|
-
{
|
|
1526
|
-
style: {
|
|
1527
|
-
display: "flex",
|
|
1528
|
-
flexWrap: "wrap",
|
|
1529
|
-
gap: "0.5rem",
|
|
1530
|
-
marginBottom: "1rem"
|
|
1531
|
-
},
|
|
1532
|
-
children: PRESET_AMOUNTS.map((amount) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1533
|
-
"button",
|
|
1534
|
-
{
|
|
1535
|
-
onClick: () => {
|
|
1536
|
-
setUseCustom(false);
|
|
1537
|
-
setSelectedAmount(amount);
|
|
1538
|
-
},
|
|
1539
|
-
style: {
|
|
1540
|
-
padding: "0.5rem 1rem",
|
|
1541
|
-
borderRadius: "var(--nozle-radius, 0.5rem)",
|
|
1542
|
-
border: "1px solid var(--nozle-border, var(--border))",
|
|
1543
|
-
background: !useCustom && selectedAmount === amount ? "var(--nozle-primary, var(--primary))" : "transparent",
|
|
1544
|
-
color: !useCustom && selectedAmount === amount ? "var(--nozle-primary-foreground, var(--primary-foreground))" : "var(--nozle-foreground, var(--foreground))",
|
|
1545
|
-
cursor: "pointer"
|
|
1546
|
-
},
|
|
1547
|
-
"aria-pressed": !useCustom && selectedAmount === amount,
|
|
1548
|
-
children: [
|
|
1549
|
-
"$",
|
|
1550
|
-
amount
|
|
1551
|
-
]
|
|
1552
|
-
},
|
|
1553
|
-
amount
|
|
1554
|
-
))
|
|
1555
|
-
}
|
|
1556
|
-
),
|
|
1557
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: "1.25rem" }, children: [
|
|
1558
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1559
|
-
"label",
|
|
1560
|
-
{
|
|
1561
|
-
style: {
|
|
1562
|
-
display: "flex",
|
|
1563
|
-
alignItems: "center",
|
|
1564
|
-
gap: "0.5rem",
|
|
1565
|
-
marginBottom: "0.5rem",
|
|
1566
|
-
fontSize: "0.875rem",
|
|
1567
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))"
|
|
1568
|
-
},
|
|
1569
|
-
children: [
|
|
1570
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1571
|
-
"input",
|
|
1572
|
-
{
|
|
1573
|
-
type: "checkbox",
|
|
1574
|
-
checked: useCustom,
|
|
1575
|
-
onChange: (e) => setUseCustom(e.target.checked)
|
|
1576
|
-
}
|
|
1577
|
-
),
|
|
1578
|
-
"Custom amount"
|
|
1579
|
-
]
|
|
1580
|
-
}
|
|
1581
|
-
),
|
|
1582
|
-
useCustom && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1583
|
-
"input",
|
|
1584
|
-
{
|
|
1585
|
-
type: "number",
|
|
1586
|
-
value: customAmount,
|
|
1587
|
-
onChange: (e) => setCustomAmount(e.target.value),
|
|
1588
|
-
placeholder: "Enter amount",
|
|
1589
|
-
min: 1,
|
|
1590
|
-
style: {
|
|
1591
|
-
width: "100%",
|
|
1592
|
-
padding: "0.5rem 0.75rem",
|
|
1593
|
-
borderRadius: "var(--nozle-radius, 0.5rem)",
|
|
1594
|
-
border: "1px solid var(--nozle-border, var(--border))",
|
|
1595
|
-
background: "var(--nozle-background, var(--background))",
|
|
1596
|
-
color: "var(--nozle-foreground, var(--foreground))",
|
|
1597
|
-
boxSizing: "border-box"
|
|
1598
|
-
}
|
|
1599
|
-
}
|
|
1600
|
-
)
|
|
1601
|
-
] }),
|
|
1602
|
-
error && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1603
|
-
"p",
|
|
1604
|
-
{
|
|
1605
|
-
style: {
|
|
1606
|
-
color: "oklch(0.6 0.2 25)",
|
|
1607
|
-
marginBottom: "1rem",
|
|
1608
|
-
fontSize: "0.875rem"
|
|
1609
|
-
},
|
|
1610
|
-
children: error
|
|
1611
|
-
}
|
|
1612
|
-
),
|
|
1613
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1614
|
-
"div",
|
|
1615
|
-
{
|
|
1616
|
-
style: {
|
|
1617
|
-
display: "flex",
|
|
1618
|
-
gap: "0.75rem",
|
|
1619
|
-
justifyContent: "flex-end"
|
|
1620
|
-
},
|
|
1621
|
-
children: [
|
|
1622
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1623
|
-
"button",
|
|
1624
|
-
{
|
|
1625
|
-
onClick: () => setIsOpen(false),
|
|
1626
|
-
style: {
|
|
1627
|
-
padding: "0.5rem 1rem",
|
|
1628
|
-
borderRadius: "var(--nozle-radius, 0.5rem)",
|
|
1629
|
-
border: "1px solid var(--nozle-border, var(--border))",
|
|
1630
|
-
background: "transparent",
|
|
1631
|
-
color: "var(--nozle-foreground, var(--foreground))",
|
|
1632
|
-
cursor: "pointer"
|
|
1633
|
-
},
|
|
1634
|
-
disabled: loading,
|
|
1635
|
-
children: "Cancel"
|
|
1636
|
-
}
|
|
1637
|
-
),
|
|
1638
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1639
|
-
"button",
|
|
1640
|
-
{
|
|
1641
|
-
onClick: () => void handlePurchase(),
|
|
1642
|
-
style: {
|
|
1643
|
-
padding: "0.5rem 1rem",
|
|
1644
|
-
borderRadius: "var(--nozle-radius, 0.5rem)",
|
|
1645
|
-
border: "none",
|
|
1646
|
-
background: "var(--nozle-primary, var(--primary))",
|
|
1647
|
-
color: "var(--nozle-primary-foreground, var(--primary-foreground))",
|
|
1648
|
-
cursor: loading ? "not-allowed" : "pointer",
|
|
1649
|
-
fontWeight: 500,
|
|
1650
|
-
opacity: loading ? 0.7 : 1
|
|
1651
|
-
},
|
|
1652
|
-
disabled: loading || finalAmount <= 0,
|
|
1653
|
-
children: loading ? "Processing..." : `Add $${finalAmount}`
|
|
1654
|
-
}
|
|
1655
|
-
)
|
|
1656
|
-
]
|
|
1657
|
-
}
|
|
1658
|
-
)
|
|
1659
|
-
] })
|
|
1169
|
+
onError,
|
|
1170
|
+
onCancel: () => setIsOpen(false)
|
|
1660
1171
|
}
|
|
1661
1172
|
)
|
|
1662
1173
|
] });
|
|
1663
1174
|
}
|
|
1664
1175
|
function FeatureGate({
|
|
1665
|
-
|
|
1666
|
-
|
|
1176
|
+
allowed,
|
|
1177
|
+
loading = false,
|
|
1667
1178
|
fallback = null,
|
|
1668
1179
|
children
|
|
1669
1180
|
}) {
|
|
1670
|
-
|
|
1671
|
-
if (
|
|
1672
|
-
return null;
|
|
1673
|
-
}
|
|
1674
|
-
if (!allowed) {
|
|
1675
|
-
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallback });
|
|
1676
|
-
}
|
|
1181
|
+
if (loading) return null;
|
|
1182
|
+
if (!allowed) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallback });
|
|
1677
1183
|
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
|
|
1678
1184
|
}
|
|
1679
1185
|
function UsageGate({
|
|
@@ -1938,288 +1444,26 @@ function UsageAlert({
|
|
|
1938
1444
|
f.key
|
|
1939
1445
|
)) });
|
|
1940
1446
|
}
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
if (loading) {
|
|
1947
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1948
|
-
"div",
|
|
1949
|
-
{
|
|
1950
|
-
className: "animate-pulse h-8 w-24 rounded",
|
|
1951
|
-
style: { background: "var(--nozle-border, var(--border))" }
|
|
1952
|
-
}
|
|
1953
|
-
);
|
|
1954
|
-
}
|
|
1955
|
-
if (error) {
|
|
1956
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1957
|
-
"span",
|
|
1958
|
-
{
|
|
1959
|
-
style: { color: "var(--nozle-destructive, var(--destructive))" },
|
|
1960
|
-
className: "text-sm",
|
|
1961
|
-
children: "Failed to load balance"
|
|
1962
|
-
}
|
|
1963
|
-
);
|
|
1964
|
-
}
|
|
1965
|
-
const amount = balance ?? 0;
|
|
1966
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1967
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { "aria-label": "wallet icon", style: { fontSize: "1.25rem" }, role: "img", children: "\u{1F4B3}" }),
|
|
1968
|
-
amount === 0 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1969
|
-
"span",
|
|
1970
|
-
{
|
|
1971
|
-
style: {
|
|
1972
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))"
|
|
1973
|
-
},
|
|
1974
|
-
className: "text-sm",
|
|
1975
|
-
children: "No credits"
|
|
1976
|
-
}
|
|
1977
|
-
) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold", children: new Intl.NumberFormat("en-US", {
|
|
1978
|
-
style: "currency",
|
|
1979
|
-
currency,
|
|
1980
|
-
minimumFractionDigits: 2
|
|
1981
|
-
}).format(amount) })
|
|
1982
|
-
] });
|
|
1983
|
-
}
|
|
1984
|
-
var TYPE_BADGE_STYLES = {
|
|
1985
|
-
grant: {
|
|
1986
|
-
background: "var(--nozle-success-bg, #dcfce7)",
|
|
1987
|
-
color: "var(--nozle-success, #16a34a)"
|
|
1447
|
+
var TIER_STYLES = {
|
|
1448
|
+
free: {
|
|
1449
|
+
background: "var(--nozle-muted, var(--muted, #f1f5f9))",
|
|
1450
|
+
color: "var(--nozle-muted-foreground, var(--muted-foreground, #64748b))",
|
|
1451
|
+
border: "1px solid var(--nozle-border, var(--border, #e2e8f0))"
|
|
1988
1452
|
},
|
|
1989
|
-
|
|
1990
|
-
background: "var(--nozle-
|
|
1991
|
-
color: "var(--nozle-
|
|
1453
|
+
starter: {
|
|
1454
|
+
background: "color-mix(in srgb, var(--nozle-primary, var(--primary, #3b82f6)) 10%, transparent)",
|
|
1455
|
+
color: "var(--nozle-primary, var(--primary, #3b82f6))",
|
|
1456
|
+
border: "1px solid color-mix(in srgb, var(--nozle-primary, var(--primary, #3b82f6)) 30%, transparent)"
|
|
1992
1457
|
},
|
|
1993
|
-
|
|
1994
|
-
background: "var(--nozle-
|
|
1995
|
-
color: "var(--nozle-
|
|
1458
|
+
pro: {
|
|
1459
|
+
background: "var(--nozle-primary, var(--primary, #3b82f6))",
|
|
1460
|
+
color: "var(--nozle-primary-foreground, var(--primary-foreground, #ffffff))",
|
|
1461
|
+
border: "1px solid transparent"
|
|
1996
1462
|
},
|
|
1997
|
-
|
|
1998
|
-
background: "var(--nozle-
|
|
1999
|
-
color: "var(--nozle-
|
|
2000
|
-
|
|
2001
|
-
};
|
|
2002
|
-
function getTypeBadgeStyle(type) {
|
|
2003
|
-
return TYPE_BADGE_STYLES[type] ?? TYPE_BADGE_STYLES.application;
|
|
2004
|
-
}
|
|
2005
|
-
function formatDate(iso) {
|
|
2006
|
-
try {
|
|
2007
|
-
return new Date(iso).toLocaleDateString("en-US", {
|
|
2008
|
-
year: "numeric",
|
|
2009
|
-
month: "short",
|
|
2010
|
-
day: "numeric"
|
|
2011
|
-
});
|
|
2012
|
-
} catch {
|
|
2013
|
-
return iso;
|
|
2014
|
-
}
|
|
2015
|
-
}
|
|
2016
|
-
function CreditHistory({
|
|
2017
|
-
customerId,
|
|
2018
|
-
viewAllHref = "/billing/credits"
|
|
2019
|
-
}) {
|
|
2020
|
-
const { client } = useBillingContext();
|
|
2021
|
-
const [transactions, setTransactions] = React.useState([]);
|
|
2022
|
-
const [loading, setLoading] = React.useState(true);
|
|
2023
|
-
const [error, setError] = React.useState(null);
|
|
2024
|
-
React.useEffect(() => {
|
|
2025
|
-
if (!client) {
|
|
2026
|
-
setError("No client in context");
|
|
2027
|
-
setLoading(false);
|
|
2028
|
-
return;
|
|
2029
|
-
}
|
|
2030
|
-
let cancelled = false;
|
|
2031
|
-
async function fetchHistory() {
|
|
2032
|
-
try {
|
|
2033
|
-
const clientInternal = client;
|
|
2034
|
-
const apiKey = clientInternal.apiKey ?? "";
|
|
2035
|
-
const baseUrl = clientInternal.baseUrl ?? "https://api.nozle.app";
|
|
2036
|
-
const timeout = clientInternal.timeout ?? 5e3;
|
|
2037
|
-
const controller = new AbortController();
|
|
2038
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
2039
|
-
const response = await fetch(
|
|
2040
|
-
`${baseUrl}/api/v1/credits/${customerId}/transactions`,
|
|
2041
|
-
{
|
|
2042
|
-
headers: { Authorization: `Bearer ${apiKey}` },
|
|
2043
|
-
signal: controller.signal
|
|
2044
|
-
}
|
|
2045
|
-
);
|
|
2046
|
-
clearTimeout(timeoutId);
|
|
2047
|
-
if (!response.ok) {
|
|
2048
|
-
throw new Error(`HTTP ${response.status}`);
|
|
2049
|
-
}
|
|
2050
|
-
const data = await response.json();
|
|
2051
|
-
if (!cancelled) {
|
|
2052
|
-
setTransactions(
|
|
2053
|
-
(data.transactions ?? []).slice().sort(
|
|
2054
|
-
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
|
2055
|
-
)
|
|
2056
|
-
);
|
|
2057
|
-
setLoading(false);
|
|
2058
|
-
}
|
|
2059
|
-
} catch (err) {
|
|
2060
|
-
if (!cancelled) {
|
|
2061
|
-
setError(err instanceof Error ? err.message : "Unknown error");
|
|
2062
|
-
setLoading(false);
|
|
2063
|
-
}
|
|
2064
|
-
}
|
|
2065
|
-
}
|
|
2066
|
-
void fetchHistory();
|
|
2067
|
-
return () => {
|
|
2068
|
-
cancelled = true;
|
|
2069
|
-
};
|
|
2070
|
-
}, [client, customerId]);
|
|
2071
|
-
if (loading) {
|
|
2072
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-pulse space-y-2", children: [1, 2, 3].map((i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2073
|
-
"div",
|
|
2074
|
-
{
|
|
2075
|
-
className: "h-6 rounded",
|
|
2076
|
-
style: { background: "var(--nozle-border, var(--border))" }
|
|
2077
|
-
},
|
|
2078
|
-
i
|
|
2079
|
-
)) });
|
|
2080
|
-
}
|
|
2081
|
-
if (error) {
|
|
2082
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2083
|
-
"span",
|
|
2084
|
-
{
|
|
2085
|
-
style: { color: "var(--nozle-destructive, var(--destructive))" },
|
|
2086
|
-
className: "text-sm",
|
|
2087
|
-
children: "Failed to load transactions"
|
|
2088
|
-
}
|
|
2089
|
-
);
|
|
2090
|
-
}
|
|
2091
|
-
const displayed = transactions.slice(0, 20);
|
|
2092
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
2093
|
-
/* @__PURE__ */ jsxRuntime.jsxs("table", { style: { width: "100%", borderCollapse: "collapse" }, children: [
|
|
2094
|
-
/* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2095
|
-
"tr",
|
|
2096
|
-
{
|
|
2097
|
-
style: {
|
|
2098
|
-
borderBottom: "1px solid var(--nozle-border, var(--border))",
|
|
2099
|
-
fontSize: "0.75rem",
|
|
2100
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))"
|
|
2101
|
-
},
|
|
2102
|
-
children: [
|
|
2103
|
-
/* @__PURE__ */ jsxRuntime.jsx("th", { style: { textAlign: "left", padding: "0.25rem 0.5rem" }, children: "Type" }),
|
|
2104
|
-
/* @__PURE__ */ jsxRuntime.jsx("th", { style: { textAlign: "right", padding: "0.25rem 0.5rem" }, children: "Amount" }),
|
|
2105
|
-
/* @__PURE__ */ jsxRuntime.jsx("th", { style: { textAlign: "left", padding: "0.25rem 0.5rem" }, children: "Description" }),
|
|
2106
|
-
/* @__PURE__ */ jsxRuntime.jsx("th", { style: { textAlign: "right", padding: "0.25rem 0.5rem" }, children: "Date" })
|
|
2107
|
-
]
|
|
2108
|
-
}
|
|
2109
|
-
) }),
|
|
2110
|
-
/* @__PURE__ */ jsxRuntime.jsx("tbody", { children: displayed.map((tx) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2111
|
-
"tr",
|
|
2112
|
-
{
|
|
2113
|
-
style: {
|
|
2114
|
-
borderBottom: "1px solid var(--nozle-border, var(--border))",
|
|
2115
|
-
fontSize: "0.875rem"
|
|
2116
|
-
},
|
|
2117
|
-
children: [
|
|
2118
|
-
/* @__PURE__ */ jsxRuntime.jsx("td", { style: { padding: "0.5rem" }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2119
|
-
"span",
|
|
2120
|
-
{
|
|
2121
|
-
style: {
|
|
2122
|
-
...getTypeBadgeStyle(tx.type),
|
|
2123
|
-
padding: "0.125rem 0.5rem",
|
|
2124
|
-
borderRadius: "9999px",
|
|
2125
|
-
fontSize: "0.75rem",
|
|
2126
|
-
fontWeight: 500
|
|
2127
|
-
},
|
|
2128
|
-
children: tx.type
|
|
2129
|
-
}
|
|
2130
|
-
) }),
|
|
2131
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2132
|
-
"td",
|
|
2133
|
-
{
|
|
2134
|
-
style: {
|
|
2135
|
-
textAlign: "right",
|
|
2136
|
-
padding: "0.5rem",
|
|
2137
|
-
fontVariantNumeric: "tabular-nums",
|
|
2138
|
-
color: tx.type === "deduct" ? "var(--nozle-destructive, var(--destructive))" : "var(--nozle-success, #16a34a)"
|
|
2139
|
-
},
|
|
2140
|
-
children: [
|
|
2141
|
-
tx.type === "deduct" ? "-" : "+",
|
|
2142
|
-
Math.abs(tx.amount).toLocaleString()
|
|
2143
|
-
]
|
|
2144
|
-
}
|
|
2145
|
-
),
|
|
2146
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2147
|
-
"td",
|
|
2148
|
-
{
|
|
2149
|
-
style: {
|
|
2150
|
-
padding: "0.5rem",
|
|
2151
|
-
color: "var(--nozle-foreground, var(--foreground))"
|
|
2152
|
-
},
|
|
2153
|
-
children: tx.description
|
|
2154
|
-
}
|
|
2155
|
-
),
|
|
2156
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2157
|
-
"td",
|
|
2158
|
-
{
|
|
2159
|
-
style: {
|
|
2160
|
-
textAlign: "right",
|
|
2161
|
-
padding: "0.5rem",
|
|
2162
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))",
|
|
2163
|
-
fontSize: "0.75rem"
|
|
2164
|
-
},
|
|
2165
|
-
children: formatDate(tx.createdAt)
|
|
2166
|
-
}
|
|
2167
|
-
)
|
|
2168
|
-
]
|
|
2169
|
-
},
|
|
2170
|
-
tx.id
|
|
2171
|
-
)) })
|
|
2172
|
-
] }),
|
|
2173
|
-
transactions.length > 20 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { textAlign: "right" }, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2174
|
-
"a",
|
|
2175
|
-
{
|
|
2176
|
-
href: viewAllHref,
|
|
2177
|
-
style: {
|
|
2178
|
-
color: "var(--nozle-primary, var(--primary))",
|
|
2179
|
-
fontSize: "0.875rem",
|
|
2180
|
-
textDecoration: "underline"
|
|
2181
|
-
},
|
|
2182
|
-
children: [
|
|
2183
|
-
"View all (",
|
|
2184
|
-
transactions.length,
|
|
2185
|
-
")"
|
|
2186
|
-
]
|
|
2187
|
-
}
|
|
2188
|
-
) }),
|
|
2189
|
-
displayed.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2190
|
-
"p",
|
|
2191
|
-
{
|
|
2192
|
-
style: {
|
|
2193
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))",
|
|
2194
|
-
fontSize: "0.875rem",
|
|
2195
|
-
textAlign: "center",
|
|
2196
|
-
padding: "1rem"
|
|
2197
|
-
},
|
|
2198
|
-
children: "No credit transactions yet."
|
|
2199
|
-
}
|
|
2200
|
-
)
|
|
2201
|
-
] });
|
|
2202
|
-
}
|
|
2203
|
-
var TIER_STYLES = {
|
|
2204
|
-
free: {
|
|
2205
|
-
background: "var(--nozle-muted, var(--muted, #f1f5f9))",
|
|
2206
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground, #64748b))",
|
|
2207
|
-
border: "1px solid var(--nozle-border, var(--border, #e2e8f0))"
|
|
2208
|
-
},
|
|
2209
|
-
starter: {
|
|
2210
|
-
background: "color-mix(in srgb, var(--nozle-primary, var(--primary, #3b82f6)) 10%, transparent)",
|
|
2211
|
-
color: "var(--nozle-primary, var(--primary, #3b82f6))",
|
|
2212
|
-
border: "1px solid color-mix(in srgb, var(--nozle-primary, var(--primary, #3b82f6)) 30%, transparent)"
|
|
2213
|
-
},
|
|
2214
|
-
pro: {
|
|
2215
|
-
background: "var(--nozle-primary, var(--primary, #3b82f6))",
|
|
2216
|
-
color: "var(--nozle-primary-foreground, var(--primary-foreground, #ffffff))",
|
|
2217
|
-
border: "1px solid transparent"
|
|
2218
|
-
},
|
|
2219
|
-
enterprise: {
|
|
2220
|
-
background: "var(--nozle-accent, var(--accent, #8b5cf6))",
|
|
2221
|
-
color: "var(--nozle-accent-foreground, var(--accent-foreground, #ffffff))",
|
|
2222
|
-
border: "1px solid transparent"
|
|
1463
|
+
enterprise: {
|
|
1464
|
+
background: "var(--nozle-accent, var(--accent, #8b5cf6))",
|
|
1465
|
+
color: "var(--nozle-accent-foreground, var(--accent-foreground, #ffffff))",
|
|
1466
|
+
border: "1px solid transparent"
|
|
2223
1467
|
}
|
|
2224
1468
|
};
|
|
2225
1469
|
function PlanBadge({ plan, tier = "free" }) {
|
|
@@ -2242,429 +1486,6 @@ function PlanBadge({ plan, tier = "free" }) {
|
|
|
2242
1486
|
}
|
|
2243
1487
|
);
|
|
2244
1488
|
}
|
|
2245
|
-
function formatDate2(iso) {
|
|
2246
|
-
if (!iso) return "\u2014";
|
|
2247
|
-
try {
|
|
2248
|
-
return new Date(iso).toLocaleDateString("en-US", {
|
|
2249
|
-
year: "numeric",
|
|
2250
|
-
month: "long",
|
|
2251
|
-
day: "numeric"
|
|
2252
|
-
});
|
|
2253
|
-
} catch {
|
|
2254
|
-
return iso;
|
|
2255
|
-
}
|
|
2256
|
-
}
|
|
2257
|
-
function CurrentPlan({
|
|
2258
|
-
customerId,
|
|
2259
|
-
onChangePlan,
|
|
2260
|
-
changePlanHref = "/plans"
|
|
2261
|
-
}) {
|
|
2262
|
-
const { client } = useBillingContext();
|
|
2263
|
-
const [plan, setPlan] = React.useState(null);
|
|
2264
|
-
const [loading, setLoading] = React.useState(true);
|
|
2265
|
-
const [error, setError] = React.useState(null);
|
|
2266
|
-
React.useEffect(() => {
|
|
2267
|
-
if (!client) {
|
|
2268
|
-
setError("No client in context");
|
|
2269
|
-
setLoading(false);
|
|
2270
|
-
return;
|
|
2271
|
-
}
|
|
2272
|
-
let cancelled = false;
|
|
2273
|
-
async function fetchPlan() {
|
|
2274
|
-
try {
|
|
2275
|
-
const clientInternal = client;
|
|
2276
|
-
const apiKey = clientInternal.apiKey ?? "";
|
|
2277
|
-
const baseUrl = clientInternal.baseUrl ?? "https://api.nozle.app";
|
|
2278
|
-
const timeout = clientInternal.timeout ?? 5e3;
|
|
2279
|
-
const controller = new AbortController();
|
|
2280
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
2281
|
-
const response = await fetch(
|
|
2282
|
-
`${baseUrl}/api/v1/subscriptions/current?customer_id=${customerId}`,
|
|
2283
|
-
{
|
|
2284
|
-
headers: { Authorization: `Bearer ${apiKey}` },
|
|
2285
|
-
signal: controller.signal
|
|
2286
|
-
}
|
|
2287
|
-
);
|
|
2288
|
-
clearTimeout(timeoutId);
|
|
2289
|
-
if (!response.ok) {
|
|
2290
|
-
throw new Error(`HTTP ${response.status}`);
|
|
2291
|
-
}
|
|
2292
|
-
const data = await response.json();
|
|
2293
|
-
if (!cancelled) {
|
|
2294
|
-
setPlan(data);
|
|
2295
|
-
setLoading(false);
|
|
2296
|
-
}
|
|
2297
|
-
} catch (err) {
|
|
2298
|
-
if (!cancelled) {
|
|
2299
|
-
setError(err instanceof Error ? err.message : "Unknown error");
|
|
2300
|
-
setLoading(false);
|
|
2301
|
-
}
|
|
2302
|
-
}
|
|
2303
|
-
}
|
|
2304
|
-
void fetchPlan();
|
|
2305
|
-
return () => {
|
|
2306
|
-
cancelled = true;
|
|
2307
|
-
};
|
|
2308
|
-
}, [client, customerId]);
|
|
2309
|
-
if (loading) {
|
|
2310
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "animate-pulse space-y-2", children: [
|
|
2311
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2312
|
-
"div",
|
|
2313
|
-
{
|
|
2314
|
-
className: "h-6 w-32 rounded",
|
|
2315
|
-
style: { background: "var(--nozle-border, var(--border))" }
|
|
2316
|
-
}
|
|
2317
|
-
),
|
|
2318
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2319
|
-
"div",
|
|
2320
|
-
{
|
|
2321
|
-
className: "h-4 w-48 rounded",
|
|
2322
|
-
style: { background: "var(--nozle-border, var(--border))" }
|
|
2323
|
-
}
|
|
2324
|
-
)
|
|
2325
|
-
] });
|
|
2326
|
-
}
|
|
2327
|
-
if (error || !plan) {
|
|
2328
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2329
|
-
"span",
|
|
2330
|
-
{
|
|
2331
|
-
style: { color: "var(--nozle-destructive, var(--destructive))" },
|
|
2332
|
-
className: "text-sm",
|
|
2333
|
-
children: "Failed to load current plan"
|
|
2334
|
-
}
|
|
2335
|
-
);
|
|
2336
|
-
}
|
|
2337
|
-
const handleChangePlan = () => {
|
|
2338
|
-
if (onChangePlan) {
|
|
2339
|
-
onChangePlan();
|
|
2340
|
-
} else {
|
|
2341
|
-
window.location.href = changePlanHref;
|
|
2342
|
-
}
|
|
2343
|
-
};
|
|
2344
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
|
|
2345
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
2346
|
-
/* @__PURE__ */ jsxRuntime.jsx(PlanBadge, { plan: plan.planName, tier: plan.tier }),
|
|
2347
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2348
|
-
"span",
|
|
2349
|
-
{
|
|
2350
|
-
style: {
|
|
2351
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))",
|
|
2352
|
-
fontSize: "0.875rem"
|
|
2353
|
-
},
|
|
2354
|
-
children: plan.interval
|
|
2355
|
-
}
|
|
2356
|
-
),
|
|
2357
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2358
|
-
"span",
|
|
2359
|
-
{
|
|
2360
|
-
style: {
|
|
2361
|
-
color: plan.status === "active" ? "var(--nozle-success, #16a34a)" : "var(--nozle-warning, var(--warning))",
|
|
2362
|
-
fontSize: "0.875rem",
|
|
2363
|
-
fontWeight: 500
|
|
2364
|
-
},
|
|
2365
|
-
children: plan.status
|
|
2366
|
-
}
|
|
2367
|
-
)
|
|
2368
|
-
] }),
|
|
2369
|
-
plan.nextBillingDate && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2370
|
-
"p",
|
|
2371
|
-
{
|
|
2372
|
-
style: {
|
|
2373
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))",
|
|
2374
|
-
fontSize: "0.875rem"
|
|
2375
|
-
},
|
|
2376
|
-
children: [
|
|
2377
|
-
"Next billing: ",
|
|
2378
|
-
formatDate2(plan.nextBillingDate)
|
|
2379
|
-
]
|
|
2380
|
-
}
|
|
2381
|
-
),
|
|
2382
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2383
|
-
"button",
|
|
2384
|
-
{
|
|
2385
|
-
onClick: handleChangePlan,
|
|
2386
|
-
style: {
|
|
2387
|
-
background: "transparent",
|
|
2388
|
-
border: "1px solid var(--nozle-primary, var(--primary))",
|
|
2389
|
-
color: "var(--nozle-primary, var(--primary))",
|
|
2390
|
-
padding: "0.375rem 0.875rem",
|
|
2391
|
-
borderRadius: "0.375rem",
|
|
2392
|
-
fontSize: "0.875rem",
|
|
2393
|
-
fontWeight: 500,
|
|
2394
|
-
cursor: "pointer"
|
|
2395
|
-
},
|
|
2396
|
-
children: "Change Plan"
|
|
2397
|
-
}
|
|
2398
|
-
)
|
|
2399
|
-
] });
|
|
2400
|
-
}
|
|
2401
|
-
var STATUS_STYLES = {
|
|
2402
|
-
paid: {
|
|
2403
|
-
background: "var(--nozle-success-bg, #dcfce7)",
|
|
2404
|
-
color: "var(--nozle-success, #16a34a)"
|
|
2405
|
-
},
|
|
2406
|
-
open: {
|
|
2407
|
-
background: "var(--nozle-info-bg, #dbeafe)",
|
|
2408
|
-
color: "var(--nozle-info, #2563eb)"
|
|
2409
|
-
},
|
|
2410
|
-
draft: {
|
|
2411
|
-
background: "var(--nozle-muted, #f1f5f9)",
|
|
2412
|
-
color: "var(--nozle-muted-foreground, #64748b)"
|
|
2413
|
-
},
|
|
2414
|
-
void: {
|
|
2415
|
-
background: "var(--nozle-muted, #f1f5f9)",
|
|
2416
|
-
color: "var(--nozle-muted-foreground, #64748b)"
|
|
2417
|
-
},
|
|
2418
|
-
uncollectible: {
|
|
2419
|
-
background: "var(--nozle-destructive-bg, #fee2e2)",
|
|
2420
|
-
color: "var(--nozle-destructive, #dc2626)"
|
|
2421
|
-
}
|
|
2422
|
-
};
|
|
2423
|
-
function formatDate3(iso) {
|
|
2424
|
-
try {
|
|
2425
|
-
return new Date(iso).toLocaleDateString("en-US", {
|
|
2426
|
-
year: "numeric",
|
|
2427
|
-
month: "short",
|
|
2428
|
-
day: "numeric"
|
|
2429
|
-
});
|
|
2430
|
-
} catch {
|
|
2431
|
-
return iso;
|
|
2432
|
-
}
|
|
2433
|
-
}
|
|
2434
|
-
function InvoiceList({ customerId }) {
|
|
2435
|
-
const { client } = useBillingContext();
|
|
2436
|
-
const [invoices, setInvoices] = React.useState([]);
|
|
2437
|
-
const [loading, setLoading] = React.useState(true);
|
|
2438
|
-
const [error, setError] = React.useState(null);
|
|
2439
|
-
React.useEffect(() => {
|
|
2440
|
-
if (!client) {
|
|
2441
|
-
setError("No client in context");
|
|
2442
|
-
setLoading(false);
|
|
2443
|
-
return;
|
|
2444
|
-
}
|
|
2445
|
-
let cancelled = false;
|
|
2446
|
-
async function fetchInvoices() {
|
|
2447
|
-
try {
|
|
2448
|
-
const clientInternal = client;
|
|
2449
|
-
const apiKey = clientInternal.apiKey ?? "";
|
|
2450
|
-
const baseUrl = clientInternal.baseUrl ?? "https://api.nozle.app";
|
|
2451
|
-
const timeout = clientInternal.timeout ?? 5e3;
|
|
2452
|
-
const controller = new AbortController();
|
|
2453
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
2454
|
-
const response = await fetch(
|
|
2455
|
-
`${baseUrl}/api/v1/invoices?customer_id=${customerId}`,
|
|
2456
|
-
{
|
|
2457
|
-
headers: { Authorization: `Bearer ${apiKey}` },
|
|
2458
|
-
signal: controller.signal
|
|
2459
|
-
}
|
|
2460
|
-
);
|
|
2461
|
-
clearTimeout(timeoutId);
|
|
2462
|
-
if (!response.ok) {
|
|
2463
|
-
throw new Error(`HTTP ${response.status}`);
|
|
2464
|
-
}
|
|
2465
|
-
const data = await response.json();
|
|
2466
|
-
if (!cancelled) {
|
|
2467
|
-
setInvoices(data.invoices ?? data.data ?? []);
|
|
2468
|
-
setLoading(false);
|
|
2469
|
-
}
|
|
2470
|
-
} catch (err) {
|
|
2471
|
-
if (!cancelled) {
|
|
2472
|
-
setError(err instanceof Error ? err.message : "Unknown error");
|
|
2473
|
-
setLoading(false);
|
|
2474
|
-
}
|
|
2475
|
-
}
|
|
2476
|
-
}
|
|
2477
|
-
void fetchInvoices();
|
|
2478
|
-
return () => {
|
|
2479
|
-
cancelled = true;
|
|
2480
|
-
};
|
|
2481
|
-
}, [client, customerId]);
|
|
2482
|
-
if (loading) {
|
|
2483
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-pulse space-y-2", children: [1, 2, 3].map((i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2484
|
-
"div",
|
|
2485
|
-
{
|
|
2486
|
-
className: "h-8 rounded",
|
|
2487
|
-
style: { background: "var(--nozle-border, var(--border))" }
|
|
2488
|
-
},
|
|
2489
|
-
i
|
|
2490
|
-
)) });
|
|
2491
|
-
}
|
|
2492
|
-
if (error) {
|
|
2493
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2494
|
-
"span",
|
|
2495
|
-
{
|
|
2496
|
-
style: { color: "var(--nozle-destructive, var(--destructive))" },
|
|
2497
|
-
className: "text-sm",
|
|
2498
|
-
children: "Failed to load invoices"
|
|
2499
|
-
}
|
|
2500
|
-
);
|
|
2501
|
-
}
|
|
2502
|
-
if (invoices.length === 0) {
|
|
2503
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2504
|
-
"p",
|
|
2505
|
-
{
|
|
2506
|
-
style: {
|
|
2507
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))",
|
|
2508
|
-
fontSize: "0.875rem",
|
|
2509
|
-
textAlign: "center",
|
|
2510
|
-
padding: "1.5rem"
|
|
2511
|
-
},
|
|
2512
|
-
children: "No invoices yet."
|
|
2513
|
-
}
|
|
2514
|
-
);
|
|
2515
|
-
}
|
|
2516
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("table", { style: { width: "100%", borderCollapse: "collapse" }, children: [
|
|
2517
|
-
/* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2518
|
-
"tr",
|
|
2519
|
-
{
|
|
2520
|
-
style: {
|
|
2521
|
-
borderBottom: "1px solid var(--nozle-border, var(--border))",
|
|
2522
|
-
fontSize: "0.75rem",
|
|
2523
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))"
|
|
2524
|
-
},
|
|
2525
|
-
children: [
|
|
2526
|
-
/* @__PURE__ */ jsxRuntime.jsx("th", { style: { textAlign: "left", padding: "0.5rem" }, children: "#" }),
|
|
2527
|
-
/* @__PURE__ */ jsxRuntime.jsx("th", { style: { textAlign: "left", padding: "0.5rem" }, children: "Date" }),
|
|
2528
|
-
/* @__PURE__ */ jsxRuntime.jsx("th", { style: { textAlign: "right", padding: "0.5rem" }, children: "Amount" }),
|
|
2529
|
-
/* @__PURE__ */ jsxRuntime.jsx("th", { style: { textAlign: "center", padding: "0.5rem" }, children: "Status" }),
|
|
2530
|
-
/* @__PURE__ */ jsxRuntime.jsx("th", { style: { textAlign: "center", padding: "0.5rem" }, children: "PDF" })
|
|
2531
|
-
]
|
|
2532
|
-
}
|
|
2533
|
-
) }),
|
|
2534
|
-
/* @__PURE__ */ jsxRuntime.jsx("tbody", { children: invoices.map((inv) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2535
|
-
"tr",
|
|
2536
|
-
{
|
|
2537
|
-
style: {
|
|
2538
|
-
borderBottom: "1px solid var(--nozle-border, var(--border))",
|
|
2539
|
-
fontSize: "0.875rem"
|
|
2540
|
-
},
|
|
2541
|
-
children: [
|
|
2542
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2543
|
-
"td",
|
|
2544
|
-
{
|
|
2545
|
-
style: {
|
|
2546
|
-
padding: "0.5rem",
|
|
2547
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))"
|
|
2548
|
-
},
|
|
2549
|
-
children: inv.number
|
|
2550
|
-
}
|
|
2551
|
-
),
|
|
2552
|
-
/* @__PURE__ */ jsxRuntime.jsx("td", { style: { padding: "0.5rem" }, children: formatDate3(inv.date) }),
|
|
2553
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2554
|
-
"td",
|
|
2555
|
-
{
|
|
2556
|
-
style: {
|
|
2557
|
-
textAlign: "right",
|
|
2558
|
-
padding: "0.5rem",
|
|
2559
|
-
fontVariantNumeric: "tabular-nums"
|
|
2560
|
-
},
|
|
2561
|
-
children: new Intl.NumberFormat("en-US", {
|
|
2562
|
-
style: "currency",
|
|
2563
|
-
currency: inv.currency ?? "USD"
|
|
2564
|
-
}).format(inv.amount / 100)
|
|
2565
|
-
}
|
|
2566
|
-
),
|
|
2567
|
-
/* @__PURE__ */ jsxRuntime.jsx("td", { style: { textAlign: "center", padding: "0.5rem" }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2568
|
-
"span",
|
|
2569
|
-
{
|
|
2570
|
-
style: {
|
|
2571
|
-
...STATUS_STYLES[inv.status] ?? STATUS_STYLES.draft,
|
|
2572
|
-
padding: "0.125rem 0.5rem",
|
|
2573
|
-
borderRadius: "9999px",
|
|
2574
|
-
fontSize: "0.75rem",
|
|
2575
|
-
fontWeight: 500
|
|
2576
|
-
},
|
|
2577
|
-
children: inv.status
|
|
2578
|
-
}
|
|
2579
|
-
) }),
|
|
2580
|
-
/* @__PURE__ */ jsxRuntime.jsx("td", { style: { textAlign: "center", padding: "0.5rem" }, children: inv.pdf_url ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2581
|
-
"a",
|
|
2582
|
-
{
|
|
2583
|
-
href: inv.pdf_url,
|
|
2584
|
-
target: "_blank",
|
|
2585
|
-
rel: "noopener noreferrer",
|
|
2586
|
-
"aria-label": `Download invoice ${inv.number}`,
|
|
2587
|
-
style: {
|
|
2588
|
-
color: "var(--nozle-primary, var(--primary))",
|
|
2589
|
-
textDecoration: "underline",
|
|
2590
|
-
fontSize: "0.75rem"
|
|
2591
|
-
},
|
|
2592
|
-
children: "Download"
|
|
2593
|
-
}
|
|
2594
|
-
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
2595
|
-
"span",
|
|
2596
|
-
{
|
|
2597
|
-
style: {
|
|
2598
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))",
|
|
2599
|
-
fontSize: "0.75rem"
|
|
2600
|
-
},
|
|
2601
|
-
children: "\u2014"
|
|
2602
|
-
}
|
|
2603
|
-
) })
|
|
2604
|
-
]
|
|
2605
|
-
},
|
|
2606
|
-
inv.id
|
|
2607
|
-
)) })
|
|
2608
|
-
] });
|
|
2609
|
-
}
|
|
2610
|
-
function Section({
|
|
2611
|
-
title,
|
|
2612
|
-
children
|
|
2613
|
-
}) {
|
|
2614
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2615
|
-
"div",
|
|
2616
|
-
{
|
|
2617
|
-
style: {
|
|
2618
|
-
padding: "1.25rem",
|
|
2619
|
-
border: "1px solid var(--nozle-border, var(--border))",
|
|
2620
|
-
borderRadius: "0.75rem",
|
|
2621
|
-
background: "var(--nozle-card, var(--card, #ffffff))"
|
|
2622
|
-
},
|
|
2623
|
-
children: [
|
|
2624
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2625
|
-
"h3",
|
|
2626
|
-
{
|
|
2627
|
-
style: {
|
|
2628
|
-
fontSize: "0.875rem",
|
|
2629
|
-
fontWeight: 600,
|
|
2630
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))",
|
|
2631
|
-
textTransform: "uppercase",
|
|
2632
|
-
letterSpacing: "0.05em",
|
|
2633
|
-
marginBottom: "1rem"
|
|
2634
|
-
},
|
|
2635
|
-
children: title
|
|
2636
|
-
}
|
|
2637
|
-
),
|
|
2638
|
-
children
|
|
2639
|
-
]
|
|
2640
|
-
}
|
|
2641
|
-
);
|
|
2642
|
-
}
|
|
2643
|
-
function BillingPortal({
|
|
2644
|
-
customerId,
|
|
2645
|
-
usageFeatures,
|
|
2646
|
-
usageLoading = false,
|
|
2647
|
-
onChangePlan
|
|
2648
|
-
}) {
|
|
2649
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2650
|
-
"div",
|
|
2651
|
-
{
|
|
2652
|
-
style: {
|
|
2653
|
-
display: "flex",
|
|
2654
|
-
flexDirection: "column",
|
|
2655
|
-
gap: "1.25rem",
|
|
2656
|
-
fontFamily: "var(--nozle-font-sans, var(--font-sans, system-ui, sans-serif))",
|
|
2657
|
-
color: "var(--nozle-foreground, var(--foreground))"
|
|
2658
|
-
},
|
|
2659
|
-
children: [
|
|
2660
|
-
/* @__PURE__ */ jsxRuntime.jsx(Section, { title: "Current Plan", children: /* @__PURE__ */ jsxRuntime.jsx(CurrentPlan, { customerId, onChangePlan }) }),
|
|
2661
|
-
/* @__PURE__ */ jsxRuntime.jsx(Section, { title: "Usage", children: /* @__PURE__ */ jsxRuntime.jsx(UsageDashboard, { features: usageFeatures, loading: usageLoading }) }),
|
|
2662
|
-
/* @__PURE__ */ jsxRuntime.jsx(Section, { title: "Invoices", children: /* @__PURE__ */ jsxRuntime.jsx(InvoiceList, { customerId }) }),
|
|
2663
|
-
/* @__PURE__ */ jsxRuntime.jsx(Section, { title: "Credits", children: /* @__PURE__ */ jsxRuntime.jsx(CreditBalance, { customerId }) })
|
|
2664
|
-
]
|
|
2665
|
-
}
|
|
2666
|
-
);
|
|
2667
|
-
}
|
|
2668
1489
|
var BRAND_LABELS = {
|
|
2669
1490
|
visa: "Visa",
|
|
2670
1491
|
mastercard: "Mastercard",
|
|
@@ -2768,341 +1589,12 @@ function PaymentMethodDisplay({
|
|
|
2768
1589
|
)
|
|
2769
1590
|
] });
|
|
2770
1591
|
}
|
|
2771
|
-
var CANCEL_REASONS = [
|
|
2772
|
-
"Too expensive",
|
|
2773
|
-
"Missing features",
|
|
2774
|
-
"Switching to competitor",
|
|
2775
|
-
"Not using it enough",
|
|
2776
|
-
"Other"
|
|
2777
|
-
];
|
|
2778
|
-
function CancelSubscriptionButton({
|
|
2779
|
-
subscriptionId,
|
|
2780
|
-
onCancelled
|
|
2781
|
-
}) {
|
|
2782
|
-
const [step, setStep] = React.useState("idle");
|
|
2783
|
-
const [selectedReason, setSelectedReason] = React.useState("");
|
|
2784
|
-
const [otherText, setOtherText] = React.useState("");
|
|
2785
|
-
const [loading, setLoading] = React.useState(false);
|
|
2786
|
-
const handleDismiss = () => {
|
|
2787
|
-
setStep("idle");
|
|
2788
|
-
setSelectedReason("");
|
|
2789
|
-
setOtherText("");
|
|
2790
|
-
};
|
|
2791
|
-
const handleConfirmCancel = () => {
|
|
2792
|
-
setStep("reason");
|
|
2793
|
-
};
|
|
2794
|
-
const handleSubmitCancellation = async () => {
|
|
2795
|
-
const reason = selectedReason === "Other" ? otherText : selectedReason;
|
|
2796
|
-
setLoading(true);
|
|
2797
|
-
await fetch(`/api/v1/subscriptions/${subscriptionId}`, {
|
|
2798
|
-
method: "DELETE",
|
|
2799
|
-
headers: { "Content-Type": "application/json" },
|
|
2800
|
-
body: JSON.stringify({ reason })
|
|
2801
|
-
});
|
|
2802
|
-
setLoading(false);
|
|
2803
|
-
handleDismiss();
|
|
2804
|
-
onCancelled?.();
|
|
2805
|
-
};
|
|
2806
|
-
const isSubmitDisabled = loading || !selectedReason || selectedReason === "Other" && !otherText.trim();
|
|
2807
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2808
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2809
|
-
"button",
|
|
2810
|
-
{
|
|
2811
|
-
onClick: () => setStep("confirm"),
|
|
2812
|
-
style: {
|
|
2813
|
-
background: "transparent",
|
|
2814
|
-
border: "1px solid var(--nozle-destructive, var(--destructive, #dc2626))",
|
|
2815
|
-
color: "var(--nozle-destructive, var(--destructive, #dc2626))",
|
|
2816
|
-
padding: "0.375rem 0.875rem",
|
|
2817
|
-
borderRadius: "0.375rem",
|
|
2818
|
-
fontSize: "0.875rem",
|
|
2819
|
-
fontWeight: 500,
|
|
2820
|
-
cursor: "pointer"
|
|
2821
|
-
},
|
|
2822
|
-
children: "Cancel Subscription"
|
|
2823
|
-
}
|
|
2824
|
-
),
|
|
2825
|
-
step === "confirm" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2826
|
-
"div",
|
|
2827
|
-
{
|
|
2828
|
-
role: "dialog",
|
|
2829
|
-
"aria-modal": "true",
|
|
2830
|
-
"aria-label": "Cancel subscription confirmation",
|
|
2831
|
-
style: {
|
|
2832
|
-
position: "fixed",
|
|
2833
|
-
inset: 0,
|
|
2834
|
-
display: "flex",
|
|
2835
|
-
alignItems: "center",
|
|
2836
|
-
justifyContent: "center",
|
|
2837
|
-
background: "rgba(0,0,0,0.4)",
|
|
2838
|
-
zIndex: 50
|
|
2839
|
-
},
|
|
2840
|
-
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2841
|
-
"div",
|
|
2842
|
-
{
|
|
2843
|
-
style: {
|
|
2844
|
-
background: "var(--nozle-background, var(--background, #fff))",
|
|
2845
|
-
border: "1px solid var(--nozle-border, var(--border))",
|
|
2846
|
-
borderRadius: "0.75rem",
|
|
2847
|
-
padding: "1.5rem",
|
|
2848
|
-
maxWidth: "28rem",
|
|
2849
|
-
width: "90%",
|
|
2850
|
-
boxShadow: "0 20px 60px rgba(0,0,0,0.15)"
|
|
2851
|
-
},
|
|
2852
|
-
children: [
|
|
2853
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2854
|
-
"h2",
|
|
2855
|
-
{
|
|
2856
|
-
style: {
|
|
2857
|
-
fontSize: "1.125rem",
|
|
2858
|
-
fontWeight: 600,
|
|
2859
|
-
marginBottom: "0.75rem"
|
|
2860
|
-
},
|
|
2861
|
-
children: "Cancel Subscription?"
|
|
2862
|
-
}
|
|
2863
|
-
),
|
|
2864
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2865
|
-
"p",
|
|
2866
|
-
{
|
|
2867
|
-
style: {
|
|
2868
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))",
|
|
2869
|
-
fontSize: "0.875rem",
|
|
2870
|
-
marginBottom: "1.5rem"
|
|
2871
|
-
},
|
|
2872
|
-
children: "Your access will end at the end of the billing period."
|
|
2873
|
-
}
|
|
2874
|
-
),
|
|
2875
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2876
|
-
"div",
|
|
2877
|
-
{
|
|
2878
|
-
style: {
|
|
2879
|
-
display: "flex",
|
|
2880
|
-
gap: "0.75rem",
|
|
2881
|
-
justifyContent: "flex-end"
|
|
2882
|
-
},
|
|
2883
|
-
children: [
|
|
2884
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2885
|
-
"button",
|
|
2886
|
-
{
|
|
2887
|
-
onClick: handleDismiss,
|
|
2888
|
-
style: {
|
|
2889
|
-
background: "transparent",
|
|
2890
|
-
border: "1px solid var(--nozle-border, var(--border))",
|
|
2891
|
-
color: "var(--nozle-foreground, var(--foreground))",
|
|
2892
|
-
padding: "0.375rem 0.875rem",
|
|
2893
|
-
borderRadius: "0.375rem",
|
|
2894
|
-
fontSize: "0.875rem",
|
|
2895
|
-
cursor: "pointer"
|
|
2896
|
-
},
|
|
2897
|
-
children: "Keep Subscription"
|
|
2898
|
-
}
|
|
2899
|
-
),
|
|
2900
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2901
|
-
"button",
|
|
2902
|
-
{
|
|
2903
|
-
onClick: handleConfirmCancel,
|
|
2904
|
-
style: {
|
|
2905
|
-
background: "var(--nozle-destructive, var(--destructive, #dc2626))",
|
|
2906
|
-
border: "1px solid transparent",
|
|
2907
|
-
color: "#fff",
|
|
2908
|
-
padding: "0.375rem 0.875rem",
|
|
2909
|
-
borderRadius: "0.375rem",
|
|
2910
|
-
fontSize: "0.875rem",
|
|
2911
|
-
fontWeight: 500,
|
|
2912
|
-
cursor: "pointer"
|
|
2913
|
-
},
|
|
2914
|
-
children: "Confirm Cancel"
|
|
2915
|
-
}
|
|
2916
|
-
)
|
|
2917
|
-
]
|
|
2918
|
-
}
|
|
2919
|
-
)
|
|
2920
|
-
]
|
|
2921
|
-
}
|
|
2922
|
-
)
|
|
2923
|
-
}
|
|
2924
|
-
),
|
|
2925
|
-
step === "reason" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2926
|
-
"div",
|
|
2927
|
-
{
|
|
2928
|
-
role: "dialog",
|
|
2929
|
-
"aria-modal": "true",
|
|
2930
|
-
"aria-label": "Cancellation reason survey",
|
|
2931
|
-
style: {
|
|
2932
|
-
position: "fixed",
|
|
2933
|
-
inset: 0,
|
|
2934
|
-
display: "flex",
|
|
2935
|
-
alignItems: "center",
|
|
2936
|
-
justifyContent: "center",
|
|
2937
|
-
background: "rgba(0,0,0,0.4)",
|
|
2938
|
-
zIndex: 50
|
|
2939
|
-
},
|
|
2940
|
-
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2941
|
-
"div",
|
|
2942
|
-
{
|
|
2943
|
-
style: {
|
|
2944
|
-
background: "var(--nozle-background, var(--background, #fff))",
|
|
2945
|
-
border: "1px solid var(--nozle-border, var(--border))",
|
|
2946
|
-
borderRadius: "0.75rem",
|
|
2947
|
-
padding: "1.5rem",
|
|
2948
|
-
maxWidth: "28rem",
|
|
2949
|
-
width: "90%",
|
|
2950
|
-
boxShadow: "0 20px 60px rgba(0,0,0,0.15)"
|
|
2951
|
-
},
|
|
2952
|
-
children: [
|
|
2953
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2954
|
-
"h2",
|
|
2955
|
-
{
|
|
2956
|
-
style: {
|
|
2957
|
-
fontSize: "1.125rem",
|
|
2958
|
-
fontWeight: 600,
|
|
2959
|
-
marginBottom: "0.5rem"
|
|
2960
|
-
},
|
|
2961
|
-
children: "Why are you cancelling?"
|
|
2962
|
-
}
|
|
2963
|
-
),
|
|
2964
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2965
|
-
"p",
|
|
2966
|
-
{
|
|
2967
|
-
style: {
|
|
2968
|
-
color: "var(--nozle-muted-foreground, var(--muted-foreground))",
|
|
2969
|
-
fontSize: "0.875rem",
|
|
2970
|
-
marginBottom: "1rem"
|
|
2971
|
-
},
|
|
2972
|
-
children: "Your feedback helps us improve."
|
|
2973
|
-
}
|
|
2974
|
-
),
|
|
2975
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2976
|
-
"fieldset",
|
|
2977
|
-
{
|
|
2978
|
-
style: {
|
|
2979
|
-
border: "none",
|
|
2980
|
-
padding: 0,
|
|
2981
|
-
margin: 0,
|
|
2982
|
-
marginBottom: "1rem"
|
|
2983
|
-
},
|
|
2984
|
-
children: [
|
|
2985
|
-
/* @__PURE__ */ jsxRuntime.jsx("legend", { className: "sr-only", children: "Cancellation reason" }),
|
|
2986
|
-
CANCEL_REASONS.map((r) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2987
|
-
"label",
|
|
2988
|
-
{
|
|
2989
|
-
style: {
|
|
2990
|
-
display: "flex",
|
|
2991
|
-
alignItems: "center",
|
|
2992
|
-
gap: "0.5rem",
|
|
2993
|
-
padding: "0.375rem 0",
|
|
2994
|
-
fontSize: "0.875rem",
|
|
2995
|
-
cursor: "pointer"
|
|
2996
|
-
},
|
|
2997
|
-
children: [
|
|
2998
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2999
|
-
"input",
|
|
3000
|
-
{
|
|
3001
|
-
type: "radio",
|
|
3002
|
-
name: "cancel-reason",
|
|
3003
|
-
value: r,
|
|
3004
|
-
checked: selectedReason === r,
|
|
3005
|
-
onChange: () => setSelectedReason(r),
|
|
3006
|
-
"aria-label": r
|
|
3007
|
-
}
|
|
3008
|
-
),
|
|
3009
|
-
r
|
|
3010
|
-
]
|
|
3011
|
-
},
|
|
3012
|
-
r
|
|
3013
|
-
))
|
|
3014
|
-
]
|
|
3015
|
-
}
|
|
3016
|
-
),
|
|
3017
|
-
selectedReason === "Other" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3018
|
-
"input",
|
|
3019
|
-
{
|
|
3020
|
-
type: "text",
|
|
3021
|
-
placeholder: "Please describe your reason",
|
|
3022
|
-
value: otherText,
|
|
3023
|
-
onChange: (e) => setOtherText(e.target.value),
|
|
3024
|
-
"aria-label": "Other reason",
|
|
3025
|
-
style: {
|
|
3026
|
-
width: "100%",
|
|
3027
|
-
border: "1px solid var(--nozle-border, var(--border))",
|
|
3028
|
-
borderRadius: "0.375rem",
|
|
3029
|
-
padding: "0.5rem 0.75rem",
|
|
3030
|
-
fontSize: "0.875rem",
|
|
3031
|
-
marginBottom: "1rem",
|
|
3032
|
-
background: "var(--nozle-background, var(--background, #fff))",
|
|
3033
|
-
color: "var(--nozle-foreground, var(--foreground))",
|
|
3034
|
-
boxSizing: "border-box"
|
|
3035
|
-
}
|
|
3036
|
-
}
|
|
3037
|
-
),
|
|
3038
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3039
|
-
"div",
|
|
3040
|
-
{
|
|
3041
|
-
style: {
|
|
3042
|
-
display: "flex",
|
|
3043
|
-
gap: "0.75rem",
|
|
3044
|
-
justifyContent: "flex-end"
|
|
3045
|
-
},
|
|
3046
|
-
children: [
|
|
3047
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3048
|
-
"button",
|
|
3049
|
-
{
|
|
3050
|
-
onClick: handleDismiss,
|
|
3051
|
-
style: {
|
|
3052
|
-
background: "transparent",
|
|
3053
|
-
border: "1px solid var(--nozle-border, var(--border))",
|
|
3054
|
-
color: "var(--nozle-foreground, var(--foreground))",
|
|
3055
|
-
padding: "0.375rem 0.875rem",
|
|
3056
|
-
borderRadius: "0.375rem",
|
|
3057
|
-
fontSize: "0.875rem",
|
|
3058
|
-
cursor: "pointer"
|
|
3059
|
-
},
|
|
3060
|
-
children: "Keep Subscription"
|
|
3061
|
-
}
|
|
3062
|
-
),
|
|
3063
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3064
|
-
"button",
|
|
3065
|
-
{
|
|
3066
|
-
onClick: handleSubmitCancellation,
|
|
3067
|
-
disabled: isSubmitDisabled,
|
|
3068
|
-
style: {
|
|
3069
|
-
background: isSubmitDisabled ? "var(--nozle-muted, var(--muted, #f1f5f9))" : "var(--nozle-destructive, var(--destructive, #dc2626))",
|
|
3070
|
-
border: "1px solid transparent",
|
|
3071
|
-
color: isSubmitDisabled ? "var(--nozle-muted-foreground, var(--muted-foreground))" : "#fff",
|
|
3072
|
-
padding: "0.375rem 0.875rem",
|
|
3073
|
-
borderRadius: "0.375rem",
|
|
3074
|
-
fontSize: "0.875rem",
|
|
3075
|
-
fontWeight: 500,
|
|
3076
|
-
cursor: isSubmitDisabled ? "not-allowed" : "pointer"
|
|
3077
|
-
},
|
|
3078
|
-
children: loading ? "Cancelling..." : "Submit Cancellation"
|
|
3079
|
-
}
|
|
3080
|
-
)
|
|
3081
|
-
]
|
|
3082
|
-
}
|
|
3083
|
-
)
|
|
3084
|
-
]
|
|
3085
|
-
}
|
|
3086
|
-
)
|
|
3087
|
-
}
|
|
3088
|
-
)
|
|
3089
|
-
] });
|
|
3090
|
-
}
|
|
3091
1592
|
|
|
3092
1593
|
exports.BillingContext = BillingContext;
|
|
3093
|
-
exports.BillingPortal = BillingPortal;
|
|
3094
|
-
exports.BillingPortalProvider = BillingPortalProvider;
|
|
3095
1594
|
exports.BillingProvider = BillingProvider;
|
|
3096
|
-
exports.CANCEL_REASONS = CANCEL_REASONS;
|
|
3097
|
-
exports.CancelSubscriptionButton = CancelSubscriptionButton;
|
|
3098
1595
|
exports.Checkout = Checkout;
|
|
3099
1596
|
exports.CheckoutButton = CheckoutButton;
|
|
3100
|
-
exports.CreditBalance = CreditBalance;
|
|
3101
|
-
exports.CreditHistory = CreditHistory;
|
|
3102
|
-
exports.CreditTopUpButton = CreditTopUpButton;
|
|
3103
|
-
exports.CurrentPlan = CurrentPlan;
|
|
3104
1597
|
exports.FeatureGate = FeatureGate;
|
|
3105
|
-
exports.InvoiceList = InvoiceList;
|
|
3106
1598
|
exports.LockedOverlay = LockedOverlay;
|
|
3107
1599
|
exports.PaymentMethodDisplay = PaymentMethodDisplay;
|
|
3108
1600
|
exports.PlanBadge = PlanBadge;
|
|
@@ -3119,11 +1611,10 @@ exports.UsageGate = UsageGate;
|
|
|
3119
1611
|
exports.UsageMeter = UsageMeter;
|
|
3120
1612
|
exports.getUsageMeterColor = getUsageMeterColor;
|
|
3121
1613
|
exports.useBillingContext = useBillingContext;
|
|
3122
|
-
exports.
|
|
3123
|
-
exports.
|
|
3124
|
-
exports.useCheckout = useCheckout;
|
|
3125
|
-
exports.useCredits = useCredits;
|
|
1614
|
+
exports.useCheckout = useCheckout2;
|
|
1615
|
+
exports.useCheckoutSession = useCheckout;
|
|
3126
1616
|
exports.useNozleClient = useNozleClient;
|
|
3127
|
-
exports.
|
|
1617
|
+
exports.useOptionalBillingContext = useOptionalBillingContext;
|
|
1618
|
+
exports.usePlans = usePlans;
|
|
3128
1619
|
//# sourceMappingURL=index.cjs.map
|
|
3129
1620
|
//# sourceMappingURL=index.cjs.map
|