@nozle-js/react 0.5.3 → 0.7.1
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 +404 -1910
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +124 -295
- package/dist/index.d.ts +124 -295
- package/dist/index.js +404 -1900
- 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,9 @@ function CheckoutInner({
|
|
|
1015
878
|
onError,
|
|
1016
879
|
onReady,
|
|
1017
880
|
className,
|
|
1018
|
-
style
|
|
881
|
+
style,
|
|
882
|
+
returnUrl,
|
|
883
|
+
children
|
|
1019
884
|
}) {
|
|
1020
885
|
const stripe = reactStripeJs.useStripe();
|
|
1021
886
|
const elements = reactStripeJs.useElements();
|
|
@@ -1028,7 +893,7 @@ function CheckoutInner({
|
|
|
1028
893
|
setError(null);
|
|
1029
894
|
const result = await stripe.confirmPayment({
|
|
1030
895
|
elements,
|
|
1031
|
-
confirmParams: { return_url: window.location.href },
|
|
896
|
+
confirmParams: { return_url: returnUrl ?? window.location.href },
|
|
1032
897
|
redirect: "if_required"
|
|
1033
898
|
});
|
|
1034
899
|
setIsProcessing(false);
|
|
@@ -1065,7 +930,7 @@ function CheckoutInner({
|
|
|
1065
930
|
}
|
|
1066
931
|
),
|
|
1067
932
|
error !== null && /* @__PURE__ */ jsxRuntime.jsx("p", { style: { color: "var(--nozle-destructive, #dc2626)", margin: "0.5rem 0 0" }, children: error.message }),
|
|
1068
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
933
|
+
children ?? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1069
934
|
"button",
|
|
1070
935
|
{
|
|
1071
936
|
type: "button",
|
|
@@ -1093,18 +958,28 @@ function Checkout({
|
|
|
1093
958
|
clientSecret,
|
|
1094
959
|
publishableKey,
|
|
1095
960
|
stripeAccount,
|
|
961
|
+
returnUrl,
|
|
1096
962
|
submitLabel,
|
|
1097
963
|
onSuccess,
|
|
964
|
+
onComplete,
|
|
1098
965
|
onError,
|
|
1099
966
|
onReady,
|
|
1100
967
|
className,
|
|
1101
|
-
style
|
|
968
|
+
style,
|
|
969
|
+
children
|
|
1102
970
|
}) {
|
|
1103
971
|
const stripePromise = React.useMemo(
|
|
1104
972
|
() => stripeJs.loadStripe(publishableKey, stripeAccount ? { stripeAccount } : void 0),
|
|
1105
973
|
[publishableKey, stripeAccount]
|
|
1106
974
|
);
|
|
1107
975
|
const appearance = React.useMemo(() => buildStripeAppearance(), []);
|
|
976
|
+
const embeddedOptions = React.useMemo(
|
|
977
|
+
() => ({ clientSecret, onComplete }),
|
|
978
|
+
[clientSecret, onComplete]
|
|
979
|
+
);
|
|
980
|
+
if (clientSecret.startsWith("cs_")) {
|
|
981
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className, style, children: /* @__PURE__ */ jsxRuntime.jsx(reactStripeJs.EmbeddedCheckoutProvider, { stripe: stripePromise, options: embeddedOptions, children: /* @__PURE__ */ jsxRuntime.jsx(reactStripeJs.EmbeddedCheckout, {}) }) });
|
|
982
|
+
}
|
|
1108
983
|
const options = { clientSecret, appearance };
|
|
1109
984
|
return /* @__PURE__ */ jsxRuntime.jsx(reactStripeJs.Elements, { stripe: stripePromise, options, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1110
985
|
CheckoutInner,
|
|
@@ -1114,278 +989,151 @@ function Checkout({
|
|
|
1114
989
|
onError,
|
|
1115
990
|
onReady,
|
|
1116
991
|
className,
|
|
1117
|
-
style
|
|
992
|
+
style,
|
|
993
|
+
returnUrl,
|
|
994
|
+
children
|
|
1118
995
|
}
|
|
1119
996
|
) }, clientSecret);
|
|
1120
997
|
}
|
|
1121
998
|
function UpgradeModal({
|
|
1122
999
|
isOpen,
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1000
|
+
planCode,
|
|
1001
|
+
returnUrl,
|
|
1002
|
+
preview,
|
|
1003
|
+
onStripeClientSecret,
|
|
1004
|
+
onCheckoutStarted,
|
|
1005
|
+
onCompleted,
|
|
1006
|
+
onScheduled,
|
|
1127
1007
|
onConfirm,
|
|
1128
|
-
onCancel
|
|
1008
|
+
onCancel,
|
|
1009
|
+
onError
|
|
1129
1010
|
}) {
|
|
1130
|
-
const
|
|
1131
|
-
const [loading, setLoading] = React.useState(false);
|
|
1011
|
+
const { createCheckout } = useBillingContext();
|
|
1132
1012
|
const [error, setError] = React.useState(null);
|
|
1133
1013
|
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
1014
|
async function handleConfirm() {
|
|
1181
1015
|
setConfirming(true);
|
|
1016
|
+
setError(null);
|
|
1182
1017
|
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`);
|
|
1018
|
+
if (!createCheckout) {
|
|
1019
|
+
throw new Error("BillingProvider createCheckout callback is required for checkout");
|
|
1196
1020
|
}
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1021
|
+
const result = await createCheckout({
|
|
1022
|
+
planCode,
|
|
1023
|
+
returnUrl: returnUrl ?? window.location.href
|
|
1024
|
+
});
|
|
1025
|
+
await handleCheckoutResult(result, {
|
|
1026
|
+
onStripeClientSecret: (clientSecret) => {
|
|
1027
|
+
onStripeClientSecret?.(clientSecret);
|
|
1028
|
+
onCheckoutStarted?.();
|
|
1029
|
+
},
|
|
1030
|
+
onComplete: () => onCompleted?.(),
|
|
1031
|
+
onScheduled: () => onScheduled?.()
|
|
1032
|
+
});
|
|
1033
|
+
onConfirm?.();
|
|
1034
|
+
} catch (cause) {
|
|
1035
|
+
const checkoutError = cause instanceof Error ? cause : new Error("Checkout failed");
|
|
1036
|
+
setError(checkoutError.message);
|
|
1037
|
+
onError?.(checkoutError);
|
|
1038
|
+
} finally {
|
|
1202
1039
|
setConfirming(false);
|
|
1203
1040
|
}
|
|
1204
1041
|
}
|
|
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
|
-
};
|
|
1042
|
+
if (!isOpen) return null;
|
|
1227
1043
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1228
1044
|
"div",
|
|
1229
1045
|
{
|
|
1230
|
-
style:
|
|
1046
|
+
style: {
|
|
1047
|
+
position: "fixed",
|
|
1048
|
+
inset: 0,
|
|
1049
|
+
background: "oklch(0 0 0 / 0.5)",
|
|
1050
|
+
display: "flex",
|
|
1051
|
+
alignItems: "center",
|
|
1052
|
+
justifyContent: "center",
|
|
1053
|
+
zIndex: 1e3
|
|
1054
|
+
},
|
|
1231
1055
|
role: "dialog",
|
|
1232
1056
|
"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",
|
|
1057
|
+
"aria-label": "Change plan",
|
|
1058
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1059
|
+
"div",
|
|
1060
|
+
{
|
|
1061
|
+
style: {
|
|
1062
|
+
background: "var(--nozle-background, var(--background))",
|
|
1063
|
+
color: "var(--nozle-foreground, var(--foreground))",
|
|
1064
|
+
border: "1px solid var(--nozle-border, var(--border))",
|
|
1065
|
+
borderRadius: "var(--nozle-radius, 0.5rem)",
|
|
1066
|
+
padding: "2rem",
|
|
1067
|
+
maxWidth: "28rem",
|
|
1068
|
+
width: "100%",
|
|
1069
|
+
boxShadow: "var(--nozle-shadow, 0 4px 6px -1px rgb(0 0 0 / 0.1))"
|
|
1070
|
+
},
|
|
1071
|
+
children: [
|
|
1072
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { style: { fontSize: "1.25rem", fontWeight: 600, margin: "0 0 1rem" }, children: "Confirm plan change" }),
|
|
1073
|
+
error && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", style: { color: "oklch(0.6 0.2 25)" }, children: error }),
|
|
1074
|
+
preview && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: "1.5rem" }, children: [
|
|
1075
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1076
|
+
"div",
|
|
1336
1077
|
{
|
|
1337
|
-
onClick: onCancel,
|
|
1338
1078
|
style: {
|
|
1339
|
-
|
|
1079
|
+
background: "var(--nozle-muted, var(--muted))",
|
|
1340
1080
|
borderRadius: "var(--nozle-radius, 0.5rem)",
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1081
|
+
padding: "1rem",
|
|
1082
|
+
display: "flex",
|
|
1083
|
+
flexDirection: "column",
|
|
1084
|
+
gap: "0.5rem"
|
|
1345
1085
|
},
|
|
1346
|
-
|
|
1347
|
-
|
|
1086
|
+
children: [
|
|
1087
|
+
preview.credit > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1088
|
+
"Credits applied: -$",
|
|
1089
|
+
preview.credit.toFixed(2)
|
|
1090
|
+
] }),
|
|
1091
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1092
|
+
"New plan charge: $",
|
|
1093
|
+
preview.debit.toFixed(2)
|
|
1094
|
+
] }),
|
|
1095
|
+
/* @__PURE__ */ jsxRuntime.jsxs("strong", { children: [
|
|
1096
|
+
"Due today: $",
|
|
1097
|
+
preview.net.toFixed(2)
|
|
1098
|
+
] })
|
|
1099
|
+
]
|
|
1348
1100
|
}
|
|
1349
1101
|
),
|
|
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
|
-
] })
|
|
1102
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { style: { fontSize: "0.875rem" }, children: [
|
|
1103
|
+
"Next billing date: ",
|
|
1104
|
+
new Date(preview.nextBillingDate).toLocaleDateString(),
|
|
1105
|
+
"."
|
|
1106
|
+
] })
|
|
1107
|
+
] }),
|
|
1108
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: "0.75rem", justifyContent: "flex-end" }, children: [
|
|
1109
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: onCancel, disabled: confirming, children: "Cancel" }),
|
|
1110
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => void handleConfirm(), disabled: confirming, children: confirming ? "Starting checkout..." : "Confirm" })
|
|
1111
|
+
] })
|
|
1112
|
+
]
|
|
1113
|
+
}
|
|
1114
|
+
)
|
|
1372
1115
|
}
|
|
1373
1116
|
);
|
|
1374
1117
|
}
|
|
1375
1118
|
function UpgradeButton({
|
|
1376
|
-
|
|
1119
|
+
planCode,
|
|
1120
|
+
returnUrl,
|
|
1121
|
+
preview,
|
|
1377
1122
|
label = "Upgrade",
|
|
1378
|
-
apiBaseUrl = "https://api.nozle.app",
|
|
1379
1123
|
className,
|
|
1380
1124
|
style,
|
|
1381
|
-
|
|
1125
|
+
onStripeClientSecret,
|
|
1126
|
+
onCheckoutStarted,
|
|
1127
|
+
onDowngradeScheduled,
|
|
1128
|
+
onUpgraded,
|
|
1129
|
+
onError
|
|
1382
1130
|
}) {
|
|
1383
|
-
const { customerId, apiKey } = useBillingPortal();
|
|
1384
1131
|
const [isOpen, setIsOpen] = React.useState(false);
|
|
1385
1132
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1386
1133
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1387
1134
|
"button",
|
|
1388
1135
|
{
|
|
1136
|
+
type: "button",
|
|
1389
1137
|
onClick: () => setIsOpen(true),
|
|
1390
1138
|
className,
|
|
1391
1139
|
style: {
|
|
@@ -1405,275 +1153,36 @@ function UpgradeButton({
|
|
|
1405
1153
|
UpgradeModal,
|
|
1406
1154
|
{
|
|
1407
1155
|
isOpen,
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1156
|
+
planCode,
|
|
1157
|
+
returnUrl,
|
|
1158
|
+
preview,
|
|
1159
|
+
onStripeClientSecret,
|
|
1160
|
+
onCheckoutStarted: () => {
|
|
1413
1161
|
setIsOpen(false);
|
|
1414
|
-
|
|
1162
|
+
onCheckoutStarted?.();
|
|
1415
1163
|
},
|
|
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}`
|
|
1164
|
+
onCompleted: () => {
|
|
1165
|
+
setIsOpen(false);
|
|
1166
|
+
onUpgraded?.();
|
|
1451
1167
|
},
|
|
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
|
|
1168
|
+
onScheduled: () => {
|
|
1169
|
+
setIsOpen(false);
|
|
1170
|
+
onDowngradeScheduled?.();
|
|
1500
1171
|
},
|
|
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
|
-
] })
|
|
1172
|
+
onError,
|
|
1173
|
+
onCancel: () => setIsOpen(false)
|
|
1660
1174
|
}
|
|
1661
1175
|
)
|
|
1662
1176
|
] });
|
|
1663
1177
|
}
|
|
1664
1178
|
function FeatureGate({
|
|
1665
|
-
|
|
1666
|
-
|
|
1179
|
+
allowed,
|
|
1180
|
+
loading = false,
|
|
1667
1181
|
fallback = null,
|
|
1668
1182
|
children
|
|
1669
1183
|
}) {
|
|
1670
|
-
|
|
1671
|
-
if (
|
|
1672
|
-
return null;
|
|
1673
|
-
}
|
|
1674
|
-
if (!allowed) {
|
|
1675
|
-
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallback });
|
|
1676
|
-
}
|
|
1184
|
+
if (loading) return null;
|
|
1185
|
+
if (!allowed) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallback });
|
|
1677
1186
|
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
|
|
1678
1187
|
}
|
|
1679
1188
|
function UsageGate({
|
|
@@ -1938,288 +1447,26 @@ function UsageAlert({
|
|
|
1938
1447
|
f.key
|
|
1939
1448
|
)) });
|
|
1940
1449
|
}
|
|
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)"
|
|
1450
|
+
var TIER_STYLES = {
|
|
1451
|
+
free: {
|
|
1452
|
+
background: "var(--nozle-muted, var(--muted, #f1f5f9))",
|
|
1453
|
+
color: "var(--nozle-muted-foreground, var(--muted-foreground, #64748b))",
|
|
1454
|
+
border: "1px solid var(--nozle-border, var(--border, #e2e8f0))"
|
|
1988
1455
|
},
|
|
1989
|
-
|
|
1990
|
-
background: "var(--nozle-
|
|
1991
|
-
color: "var(--nozle-
|
|
1456
|
+
starter: {
|
|
1457
|
+
background: "color-mix(in srgb, var(--nozle-primary, var(--primary, #3b82f6)) 10%, transparent)",
|
|
1458
|
+
color: "var(--nozle-primary, var(--primary, #3b82f6))",
|
|
1459
|
+
border: "1px solid color-mix(in srgb, var(--nozle-primary, var(--primary, #3b82f6)) 30%, transparent)"
|
|
1992
1460
|
},
|
|
1993
|
-
|
|
1994
|
-
background: "var(--nozle-
|
|
1995
|
-
color: "var(--nozle-
|
|
1461
|
+
pro: {
|
|
1462
|
+
background: "var(--nozle-primary, var(--primary, #3b82f6))",
|
|
1463
|
+
color: "var(--nozle-primary-foreground, var(--primary-foreground, #ffffff))",
|
|
1464
|
+
border: "1px solid transparent"
|
|
1996
1465
|
},
|
|
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"
|
|
1466
|
+
enterprise: {
|
|
1467
|
+
background: "var(--nozle-accent, var(--accent, #8b5cf6))",
|
|
1468
|
+
color: "var(--nozle-accent-foreground, var(--accent-foreground, #ffffff))",
|
|
1469
|
+
border: "1px solid transparent"
|
|
2223
1470
|
}
|
|
2224
1471
|
};
|
|
2225
1472
|
function PlanBadge({ plan, tier = "free" }) {
|
|
@@ -2242,429 +1489,6 @@ function PlanBadge({ plan, tier = "free" }) {
|
|
|
2242
1489
|
}
|
|
2243
1490
|
);
|
|
2244
1491
|
}
|
|
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
1492
|
var BRAND_LABELS = {
|
|
2669
1493
|
visa: "Visa",
|
|
2670
1494
|
mastercard: "Mastercard",
|
|
@@ -2768,341 +1592,12 @@ function PaymentMethodDisplay({
|
|
|
2768
1592
|
)
|
|
2769
1593
|
] });
|
|
2770
1594
|
}
|
|
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
1595
|
|
|
3092
1596
|
exports.BillingContext = BillingContext;
|
|
3093
|
-
exports.BillingPortal = BillingPortal;
|
|
3094
|
-
exports.BillingPortalProvider = BillingPortalProvider;
|
|
3095
1597
|
exports.BillingProvider = BillingProvider;
|
|
3096
|
-
exports.CANCEL_REASONS = CANCEL_REASONS;
|
|
3097
|
-
exports.CancelSubscriptionButton = CancelSubscriptionButton;
|
|
3098
1598
|
exports.Checkout = Checkout;
|
|
3099
1599
|
exports.CheckoutButton = CheckoutButton;
|
|
3100
|
-
exports.CreditBalance = CreditBalance;
|
|
3101
|
-
exports.CreditHistory = CreditHistory;
|
|
3102
|
-
exports.CreditTopUpButton = CreditTopUpButton;
|
|
3103
|
-
exports.CurrentPlan = CurrentPlan;
|
|
3104
1600
|
exports.FeatureGate = FeatureGate;
|
|
3105
|
-
exports.InvoiceList = InvoiceList;
|
|
3106
1601
|
exports.LockedOverlay = LockedOverlay;
|
|
3107
1602
|
exports.PaymentMethodDisplay = PaymentMethodDisplay;
|
|
3108
1603
|
exports.PlanBadge = PlanBadge;
|
|
@@ -3119,11 +1614,10 @@ exports.UsageGate = UsageGate;
|
|
|
3119
1614
|
exports.UsageMeter = UsageMeter;
|
|
3120
1615
|
exports.getUsageMeterColor = getUsageMeterColor;
|
|
3121
1616
|
exports.useBillingContext = useBillingContext;
|
|
3122
|
-
exports.
|
|
3123
|
-
exports.
|
|
3124
|
-
exports.useCheckout = useCheckout;
|
|
3125
|
-
exports.useCredits = useCredits;
|
|
1617
|
+
exports.useCheckout = useCheckout2;
|
|
1618
|
+
exports.useCheckoutSession = useCheckout;
|
|
3126
1619
|
exports.useNozleClient = useNozleClient;
|
|
3127
|
-
exports.
|
|
1620
|
+
exports.useOptionalBillingContext = useOptionalBillingContext;
|
|
1621
|
+
exports.usePlans = usePlans;
|
|
3128
1622
|
//# sourceMappingURL=index.cjs.map
|
|
3129
1623
|
//# sourceMappingURL=index.cjs.map
|