@nozle-js/react 0.5.3 → 0.7.0

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