@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.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,9 @@ function CheckoutInner({
|
|
|
1009
872
|
onError,
|
|
1010
873
|
onReady,
|
|
1011
874
|
className,
|
|
1012
|
-
style
|
|
875
|
+
style,
|
|
876
|
+
returnUrl,
|
|
877
|
+
children
|
|
1013
878
|
}) {
|
|
1014
879
|
const stripe = useStripe();
|
|
1015
880
|
const elements = useElements();
|
|
@@ -1022,7 +887,7 @@ function CheckoutInner({
|
|
|
1022
887
|
setError(null);
|
|
1023
888
|
const result = await stripe.confirmPayment({
|
|
1024
889
|
elements,
|
|
1025
|
-
confirmParams: { return_url: window.location.href },
|
|
890
|
+
confirmParams: { return_url: returnUrl ?? window.location.href },
|
|
1026
891
|
redirect: "if_required"
|
|
1027
892
|
});
|
|
1028
893
|
setIsProcessing(false);
|
|
@@ -1059,7 +924,7 @@ function CheckoutInner({
|
|
|
1059
924
|
}
|
|
1060
925
|
),
|
|
1061
926
|
error !== null && /* @__PURE__ */ jsx("p", { style: { color: "var(--nozle-destructive, #dc2626)", margin: "0.5rem 0 0" }, children: error.message }),
|
|
1062
|
-
/* @__PURE__ */ jsx(
|
|
927
|
+
children ?? /* @__PURE__ */ jsx(
|
|
1063
928
|
"button",
|
|
1064
929
|
{
|
|
1065
930
|
type: "button",
|
|
@@ -1087,18 +952,28 @@ function Checkout({
|
|
|
1087
952
|
clientSecret,
|
|
1088
953
|
publishableKey,
|
|
1089
954
|
stripeAccount,
|
|
955
|
+
returnUrl,
|
|
1090
956
|
submitLabel,
|
|
1091
957
|
onSuccess,
|
|
958
|
+
onComplete,
|
|
1092
959
|
onError,
|
|
1093
960
|
onReady,
|
|
1094
961
|
className,
|
|
1095
|
-
style
|
|
962
|
+
style,
|
|
963
|
+
children
|
|
1096
964
|
}) {
|
|
1097
965
|
const stripePromise = useMemo(
|
|
1098
966
|
() => loadStripe(publishableKey, stripeAccount ? { stripeAccount } : void 0),
|
|
1099
967
|
[publishableKey, stripeAccount]
|
|
1100
968
|
);
|
|
1101
969
|
const appearance = useMemo(() => buildStripeAppearance(), []);
|
|
970
|
+
const embeddedOptions = useMemo(
|
|
971
|
+
() => ({ clientSecret, onComplete }),
|
|
972
|
+
[clientSecret, onComplete]
|
|
973
|
+
);
|
|
974
|
+
if (clientSecret.startsWith("cs_")) {
|
|
975
|
+
return /* @__PURE__ */ jsx("div", { className, style, children: /* @__PURE__ */ jsx(EmbeddedCheckoutProvider, { stripe: stripePromise, options: embeddedOptions, children: /* @__PURE__ */ jsx(EmbeddedCheckout, {}) }) });
|
|
976
|
+
}
|
|
1102
977
|
const options = { clientSecret, appearance };
|
|
1103
978
|
return /* @__PURE__ */ jsx(Elements, { stripe: stripePromise, options, children: /* @__PURE__ */ jsx(
|
|
1104
979
|
CheckoutInner,
|
|
@@ -1108,278 +983,151 @@ function Checkout({
|
|
|
1108
983
|
onError,
|
|
1109
984
|
onReady,
|
|
1110
985
|
className,
|
|
1111
|
-
style
|
|
986
|
+
style,
|
|
987
|
+
returnUrl,
|
|
988
|
+
children
|
|
1112
989
|
}
|
|
1113
990
|
) }, clientSecret);
|
|
1114
991
|
}
|
|
1115
992
|
function UpgradeModal({
|
|
1116
993
|
isOpen,
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
994
|
+
planCode,
|
|
995
|
+
returnUrl,
|
|
996
|
+
preview,
|
|
997
|
+
onStripeClientSecret,
|
|
998
|
+
onCheckoutStarted,
|
|
999
|
+
onCompleted,
|
|
1000
|
+
onScheduled,
|
|
1121
1001
|
onConfirm,
|
|
1122
|
-
onCancel
|
|
1002
|
+
onCancel,
|
|
1003
|
+
onError
|
|
1123
1004
|
}) {
|
|
1124
|
-
const
|
|
1125
|
-
const [loading, setLoading] = useState(false);
|
|
1005
|
+
const { createCheckout } = useBillingContext();
|
|
1126
1006
|
const [error, setError] = useState(null);
|
|
1127
1007
|
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
1008
|
async function handleConfirm() {
|
|
1175
1009
|
setConfirming(true);
|
|
1010
|
+
setError(null);
|
|
1176
1011
|
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`);
|
|
1012
|
+
if (!createCheckout) {
|
|
1013
|
+
throw new Error("BillingProvider createCheckout callback is required for checkout");
|
|
1190
1014
|
}
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1015
|
+
const result = await createCheckout({
|
|
1016
|
+
planCode,
|
|
1017
|
+
returnUrl: returnUrl ?? window.location.href
|
|
1018
|
+
});
|
|
1019
|
+
await handleCheckoutResult(result, {
|
|
1020
|
+
onStripeClientSecret: (clientSecret) => {
|
|
1021
|
+
onStripeClientSecret?.(clientSecret);
|
|
1022
|
+
onCheckoutStarted?.();
|
|
1023
|
+
},
|
|
1024
|
+
onComplete: () => onCompleted?.(),
|
|
1025
|
+
onScheduled: () => onScheduled?.()
|
|
1026
|
+
});
|
|
1027
|
+
onConfirm?.();
|
|
1028
|
+
} catch (cause) {
|
|
1029
|
+
const checkoutError = cause instanceof Error ? cause : new Error("Checkout failed");
|
|
1030
|
+
setError(checkoutError.message);
|
|
1031
|
+
onError?.(checkoutError);
|
|
1032
|
+
} finally {
|
|
1196
1033
|
setConfirming(false);
|
|
1197
1034
|
}
|
|
1198
1035
|
}
|
|
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
|
-
};
|
|
1036
|
+
if (!isOpen) return null;
|
|
1221
1037
|
return /* @__PURE__ */ jsx(
|
|
1222
1038
|
"div",
|
|
1223
1039
|
{
|
|
1224
|
-
style:
|
|
1040
|
+
style: {
|
|
1041
|
+
position: "fixed",
|
|
1042
|
+
inset: 0,
|
|
1043
|
+
background: "oklch(0 0 0 / 0.5)",
|
|
1044
|
+
display: "flex",
|
|
1045
|
+
alignItems: "center",
|
|
1046
|
+
justifyContent: "center",
|
|
1047
|
+
zIndex: 1e3
|
|
1048
|
+
},
|
|
1225
1049
|
role: "dialog",
|
|
1226
1050
|
"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",
|
|
1051
|
+
"aria-label": "Change plan",
|
|
1052
|
+
children: /* @__PURE__ */ jsxs(
|
|
1053
|
+
"div",
|
|
1054
|
+
{
|
|
1055
|
+
style: {
|
|
1056
|
+
background: "var(--nozle-background, var(--background))",
|
|
1057
|
+
color: "var(--nozle-foreground, var(--foreground))",
|
|
1058
|
+
border: "1px solid var(--nozle-border, var(--border))",
|
|
1059
|
+
borderRadius: "var(--nozle-radius, 0.5rem)",
|
|
1060
|
+
padding: "2rem",
|
|
1061
|
+
maxWidth: "28rem",
|
|
1062
|
+
width: "100%",
|
|
1063
|
+
boxShadow: "var(--nozle-shadow, 0 4px 6px -1px rgb(0 0 0 / 0.1))"
|
|
1064
|
+
},
|
|
1065
|
+
children: [
|
|
1066
|
+
/* @__PURE__ */ jsx("h2", { style: { fontSize: "1.25rem", fontWeight: 600, margin: "0 0 1rem" }, children: "Confirm plan change" }),
|
|
1067
|
+
error && /* @__PURE__ */ jsx("p", { role: "alert", style: { color: "oklch(0.6 0.2 25)" }, children: error }),
|
|
1068
|
+
preview && /* @__PURE__ */ jsxs("div", { style: { marginBottom: "1.5rem" }, children: [
|
|
1069
|
+
/* @__PURE__ */ jsxs(
|
|
1070
|
+
"div",
|
|
1330
1071
|
{
|
|
1331
|
-
onClick: onCancel,
|
|
1332
1072
|
style: {
|
|
1333
|
-
|
|
1073
|
+
background: "var(--nozle-muted, var(--muted))",
|
|
1334
1074
|
borderRadius: "var(--nozle-radius, 0.5rem)",
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1075
|
+
padding: "1rem",
|
|
1076
|
+
display: "flex",
|
|
1077
|
+
flexDirection: "column",
|
|
1078
|
+
gap: "0.5rem"
|
|
1339
1079
|
},
|
|
1340
|
-
|
|
1341
|
-
|
|
1080
|
+
children: [
|
|
1081
|
+
preview.credit > 0 && /* @__PURE__ */ jsxs("div", { children: [
|
|
1082
|
+
"Credits applied: -$",
|
|
1083
|
+
preview.credit.toFixed(2)
|
|
1084
|
+
] }),
|
|
1085
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
1086
|
+
"New plan charge: $",
|
|
1087
|
+
preview.debit.toFixed(2)
|
|
1088
|
+
] }),
|
|
1089
|
+
/* @__PURE__ */ jsxs("strong", { children: [
|
|
1090
|
+
"Due today: $",
|
|
1091
|
+
preview.net.toFixed(2)
|
|
1092
|
+
] })
|
|
1093
|
+
]
|
|
1342
1094
|
}
|
|
1343
1095
|
),
|
|
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
|
-
] })
|
|
1096
|
+
/* @__PURE__ */ jsxs("p", { style: { fontSize: "0.875rem" }, children: [
|
|
1097
|
+
"Next billing date: ",
|
|
1098
|
+
new Date(preview.nextBillingDate).toLocaleDateString(),
|
|
1099
|
+
"."
|
|
1100
|
+
] })
|
|
1101
|
+
] }),
|
|
1102
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "0.75rem", justifyContent: "flex-end" }, children: [
|
|
1103
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: onCancel, disabled: confirming, children: "Cancel" }),
|
|
1104
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => void handleConfirm(), disabled: confirming, children: confirming ? "Starting checkout..." : "Confirm" })
|
|
1105
|
+
] })
|
|
1106
|
+
]
|
|
1107
|
+
}
|
|
1108
|
+
)
|
|
1366
1109
|
}
|
|
1367
1110
|
);
|
|
1368
1111
|
}
|
|
1369
1112
|
function UpgradeButton({
|
|
1370
|
-
|
|
1113
|
+
planCode,
|
|
1114
|
+
returnUrl,
|
|
1115
|
+
preview,
|
|
1371
1116
|
label = "Upgrade",
|
|
1372
|
-
apiBaseUrl = "https://api.nozle.app",
|
|
1373
1117
|
className,
|
|
1374
1118
|
style,
|
|
1375
|
-
|
|
1119
|
+
onStripeClientSecret,
|
|
1120
|
+
onCheckoutStarted,
|
|
1121
|
+
onDowngradeScheduled,
|
|
1122
|
+
onUpgraded,
|
|
1123
|
+
onError
|
|
1376
1124
|
}) {
|
|
1377
|
-
const { customerId, apiKey } = useBillingPortal();
|
|
1378
1125
|
const [isOpen, setIsOpen] = useState(false);
|
|
1379
1126
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1380
1127
|
/* @__PURE__ */ jsx(
|
|
1381
1128
|
"button",
|
|
1382
1129
|
{
|
|
1130
|
+
type: "button",
|
|
1383
1131
|
onClick: () => setIsOpen(true),
|
|
1384
1132
|
className,
|
|
1385
1133
|
style: {
|
|
@@ -1399,275 +1147,36 @@ function UpgradeButton({
|
|
|
1399
1147
|
UpgradeModal,
|
|
1400
1148
|
{
|
|
1401
1149
|
isOpen,
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1150
|
+
planCode,
|
|
1151
|
+
returnUrl,
|
|
1152
|
+
preview,
|
|
1153
|
+
onStripeClientSecret,
|
|
1154
|
+
onCheckoutStarted: () => {
|
|
1407
1155
|
setIsOpen(false);
|
|
1408
|
-
|
|
1156
|
+
onCheckoutStarted?.();
|
|
1409
1157
|
},
|
|
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}`
|
|
1158
|
+
onCompleted: () => {
|
|
1159
|
+
setIsOpen(false);
|
|
1160
|
+
onUpgraded?.();
|
|
1445
1161
|
},
|
|
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
|
|
1162
|
+
onScheduled: () => {
|
|
1163
|
+
setIsOpen(false);
|
|
1164
|
+
onDowngradeScheduled?.();
|
|
1494
1165
|
},
|
|
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
|
-
] })
|
|
1166
|
+
onError,
|
|
1167
|
+
onCancel: () => setIsOpen(false)
|
|
1654
1168
|
}
|
|
1655
1169
|
)
|
|
1656
1170
|
] });
|
|
1657
1171
|
}
|
|
1658
1172
|
function FeatureGate({
|
|
1659
|
-
|
|
1660
|
-
|
|
1173
|
+
allowed,
|
|
1174
|
+
loading = false,
|
|
1661
1175
|
fallback = null,
|
|
1662
1176
|
children
|
|
1663
1177
|
}) {
|
|
1664
|
-
|
|
1665
|
-
if (
|
|
1666
|
-
return null;
|
|
1667
|
-
}
|
|
1668
|
-
if (!allowed) {
|
|
1669
|
-
return /* @__PURE__ */ jsx(Fragment, { children: fallback });
|
|
1670
|
-
}
|
|
1178
|
+
if (loading) return null;
|
|
1179
|
+
if (!allowed) return /* @__PURE__ */ jsx(Fragment, { children: fallback });
|
|
1671
1180
|
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
1672
1181
|
}
|
|
1673
1182
|
function UsageGate({
|
|
@@ -1932,288 +1441,26 @@ function UsageAlert({
|
|
|
1932
1441
|
f.key
|
|
1933
1442
|
)) });
|
|
1934
1443
|
}
|
|
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)"
|
|
1444
|
+
var TIER_STYLES = {
|
|
1445
|
+
free: {
|
|
1446
|
+
background: "var(--nozle-muted, var(--muted, #f1f5f9))",
|
|
1447
|
+
color: "var(--nozle-muted-foreground, var(--muted-foreground, #64748b))",
|
|
1448
|
+
border: "1px solid var(--nozle-border, var(--border, #e2e8f0))"
|
|
1982
1449
|
},
|
|
1983
|
-
|
|
1984
|
-
background: "var(--nozle-
|
|
1985
|
-
color: "var(--nozle-
|
|
1450
|
+
starter: {
|
|
1451
|
+
background: "color-mix(in srgb, var(--nozle-primary, var(--primary, #3b82f6)) 10%, transparent)",
|
|
1452
|
+
color: "var(--nozle-primary, var(--primary, #3b82f6))",
|
|
1453
|
+
border: "1px solid color-mix(in srgb, var(--nozle-primary, var(--primary, #3b82f6)) 30%, transparent)"
|
|
1986
1454
|
},
|
|
1987
|
-
|
|
1988
|
-
background: "var(--nozle-
|
|
1989
|
-
color: "var(--nozle-
|
|
1455
|
+
pro: {
|
|
1456
|
+
background: "var(--nozle-primary, var(--primary, #3b82f6))",
|
|
1457
|
+
color: "var(--nozle-primary-foreground, var(--primary-foreground, #ffffff))",
|
|
1458
|
+
border: "1px solid transparent"
|
|
1990
1459
|
},
|
|
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"
|
|
1460
|
+
enterprise: {
|
|
1461
|
+
background: "var(--nozle-accent, var(--accent, #8b5cf6))",
|
|
1462
|
+
color: "var(--nozle-accent-foreground, var(--accent-foreground, #ffffff))",
|
|
1463
|
+
border: "1px solid transparent"
|
|
2217
1464
|
}
|
|
2218
1465
|
};
|
|
2219
1466
|
function PlanBadge({ plan, tier = "free" }) {
|
|
@@ -2236,429 +1483,6 @@ function PlanBadge({ plan, tier = "free" }) {
|
|
|
2236
1483
|
}
|
|
2237
1484
|
);
|
|
2238
1485
|
}
|
|
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
1486
|
var BRAND_LABELS = {
|
|
2663
1487
|
visa: "Visa",
|
|
2664
1488
|
mastercard: "Mastercard",
|
|
@@ -2762,327 +1586,7 @@ function PaymentMethodDisplay({
|
|
|
2762
1586
|
)
|
|
2763
1587
|
] });
|
|
2764
1588
|
}
|
|
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
1589
|
|
|
3086
|
-
export { BillingContext,
|
|
1590
|
+
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
1591
|
//# sourceMappingURL=index.js.map
|
|
3088
1592
|
//# sourceMappingURL=index.js.map
|