@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/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,9 @@ function CheckoutInner({
1015
878
  onError,
1016
879
  onReady,
1017
880
  className,
1018
- style
881
+ style,
882
+ returnUrl,
883
+ children
1019
884
  }) {
1020
885
  const stripe = reactStripeJs.useStripe();
1021
886
  const elements = reactStripeJs.useElements();
@@ -1028,7 +893,7 @@ function CheckoutInner({
1028
893
  setError(null);
1029
894
  const result = await stripe.confirmPayment({
1030
895
  elements,
1031
- confirmParams: { return_url: window.location.href },
896
+ confirmParams: { return_url: returnUrl ?? window.location.href },
1032
897
  redirect: "if_required"
1033
898
  });
1034
899
  setIsProcessing(false);
@@ -1065,7 +930,7 @@ function CheckoutInner({
1065
930
  }
1066
931
  ),
1067
932
  error !== null && /* @__PURE__ */ jsxRuntime.jsx("p", { style: { color: "var(--nozle-destructive, #dc2626)", margin: "0.5rem 0 0" }, children: error.message }),
1068
- /* @__PURE__ */ jsxRuntime.jsx(
933
+ children ?? /* @__PURE__ */ jsxRuntime.jsx(
1069
934
  "button",
1070
935
  {
1071
936
  type: "button",
@@ -1093,18 +958,28 @@ function Checkout({
1093
958
  clientSecret,
1094
959
  publishableKey,
1095
960
  stripeAccount,
961
+ returnUrl,
1096
962
  submitLabel,
1097
963
  onSuccess,
964
+ onComplete,
1098
965
  onError,
1099
966
  onReady,
1100
967
  className,
1101
- style
968
+ style,
969
+ children
1102
970
  }) {
1103
971
  const stripePromise = React.useMemo(
1104
972
  () => stripeJs.loadStripe(publishableKey, stripeAccount ? { stripeAccount } : void 0),
1105
973
  [publishableKey, stripeAccount]
1106
974
  );
1107
975
  const appearance = React.useMemo(() => buildStripeAppearance(), []);
976
+ const embeddedOptions = React.useMemo(
977
+ () => ({ clientSecret, onComplete }),
978
+ [clientSecret, onComplete]
979
+ );
980
+ if (clientSecret.startsWith("cs_")) {
981
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className, style, children: /* @__PURE__ */ jsxRuntime.jsx(reactStripeJs.EmbeddedCheckoutProvider, { stripe: stripePromise, options: embeddedOptions, children: /* @__PURE__ */ jsxRuntime.jsx(reactStripeJs.EmbeddedCheckout, {}) }) });
982
+ }
1108
983
  const options = { clientSecret, appearance };
1109
984
  return /* @__PURE__ */ jsxRuntime.jsx(reactStripeJs.Elements, { stripe: stripePromise, options, children: /* @__PURE__ */ jsxRuntime.jsx(
1110
985
  CheckoutInner,
@@ -1114,278 +989,151 @@ function Checkout({
1114
989
  onError,
1115
990
  onReady,
1116
991
  className,
1117
- style
992
+ style,
993
+ returnUrl,
994
+ children
1118
995
  }
1119
996
  ) }, clientSecret);
1120
997
  }
1121
998
  function UpgradeModal({
1122
999
  isOpen,
1123
- targetPlanId,
1124
- customerId,
1125
- apiBaseUrl = "https://api.nozle.app",
1126
- apiKey = "",
1000
+ planCode,
1001
+ returnUrl,
1002
+ preview,
1003
+ onStripeClientSecret,
1004
+ onCheckoutStarted,
1005
+ onCompleted,
1006
+ onScheduled,
1127
1007
  onConfirm,
1128
- onCancel
1008
+ onCancel,
1009
+ onError
1129
1010
  }) {
1130
- const [preview, setPreview] = React.useState(null);
1131
- const [loading, setLoading] = React.useState(false);
1011
+ const { createCheckout } = useBillingContext();
1132
1012
  const [error, setError] = React.useState(null);
1133
1013
  const [confirming, setConfirming] = React.useState(false);
1134
- React.useEffect(() => {
1135
- if (!isOpen || !targetPlanId || !customerId) {
1136
- return;
1137
- }
1138
- let cancelled = false;
1139
- setLoading(true);
1140
- setError(null);
1141
- setPreview(null);
1142
- async function fetchPreview() {
1143
- try {
1144
- const response = await fetch(
1145
- `${apiBaseUrl}/api/v1/subscriptions/preview`,
1146
- {
1147
- method: "POST",
1148
- headers: {
1149
- "Content-Type": "application/json",
1150
- Authorization: `Bearer ${apiKey}`
1151
- },
1152
- body: JSON.stringify({ targetPlanId, customerId })
1153
- }
1154
- );
1155
- if (!response.ok) {
1156
- throw new Error(
1157
- `HTTP ${response.status}: Failed to load proration preview`
1158
- );
1159
- }
1160
- const data = await response.json();
1161
- if (!cancelled) {
1162
- setPreview(data);
1163
- }
1164
- } catch (err) {
1165
- if (!cancelled) {
1166
- const message = err instanceof Error ? err.message : "Failed to load preview";
1167
- setError(message);
1168
- }
1169
- } finally {
1170
- if (!cancelled) {
1171
- setLoading(false);
1172
- }
1173
- }
1174
- }
1175
- void fetchPreview();
1176
- return () => {
1177
- cancelled = true;
1178
- };
1179
- }, [isOpen, targetPlanId, customerId, apiBaseUrl, apiKey]);
1180
1014
  async function handleConfirm() {
1181
1015
  setConfirming(true);
1016
+ setError(null);
1182
1017
  try {
1183
- 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`);
1018
+ if (!createCheckout) {
1019
+ throw new Error("BillingProvider createCheckout callback is required for checkout");
1196
1020
  }
1197
- onConfirm?.();
1198
- } catch (err) {
1199
- const message = err instanceof Error ? err.message : "Upgrade failed";
1200
- setError(message);
1201
- } finally {
1021
+ const result = await createCheckout({
1022
+ planCode,
1023
+ returnUrl: returnUrl ?? window.location.href
1024
+ });
1025
+ await handleCheckoutResult(result, {
1026
+ onStripeClientSecret: (clientSecret) => {
1027
+ onStripeClientSecret?.(clientSecret);
1028
+ onCheckoutStarted?.();
1029
+ },
1030
+ onComplete: () => onCompleted?.(),
1031
+ onScheduled: () => onScheduled?.()
1032
+ });
1033
+ onConfirm?.();
1034
+ } catch (cause) {
1035
+ const checkoutError = cause instanceof Error ? cause : new Error("Checkout failed");
1036
+ setError(checkoutError.message);
1037
+ onError?.(checkoutError);
1038
+ } finally {
1202
1039
  setConfirming(false);
1203
1040
  }
1204
1041
  }
1205
- if (!isOpen) {
1206
- return null;
1207
- }
1208
- const overlayStyle = {
1209
- position: "fixed",
1210
- inset: 0,
1211
- background: "oklch(0 0 0 / 0.5)",
1212
- display: "flex",
1213
- alignItems: "center",
1214
- justifyContent: "center",
1215
- zIndex: 1e3
1216
- };
1217
- const modalStyle = {
1218
- background: "var(--nozle-background, var(--background))",
1219
- color: "var(--nozle-foreground, var(--foreground))",
1220
- border: "1px solid var(--nozle-border, var(--border))",
1221
- borderRadius: "var(--nozle-radius, 0.5rem)",
1222
- padding: "2rem",
1223
- maxWidth: "28rem",
1224
- width: "100%",
1225
- boxShadow: "var(--nozle-shadow, 0 4px 6px -1px rgb(0 0 0 / 0.1))"
1226
- };
1042
+ if (!isOpen) return null;
1227
1043
  return /* @__PURE__ */ jsxRuntime.jsx(
1228
1044
  "div",
1229
1045
  {
1230
- style: overlayStyle,
1046
+ style: {
1047
+ position: "fixed",
1048
+ inset: 0,
1049
+ background: "oklch(0 0 0 / 0.5)",
1050
+ display: "flex",
1051
+ alignItems: "center",
1052
+ justifyContent: "center",
1053
+ zIndex: 1e3
1054
+ },
1231
1055
  role: "dialog",
1232
1056
  "aria-modal": "true",
1233
- "aria-label": "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",
1057
+ "aria-label": "Change plan",
1058
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
1059
+ "div",
1060
+ {
1061
+ style: {
1062
+ background: "var(--nozle-background, var(--background))",
1063
+ color: "var(--nozle-foreground, var(--foreground))",
1064
+ border: "1px solid var(--nozle-border, var(--border))",
1065
+ borderRadius: "var(--nozle-radius, 0.5rem)",
1066
+ padding: "2rem",
1067
+ maxWidth: "28rem",
1068
+ width: "100%",
1069
+ boxShadow: "var(--nozle-shadow, 0 4px 6px -1px rgb(0 0 0 / 0.1))"
1070
+ },
1071
+ children: [
1072
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { style: { fontSize: "1.25rem", fontWeight: 600, margin: "0 0 1rem" }, children: "Confirm plan change" }),
1073
+ error && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", style: { color: "oklch(0.6 0.2 25)" }, children: error }),
1074
+ preview && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: "1.5rem" }, children: [
1075
+ /* @__PURE__ */ jsxRuntime.jsxs(
1076
+ "div",
1336
1077
  {
1337
- onClick: onCancel,
1338
1078
  style: {
1339
- padding: "0.5rem 1rem",
1079
+ background: "var(--nozle-muted, var(--muted))",
1340
1080
  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"
1081
+ padding: "1rem",
1082
+ display: "flex",
1083
+ flexDirection: "column",
1084
+ gap: "0.5rem"
1345
1085
  },
1346
- disabled: confirming,
1347
- children: "Cancel"
1086
+ children: [
1087
+ preview.credit > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1088
+ "Credits applied: -$",
1089
+ preview.credit.toFixed(2)
1090
+ ] }),
1091
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1092
+ "New plan charge: $",
1093
+ preview.debit.toFixed(2)
1094
+ ] }),
1095
+ /* @__PURE__ */ jsxRuntime.jsxs("strong", { children: [
1096
+ "Due today: $",
1097
+ preview.net.toFixed(2)
1098
+ ] })
1099
+ ]
1348
1100
  }
1349
1101
  ),
1350
- /* @__PURE__ */ jsxRuntime.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
- ] })
1102
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { style: { fontSize: "0.875rem" }, children: [
1103
+ "Next billing date: ",
1104
+ new Date(preview.nextBillingDate).toLocaleDateString(),
1105
+ "."
1106
+ ] })
1107
+ ] }),
1108
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: "0.75rem", justifyContent: "flex-end" }, children: [
1109
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: onCancel, disabled: confirming, children: "Cancel" }),
1110
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => void handleConfirm(), disabled: confirming, children: confirming ? "Starting checkout..." : "Confirm" })
1111
+ ] })
1112
+ ]
1113
+ }
1114
+ )
1372
1115
  }
1373
1116
  );
1374
1117
  }
1375
1118
  function UpgradeButton({
1376
- targetPlanId,
1119
+ planCode,
1120
+ returnUrl,
1121
+ preview,
1377
1122
  label = "Upgrade",
1378
- apiBaseUrl = "https://api.nozle.app",
1379
1123
  className,
1380
1124
  style,
1381
- onUpgraded
1125
+ onStripeClientSecret,
1126
+ onCheckoutStarted,
1127
+ onDowngradeScheduled,
1128
+ onUpgraded,
1129
+ onError
1382
1130
  }) {
1383
- const { customerId, apiKey } = useBillingPortal();
1384
1131
  const [isOpen, setIsOpen] = React.useState(false);
1385
1132
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1386
1133
  /* @__PURE__ */ jsxRuntime.jsx(
1387
1134
  "button",
1388
1135
  {
1136
+ type: "button",
1389
1137
  onClick: () => setIsOpen(true),
1390
1138
  className,
1391
1139
  style: {
@@ -1405,275 +1153,36 @@ function UpgradeButton({
1405
1153
  UpgradeModal,
1406
1154
  {
1407
1155
  isOpen,
1408
- targetPlanId,
1409
- customerId,
1410
- apiBaseUrl,
1411
- apiKey,
1412
- onConfirm: () => {
1156
+ planCode,
1157
+ returnUrl,
1158
+ preview,
1159
+ onStripeClientSecret,
1160
+ onCheckoutStarted: () => {
1413
1161
  setIsOpen(false);
1414
- onUpgraded?.();
1162
+ onCheckoutStarted?.();
1415
1163
  },
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}`
1164
+ onCompleted: () => {
1165
+ setIsOpen(false);
1166
+ onUpgraded?.();
1451
1167
  },
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
1168
+ onScheduled: () => {
1169
+ setIsOpen(false);
1170
+ onDowngradeScheduled?.();
1500
1171
  },
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
- ] })
1172
+ onError,
1173
+ onCancel: () => setIsOpen(false)
1660
1174
  }
1661
1175
  )
1662
1176
  ] });
