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