1663
1177
  }
1664
1178
  function FeatureGate({
1665
- customerId,
1666
- feature,
1179
+ allowed,
1180
+ loading = false,
1667
1181
  fallback = null,
1668
1182
  children
1669
1183
  }) {
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
- }
1184
+ if (loading) return null;
1185
+ if (!allowed) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallback });
1677
1186
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
1678
1187
  }
1679
1188
  function UsageGate({
@@ -1938,288 +1447,26 @@ function UsageAlert({
1938
1447
  f.key
1939
1448
  )) });
1940
1449
  }
1941
- 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)"
1450
+ var TIER_STYLES = {
1451
+ free: {
1452
+ background: "var(--nozle-muted, var(--muted, #f1f5f9))",
1453
+ color: "var(--nozle-muted-foreground, var(--muted-foreground, #64748b))",
1454
+ border: "1px solid var(--nozle-border, var(--border, #e2e8f0))"
1988
1455
  },
1989
- deduct: {
1990
- background: "var(--nozle-destructive-bg, #fee2e2)",
1991
- color: "var(--nozle-destructive, #dc2626)"
1456
+ starter: {
1457
+ background: "color-mix(in srgb, var(--nozle-primary, var(--primary, #3b82f6)) 10%, transparent)",
1458
+ color: "var(--nozle-primary, var(--primary, #3b82f6))",
1459
+ border: "1px solid color-mix(in srgb, var(--nozle-primary, var(--primary, #3b82f6)) 30%, transparent)"
1992
1460
  },
1993
- purchase: {
1994
- background: "var(--nozle-info-bg, #dbeafe)",
1995
- color: "var(--nozle-info, #2563eb)"
1461
+ pro: {
1462
+ background: "var(--nozle-primary, var(--primary, #3b82f6))",
1463
+ color: "var(--nozle-primary-foreground, var(--primary-foreground, #ffffff))",
1464
+ border: "1px solid transparent"
1996
1465
  },
1997
- 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"
1466
+ enterprise: {
1467
+ background: "var(--nozle-accent, var(--accent, #8b5cf6))",
1468
+ color: "var(--nozle-accent-foreground, var(--accent-foreground, #ffffff))",
1469
+ border: "1px solid transparent"
2223
1470
  }
2224
1471
  };
2225
1472
  function PlanBadge({ plan, tier = "free" }) {
@@ -2242,429 +1489,6 @@ function PlanBadge({ plan, tier = "free" }) {
2242
1489
  }
2243
1490
  );
2244
1491
  }
2245
- function formatDate2(iso) {
2246
- if (!iso) return "\u2014";
2247
- try {
2248
- return new Date(iso).toLocaleDateString("en-US", {
2249
- year: "numeric",
2250
- month: "long",
2251
- day: "numeric"
2252
- });
2253
- } catch {
2254
- return iso;
2255
- }
2256
- }
2257
- function CurrentPlan({
2258
- customerId,
2259
- onChangePlan,
2260
- changePlanHref = "/plans"
2261
- }) {
2262
- const { client } = useBillingContext();
2263
- const [plan, setPlan] = React.useState(null);
2264
- const [loading, setLoading] = React.useState(true);
2265
- const [error, setError] = React.useState(null);
2266
- React.useEffect(() => {
2267
- if (!client) {
2268
- setError("No client in context");
2269
- setLoading(false);
2270
- return;
2271
- }
2272
- let cancelled = false;
2273
- async function fetchPlan() {
2274
- try {
2275
- const clientInternal = client;
2276
- const apiKey = clientInternal.apiKey ?? "";
2277
- const baseUrl = clientInternal.baseUrl ?? "https://api.nozle.app";
2278
- const timeout = clientInternal.timeout ?? 5e3;
2279
- const controller = new AbortController();
2280
- const timeoutId = setTimeout(() => controller.abort(), timeout);
2281
- const response = await fetch(
2282
- `${baseUrl}/api/v1/subscriptions/current?customer_id=${customerId}`,
2283
- {
2284
- headers: { Authorization: `Bearer ${apiKey}` },
2285
- signal: controller.signal
2286
- }
2287
- );
2288
- clearTimeout(timeoutId);
2289
- if (!response.ok) {
2290
- throw new Error(`HTTP ${response.status}`);
2291
- }
2292
- const data = await response.json();
2293
- if (!cancelled) {
2294
- setPlan(data);
2295
- setLoading(false);
2296
- }
2297
- } catch (err) {
2298
- if (!cancelled) {
2299
- setError(err instanceof Error ? err.message : "Unknown error");
2300
- setLoading(false);
2301
- }
2302
- }
2303
- }
2304
- void fetchPlan();
2305
- return () => {
2306
- cancelled = true;
2307
- };
2308
- }, [client, customerId]);
2309
- if (loading) {
2310
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "animate-pulse space-y-2", children: [
2311
- /* @__PURE__ */ jsxRuntime.jsx(
2312
- "div",
2313
- {
2314
- className: "h-6 w-32 rounded",
2315
- style: { background: "var(--nozle-border, var(--border))" }
2316
- }
2317
- ),
2318
- /* @__PURE__ */ jsxRuntime.jsx(
2319
- "div",
2320
- {
2321
- className: "h-4 w-48 rounded",
2322
- style: { background: "var(--nozle-border, var(--border))" }
2323
- }
2324
- )
2325
- ] });
2326
- }
2327
- if (error || !plan) {
2328
- return /* @__PURE__ */ jsxRuntime.jsx(
2329
- "span",
2330
- {
2331
- style: { color: "var(--nozle-destructive, var(--destructive))" },
2332
- className: "text-sm",
2333
- children: "Failed to load current plan"
2334
- }
2335
- );
2336
- }
2337
- const handleChangePlan = () => {
2338
- if (onChangePlan) {
2339
- onChangePlan();
2340
- } else {
2341
- window.location.href = changePlanHref;
2342
- }
2343
- };
2344
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
2345
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
2346
- /* @__PURE__ */ jsxRuntime.jsx(PlanBadge, { plan: plan.planName, tier: plan.tier }),
2347
- /* @__PURE__ */ jsxRuntime.jsx(
2348
- "span",
2349
- {
2350
- style: {
2351
- color: "var(--nozle-muted-foreground, var(--muted-foreground))",
2352
- fontSize: "0.875rem"
2353
- },
2354
- children: plan.interval
2355
- }
2356
- ),
2357
- /* @__PURE__ */ jsxRuntime.jsx(
2358
- "span",
2359
- {
2360
- style: {
2361
- color: plan.status === "active" ? "var(--nozle-success, #16a34a)" : "var(--nozle-warning, var(--warning))",
2362
- fontSize: "0.875rem",
2363
- fontWeight: 500
2364
- },
2365
- children: plan.status
2366
- }
2367
- )
2368
- ] }),
2369
- plan.nextBillingDate && /* @__PURE__ */ jsxRuntime.jsxs(
2370
- "p",
2371
- {
2372
- style: {
2373
- color: "var(--nozle-muted-foreground, var(--muted-foreground))",
2374
- fontSize: "0.875rem"
2375
- },
2376
- children: [
2377
- "Next billing: ",
2378
- formatDate2(plan.nextBillingDate)
2379
- ]
2380
- }
2381
- ),
2382
- /* @__PURE__ */ jsxRuntime.jsx(
2383
- "button",
2384
- {
2385
- onClick: handleChangePlan,
2386
- style: {
2387
- background: "transparent",
2388
- border: "1px solid var(--nozle-primary, var(--primary))",
2389
- color: "var(--nozle-primary, var(--primary))",
2390
- padding: "0.375rem 0.875rem",
2391
- borderRadius: "0.375rem",
2392
- fontSize: "0.875rem",
2393
- fontWeight: 500,
2394
- cursor: "pointer"
2395
- },
2396
- children: "Change Plan"
2397
- }
2398
- )
2399
- ] });
2400
- }
2401
- var STATUS_STYLES = {
2402
- paid: {
2403
- background: "var(--nozle-success-bg, #dcfce7)",
2404
- color: "var(--nozle-success, #16a34a)"
2405
- },
2406
- open: {
2407
- background: "var(--nozle-info-bg, #dbeafe)",
2408
- color: "var(--nozle-info, #2563eb)"
2409
- },
2410
- draft: {
2411
- background: "var(--nozle-muted, #f1f5f9)",
2412
- color: "var(--nozle-muted-foreground, #64748b)"
2413
- },
2414
- void: {
2415
- background: "var(--nozle-muted, #f1f5f9)",
2416
- color: "var(--nozle-muted-foreground, #64748b)"
2417
- },
2418
- uncollectible: {
2419
- background: "var(--nozle-destructive-bg, #fee2e2)",
2420
- color: "var(--nozle-destructive, #dc2626)"
2421
- }
2422
- };
2423
- function formatDate3(iso) {
2424
- try {
2425
- return new Date(iso).toLocaleDateString("en-US", {
2426
- year: "numeric",
2427
- month: "short",
2428
- day: "numeric"
2429
- });
2430
- } catch {
2431
- return iso;
2432
- }
2433
- }
2434
- function InvoiceList({ customerId }) {
2435
- const { client } = useBillingContext();
2436
- const [invoices, setInvoices] = React.useState([]);
2437
- const [loading, setLoading] = React.useState(true);
2438
- const [error, setError] = React.useState(null);
2439
- React.useEffect(() => {
2440
- if (!client) {
2441
- setError("No client in context");
2442
- setLoading(false);
2443
- return;
2444
- }
2445
- let cancelled = false;
2446
- async function fetchInvoices() {
2447
- try {
2448
- const clientInternal = client;
2449
- const apiKey = clientInternal.apiKey ?? "";
2450
- const baseUrl = clientInternal.baseUrl ?? "https://api.nozle.app";
2451
- const timeout = clientInternal.timeout ?? 5e3;
2452
- const controller = new AbortController();
2453
- const timeoutId = setTimeout(() => controller.abort(), timeout);
2454
- const response = await fetch(
2455
- `${baseUrl}/api/v1/invoices?customer_id=${customerId}`,
2456
- {
2457
- headers: { Authorization: `Bearer ${apiKey}` },
2458
- signal: controller.signal
2459
- }
2460
- );
2461
- clearTimeout(timeoutId);
2462
- if (!response.ok) {
2463
- throw new Error(`HTTP ${response.status}`);
2464
- }
2465
- const data = await response.json();
2466
- if (!cancelled) {
2467
- setInvoices(data.invoices ?? data.data ?? []);
2468
- setLoading(false);
2469
- }
2470
- } catch (err) {
2471
- if (!cancelled) {
2472
- setError(err instanceof Error ? err.message : "Unknown error");
2473
- setLoading(false);
2474
- }
2475
- }
2476
- }
2477
- void fetchInvoices();
2478
- return () => {
2479
- cancelled = true;
2480
- };
2481
- }, [client, customerId]);
2482
- if (loading) {
2483
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-pulse space-y-2", children: [1, 2, 3].map((i) => /* @__PURE__ */ jsxRuntime.jsx(
2484
- "div",
2485
- {
2486
- className: "h-8 rounded",
2487
- style: { background: "var(--nozle-border, var(--border))" }
2488
- },
2489
- i
2490
- )) });
2491
- }
2492
- if (error) {
2493
- return /* @__PURE__ */ jsxRuntime.jsx(
2494
- "span",
2495
- {
2496
- style: { color: "var(--nozle-destructive, var(--destructive))" },
2497
- className: "text-sm",
2498
- children: "Failed to load invoices"
2499
- }
2500
- );
2501
- }
2502
- if (invoices.length === 0) {
2503
- return /* @__PURE__ */ jsxRuntime.jsx(
2504
- "p",
2505
- {
2506
- style: {
2507
- color: "var(--nozle-muted-foreground, var(--muted-foreground))",
2508
- fontSize: "0.875rem",
2509
- textAlign: "center",
2510
- padding: "1.5rem"
2511
- },
2512
- children: "No invoices yet."
2513
- }
2514
- );
2515
- }
2516
- return /* @__PURE__ */ jsxRuntime.jsxs("table", { style: { width: "100%", borderCollapse: "collapse" }, children: [
2517
- /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs(
2518
- "tr",
2519
- {
2520
- style: {
2521
- borderBottom: "1px solid var(--nozle-border, var(--border))",
2522
- fontSize: "0.75rem",
2523
- color: "var(--nozle-muted-foreground, var(--muted-foreground))"
2524
- },
2525
- children: [
2526
- /* @__PURE__ */ jsxRuntime.jsx("th", { style: { textAlign: "left", padding: "0.5rem" }, children: "#" }),
2527
- /* @__PURE__ */ jsxRuntime.jsx("th", { style: { textAlign: "left", padding: "0.5rem" }, children: "Date" }),
2528
- /* @__PURE__ */ jsxRuntime.jsx("th", { style: { textAlign: "right", padding: "0.5rem" }, children: "Amount" }),
2529
- /* @__PURE__ */ jsxRuntime.jsx("th", { style: { textAlign: "center", padding: "0.5rem" }, children: "Status" }),
2530
- /* @__PURE__ */ jsxRuntime.jsx("th", { style: { textAlign: "center", padding: "0.5rem" }, children: "PDF" })
2531
- ]
2532
- }
2533
- ) }),
2534
- /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: invoices.map((inv) => /* @__PURE__ */ jsxRuntime.jsxs(
2535
- "tr",
2536
- {
2537
- style: {
2538
- borderBottom: "1px solid var(--nozle-border, var(--border))",
2539
- fontSize: "0.875rem"
2540
- },
2541
- children: [
2542
- /* @__PURE__ */ jsxRuntime.jsx(
2543
- "td",
2544
- {
2545
- style: {
2546
- padding: "0.5rem",
2547
- color: "var(--nozle-muted-foreground, var(--muted-foreground))"
2548
- },
2549
- children: inv.number
2550
- }
2551
- ),
2552
- /* @__PURE__ */ jsxRuntime.jsx("td", { style: { padding: "0.5rem" }, children: formatDate3(inv.date) }),
2553
- /* @__PURE__ */ jsxRuntime.jsx(
2554
- "td",
2555
- {
2556
- style: {
2557
- textAlign: "right",
2558
- padding: "0.5rem",
2559
- fontVariantNumeric: "tabular-nums"
2560
- },
2561
- children: new Intl.NumberFormat("en-US", {
2562
- style: "currency",
2563
- currency: inv.currency ?? "USD"
2564
- }).format(inv.amount / 100)
2565
- }
2566
- ),
2567
- /* @__PURE__ */ jsxRuntime.jsx("td", { style: { textAlign: "center", padding: "0.5rem" }, children: /* @__PURE__ */ jsxRuntime.jsx(
2568
- "span",
2569
- {
2570
- style: {
2571
- ...STATUS_STYLES[inv.status] ?? STATUS_STYLES.draft,
2572
- padding: "0.125rem 0.5rem",
2573
- borderRadius: "9999px",
2574
- fontSize: "0.75rem",
2575
- fontWeight: 500
2576
- },
2577
- children: inv.status
2578
- }
2579
- ) }),
2580
- /* @__PURE__ */ jsxRuntime.jsx("td", { style: { textAlign: "center", padding: "0.5rem" }, children: inv.pdf_url ? /* @__PURE__ */ jsxRuntime.jsx(
2581
- "a",
2582
- {
2583
- href: inv.pdf_url,
2584
- target: "_blank",
2585
- rel: "noopener noreferrer",
2586
- "aria-label": `Download invoice ${inv.number}`,
2587
- style: {
2588
- color: "var(--nozle-primary, var(--primary))",
2589
- textDecoration: "underline",
2590
- fontSize: "0.75rem"
2591
- },
2592
- children: "Download"
2593
- }
2594
- ) : /* @__PURE__ */ jsxRuntime.jsx(
2595
- "span",
2596
- {
2597
- style: {
2598
- color: "var(--nozle-muted-foreground, var(--muted-foreground))",
2599
- fontSize: "0.75rem"
2600
- },
2601
- children: "\u2014"
2602
- }
2603
- ) })
2604
- ]
2605
- },
2606
- inv.id
2607
- )) })
2608
- ] });
2609
- }
2610
- function Section({
2611
- title,
2612
- children
2613
- }) {
2614
- return /* @__PURE__ */ jsxRuntime.jsxs(
2615
- "div",
2616
- {
2617
- style: {
2618
- padding: "1.25rem",
2619
- border: "1px solid var(--nozle-border, var(--border))",
2620
- borderRadius: "0.75rem",
2621
- background: "var(--nozle-card, var(--card, #ffffff))"
2622
- },
2623
- children: [
2624
- /* @__PURE__ */ jsxRuntime.jsx(
2625
- "h3",
2626
- {
2627
- style: {
2628
- fontSize: "0.875rem",
2629
- fontWeight: 600,
2630
- color: "var(--nozle-muted-foreground, var(--muted-foreground))",
2631
- textTransform: "uppercase",
2632
- letterSpacing: "0.05em",
2633
- marginBottom: "1rem"
2634
- },
2635
- children: title
2636
- }
2637
- ),
2638
- children
2639
- ]
2640
- }
2641
- );
2642
- }
2643
- function BillingPortal({
2644
- customerId,
2645
- usageFeatures,
2646
- usageLoading = false,
2647
- onChangePlan
2648
- }) {
2649
- return /* @__PURE__ */ jsxRuntime.jsxs(
2650
- "div",
2651
- {
2652
- style: {
2653
- display: "flex",
2654
- flexDirection: "column",
2655
- gap: "1.25rem",
2656
- fontFamily: "var(--nozle-font-sans, var(--font-sans, system-ui, sans-serif))",
2657
- color: "var(--nozle-foreground, var(--foreground))"
2658
- },
2659
- children: [
2660
- /* @__PURE__ */ jsxRuntime.jsx(Section, { title: "Current Plan", children: /* @__PURE__ */ jsxRuntime.jsx(CurrentPlan, { customerId, onChangePlan }) }),
2661
- /* @__PURE__ */ jsxRuntime.jsx(Section, { title: "Usage", children: /* @__PURE__ */ jsxRuntime.jsx(UsageDashboard, { features: usageFeatures, loading: usageLoading }) }),
2662
- /* @__PURE__ */ jsxRuntime.jsx(Section, { title: "Invoices", children: /* @__PURE__ */ jsxRuntime.jsx(InvoiceList, { customerId }) }),
2663
- /* @__PURE__ */ jsxRuntime.jsx(Section, { title: "Credits", children: /* @__PURE__ */ jsxRuntime.jsx(CreditBalance, { customerId }) })
2664
- ]
2665
- }
2666
- );
2667
- }
2668
1492
  var BRAND_LABELS = {
2669
1493
  visa: "Visa",
2670
1494
  mastercard: "Mastercard",
@@ -2768,341 +1592,12 @@ function PaymentMethodDisplay({
2768
1592
  )
2769
1593
  ] });
2770
1594
  }
2771
- var CANCEL_REASONS = [
2772
- "Too expensive",
2773
- "Missing features",
2774
- "Switching to competitor",
2775
- "Not using it enough",
2776
- "Other"
2777
- ];
2778
- function CancelSubscriptionButton({
2779
- subscriptionId,
2780
- onCancelled
2781
- }) {
2782
- const [step, setStep] = React.useState("idle");
2783
- const [selectedReason, setSelectedReason] = React.useState("");
2784
- const [otherText, setOtherText] = React.useState("");
2785
- const [loading, setLoading] = React.useState(false);
2786
- const handleDismiss = () => {
2787
- setStep("idle");
2788
- setSelectedReason("");
2789
- setOtherText("");
2790
- };
2791
- const handleConfirmCancel = () => {
2792
- setStep("reason");
2793
- };
2794
- const handleSubmitCancellation = async () => {
2795
- const reason = selectedReason === "Other" ? otherText : selectedReason;
2796
- setLoading(true);
2797
- await fetch(`/api/v1/subscriptions/${subscriptionId}`, {
2798
- method: "DELETE",
2799
- headers: { "Content-Type": "application/json" },
2800
- body: JSON.stringify({ reason })
2801
- });
2802
- setLoading(false);
2803
- handleDismiss();
2804
- onCancelled?.();
2805
- };
2806
- const isSubmitDisabled = loading || !selectedReason || selectedReason === "Other" && !otherText.trim();
2807
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2808
- /* @__PURE__ */ jsxRuntime.jsx(
2809
- "button",
2810
- {
2811
- onClick: () => setStep("confirm"),
2812
- style: {
2813
- background: "transparent",
2814
- border: "1px solid var(--nozle-destructive, var(--destructive, #dc2626))",
2815
- color: "var(--nozle-destructive, var(--destructive, #dc2626))",
2816
- padding: "0.375rem 0.875rem",
2817
- borderRadius: "0.375rem",
2818
- fontSize: "0.875rem",
2819
- fontWeight: 500,
2820
- cursor: "pointer"
2821
- },
2822
- children: "Cancel Subscription"
2823
- }
2824
- ),
2825
- step === "confirm" && /* @__PURE__ */ jsxRuntime.jsx(
2826
- "div",
2827
- {
2828
- role: "dialog",
2829
- "aria-modal": "true",
2830
- "aria-label": "Cancel subscription confirmation",
2831
- style: {
2832
- position: "fixed",
2833
- inset: 0,
2834
- display: "flex",
2835
- alignItems: "center",
2836
- justifyContent: "center",
2837
- background: "rgba(0,0,0,0.4)",
2838
- zIndex: 50
2839
- },
2840
- children: /* @__PURE__ */ jsxRuntime.jsxs(
2841
- "div",
2842
- {
2843
- style: {
2844
- background: "var(--nozle-background, var(--background, #fff))",
2845
- border: "1px solid var(--nozle-border, var(--border))",
2846
- borderRadius: "0.75rem",
2847
- padding: "1.5rem",
2848
- maxWidth: "28rem",
2849
- width: "90%",
2850
- boxShadow: "0 20px 60px rgba(0,0,0,0.15)"
2851
- },
2852
- children: [
2853
- /* @__PURE__ */ jsxRuntime.jsx(
2854
- "h2",
2855
- {
2856
- style: {
2857
- fontSize: "1.125rem",
2858
- fontWeight: 600,
2859
- marginBottom: "0.75rem"
2860
- },
2861
- children: "Cancel Subscription?"
2862
- }
2863
- ),
2864
- /* @__PURE__ */ jsxRuntime.jsx(
2865
- "p",
2866
- {
2867
- style: {
2868
- color: "var(--nozle-muted-foreground, var(--muted-foreground))",
2869
- fontSize: "0.875rem",
2870
- marginBottom: "1.5rem"
2871
- },
2872
- children: "Your access will end at the end of the billing period."
2873
- }
2874
- ),
2875
- /* @__PURE__ */ jsxRuntime.jsxs(
2876
- "div",
2877
- {
2878
- style: {
2879
- display: "flex",
2880
- gap: "0.75rem",
2881
- justifyContent: "flex-end"
2882
- },
2883
- children: [
2884
- /* @__PURE__ */ jsxRuntime.jsx(
2885
- "button",
2886
- {
2887
- onClick: handleDismiss,
2888
- style: {
2889
- background: "transparent",
2890
- border: "1px solid var(--nozle-border, var(--border))",
2891
- color: "var(--nozle-foreground, var(--foreground))",
2892
- padding: "0.375rem 0.875rem",
2893
- borderRadius: "0.375rem",
2894
- fontSize: "0.875rem",
2895
- cursor: "pointer"
2896
- },
2897
- children: "Keep Subscription"
2898
- }
2899
- ),
2900
- /* @__PURE__ */ jsxRuntime.jsx(
2901
- "button",
2902
- {
2903
- onClick: handleConfirmCancel,
2904
- style: {
2905
- background: "var(--nozle-destructive, var(--destructive, #dc2626))",
2906
- border: "1px solid transparent",
2907
- color: "#fff",
2908
- padding: "0.375rem 0.875rem",
2909
- borderRadius: "0.375rem",
2910
- fontSize: "0.875rem",
2911
- fontWeight: 500,
2912
- cursor: "pointer"
2913
- },
2914
- children: "Confirm Cancel"
2915
- }
2916
- )
2917
- ]
2918
- }
2919
- )
2920
- ]
2921
- }
2922
- )
2923
- }
2924
- ),
2925
- step === "reason" && /* @__PURE__ */ jsxRuntime.jsx(
2926
- "div",
2927
- {
2928
- role: "dialog",
2929
- "aria-modal": "true",
2930
- "aria-label": "Cancellation reason survey",
2931
- style: {
2932
- position: "fixed",
2933
- inset: 0,
2934
- display: "flex",
2935
- alignItems: "center",
2936
- justifyContent: "center",
2937
- background: "rgba(0,0,0,0.4)",
2938
- zIndex: 50
2939
- },
2940
- children: /* @__PURE__ */ jsxRuntime.jsxs(
2941
- "div",
2942
- {
2943
- style: {
2944
- background: "var(--nozle-background, var(--background, #fff))",
2945
- border: "1px solid var(--nozle-border, var(--border))",
2946
- borderRadius: "0.75rem",
2947
- padding: "1.5rem",
2948
- maxWidth: "28rem",
2949
- width: "90%",
2950
- boxShadow: "0 20px 60px rgba(0,0,0,0.15)"
2951
- },
2952
- children: [
2953
- /* @__PURE__ */ jsxRuntime.jsx(
2954
- "h2",
2955
- {
2956
- style: {
2957
- fontSize: "1.125rem",
2958
- fontWeight: 600,
2959
- marginBottom: "0.5rem"
2960
- },
2961
- children: "Why are you cancelling?"
2962
- }
2963
- ),
2964
- /* @__PURE__ */ jsxRuntime.jsx(
2965
- "p",
2966
- {
2967
- style: {
2968
- color: "var(--nozle-muted-foreground, var(--muted-foreground))",
2969
- fontSize: "0.875rem",
2970
- marginBottom: "1rem"
2971
- },
2972
- children: "Your feedback helps us improve."
2973
- }
2974
- ),
2975
- /* @__PURE__ */ jsxRuntime.jsxs(
2976
- "fieldset",
2977
- {
2978
- style: {
2979
- border: "none",
2980
- padding: 0,
2981
- margin: 0,
2982
- marginBottom: "1rem"
2983
- },
2984
- children: [
2985
- /* @__PURE__ */ jsxRuntime.jsx("legend", { className: "sr-only", children: "Cancellation reason" }),
2986
- CANCEL_REASONS.map((r) => /* @__PURE__ */ jsxRuntime.jsxs(
2987
- "label",
2988
- {
2989
- style: {
2990
- display: "flex",
2991
- alignItems: "center",
2992
- gap: "0.5rem",
2993
- padding: "0.375rem 0",
2994
- fontSize: "0.875rem",
2995
- cursor: "pointer"
2996
- },
2997
- children: [
2998
- /* @__PURE__ */ jsxRuntime.jsx(
2999
- "input",
3000
- {
3001
- type: "radio",
3002
- name: "cancel-reason",
3003
- value: r,
3004
- checked: selectedReason === r,
3005
- onChange: () => setSelectedReason(r),
3006
- "aria-label": r
3007
- }
3008
- ),
3009
- r
3010
- ]
3011
- },
3012
- r
3013
- ))
3014
- ]
3015
- }
3016
- ),
3017
- selectedReason === "Other" && /* @__PURE__ */ jsxRuntime.jsx(
3018
- "input",
3019
- {
3020
- type: "text",
3021
- placeholder: "Please describe your reason",
3022
- value: otherText,
3023
- onChange: (e) => setOtherText(e.target.value),
3024
- "aria-label": "Other reason",
3025
- style: {
3026
- width: "100%",
3027
- border: "1px solid var(--nozle-border, var(--border))",
3028
- borderRadius: "0.375rem",
3029
- padding: "0.5rem 0.75rem",
3030
- fontSize: "0.875rem",
3031
- marginBottom: "1rem",
3032
- background: "var(--nozle-background, var(--background, #fff))",
3033
- color: "var(--nozle-foreground, var(--foreground))",
3034
- boxSizing: "border-box"
3035
- }
3036
- }
3037
- ),
3038
- /* @__PURE__ */ jsxRuntime.jsxs(
3039
- "div",
3040
- {
3041
- style: {
3042
- display: "flex",
3043
- gap: "0.75rem",
3044
- justifyContent: "flex-end"
3045
- },
3046
- children: [
3047
- /* @__PURE__ */ jsxRuntime.jsx(
3048
- "button",
3049
- {
3050
- onClick: handleDismiss,
3051
- style: {
3052
- background: "transparent",
3053
- border: "1px solid var(--nozle-border, var(--border))",
3054
- color: "var(--nozle-foreground, var(--foreground))",
3055
- padding: "0.375rem 0.875rem",
3056
- borderRadius: "0.375rem",
3057
- fontSize: "0.875rem",
3058
- cursor: "pointer"
3059
- },
3060
- children: "Keep Subscription"
3061
- }
3062
- ),
3063
- /* @__PURE__ */ jsxRuntime.jsx(
3064
- "button",
3065
- {
3066
- onClick: handleSubmitCancellation,
3067
- disabled: isSubmitDisabled,
3068
- style: {
3069
- background: isSubmitDisabled ? "var(--nozle-muted, var(--muted, #f1f5f9))" : "var(--nozle-destructive, var(--destructive, #dc2626))",
3070
- border: "1px solid transparent",
3071
- color: isSubmitDisabled ? "var(--nozle-muted-foreground, var(--muted-foreground))" : "#fff",
3072
- padding: "0.375rem 0.875rem",
3073
- borderRadius: "0.375rem",
3074
- fontSize: "0.875rem",
3075
- fontWeight: 500,
3076
- cursor: isSubmitDisabled ? "not-allowed" : "pointer"
3077
- },
3078
- children: loading ? "Cancelling..." : "Submit Cancellation"
3079
- }
3080
- )
3081
- ]
3082
- }
3083
- )
3084
- ]
3085
- }
3086
- )
3087
- }
3088
- )
3089
- ] });
3090
- }
3091
1595
 
3092
1596
  exports.BillingContext = BillingContext;
3093
- exports.BillingPortal = BillingPortal;
3094
- exports.BillingPortalProvider = BillingPortalProvider;
3095
1597
  exports.BillingProvider = BillingProvider;
3096
- exports.CANCEL_REASONS = CANCEL_REASONS;
3097
- exports.CancelSubscriptionButton = CancelSubscriptionButton;
3098
1598
  exports.Checkout = Checkout;
3099
1599
  exports.CheckoutButton = CheckoutButton;
3100
- exports.CreditBalance = CreditBalance;
3101
- exports.CreditHistory = CreditHistory;
3102
- exports.CreditTopUpButton = CreditTopUpButton;
3103
- exports.CurrentPlan = CurrentPlan;
3104
1600
  exports.FeatureGate = FeatureGate;
3105
- exports.InvoiceList = InvoiceList;
3106
1601
  exports.LockedOverlay = LockedOverlay;
3107
1602
  exports.PaymentMethodDisplay = PaymentMethodDisplay;
3108
1603
  exports.PlanBadge = PlanBadge;
@@ -3119,11 +1614,10 @@ exports.UsageGate = UsageGate;
3119
1614
  exports.UsageMeter = UsageMeter;
3120
1615
  exports.getUsageMeterColor = getUsageMeterColor;
3121
1616
  exports.useBillingContext = useBillingContext;
3122
- exports.useBillingPortal = useBillingPortal;
3123
- exports.useCan = useCan;
3124
- exports.useCheckout = useCheckout;
3125
- exports.useCredits = useCredits;
1617
+ exports.useCheckout = useCheckout2;
1618
+ exports.useCheckoutSession = useCheckout;
3126
1619
  exports.useNozleClient = useNozleClient;
3127
- exports.useUsage = useUsage;
1620
+ exports.useOptionalBillingContext = useOptionalBillingContext;
1621
+ exports.usePlans = usePlans;
3128
1622
  //# sourceMappingURL=index.cjs.map
3129
1623
  //# sourceMappingURL=index.cjs.map