@nozle-js/react 0.3.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.js ADDED
@@ -0,0 +1,997 @@
1
+ import { createContext, useRef, useMemo, useEffect, useContext, useSyncExternalStore, useState, useCallback } from 'react';
2
+ import { loadStripe } from '@stripe/stripe-js';
3
+ import { Centrifuge } from 'centrifuge';
4
+ import { jsx, jsxs } from 'react/jsx-runtime';
5
+
6
+ // src/provider.tsx
7
+
8
+ // src/store.ts
9
+ var BillingStore = class {
10
+ baseUrl;
11
+ apiKey;
12
+ customerId;
13
+ state = {
14
+ entitlements: null,
15
+ credits: null,
16
+ usage: {},
17
+ connectionState: "connecting",
18
+ error: null
19
+ };
20
+ listeners = /* @__PURE__ */ new Set();
21
+ constructor(baseUrl, apiKey, customerId) {
22
+ this.baseUrl = baseUrl;
23
+ this.apiKey = apiKey;
24
+ this.customerId = customerId;
25
+ }
26
+ getSnapshot = () => this.state;
27
+ getServerSnapshot = () => this.state;
28
+ subscribe = (listener) => {
29
+ this.listeners.add(listener);
30
+ return () => {
31
+ this.listeners.delete(listener);
32
+ };
33
+ };
34
+ emit() {
35
+ for (const listener of this.listeners) {
36
+ listener();
37
+ }
38
+ }
39
+ setConnectionState(state) {
40
+ this.state = { ...this.state, connectionState: state };
41
+ this.emit();
42
+ }
43
+ setError(error) {
44
+ this.state = { ...this.state, error };
45
+ this.emit();
46
+ }
47
+ updateUsage(metric, used, limit, remaining) {
48
+ this.state = {
49
+ ...this.state,
50
+ usage: {
51
+ ...this.state.usage,
52
+ [metric]: { used, limit, remaining }
53
+ }
54
+ };
55
+ this.emit();
56
+ }
57
+ async fetchUsage(feature) {
58
+ try {
59
+ const res = await fetch(
60
+ `${this.baseUrl}/v1/can?customer_id=${this.customerId}&feature=${feature}`,
61
+ { headers: { Authorization: `Bearer ${this.apiKey}` } }
62
+ );
63
+ if (!res.ok) return;
64
+ const data = await res.json();
65
+ this.updateUsage(feature, data.used, data.limit, data.remaining);
66
+ this.state = {
67
+ ...this.state,
68
+ entitlements: {
69
+ plan_slug: "pro",
70
+ subscription_status: "active",
71
+ features: { [feature]: { enabled: data.allowed } },
72
+ limits: { [feature]: { limit: data.limit, used: data.used, source: "entitlement" } }
73
+ }
74
+ };
75
+ this.emit();
76
+ } catch (err) {
77
+ this.state = {
78
+ ...this.state,
79
+ error: err instanceof Error ? err : new Error(String(err))
80
+ };
81
+ this.emit();
82
+ }
83
+ }
84
+ async fetchCheckoutSecret(planCode, successUrl) {
85
+ const res = await fetch(`${this.baseUrl}/v1/checkout`, {
86
+ method: "POST",
87
+ headers: {
88
+ Authorization: `Bearer ${this.apiKey}`,
89
+ "Content-Type": "application/json"
90
+ },
91
+ body: JSON.stringify({
92
+ plan_code: planCode,
93
+ customer_id: this.customerId,
94
+ success_url: successUrl || `${window.location.origin}/checkout/complete?session_id={CHECKOUT_SESSION_ID}`
95
+ })
96
+ });
97
+ if (!res.ok) throw new Error("Failed to create checkout session");
98
+ const data = await res.json();
99
+ return data.client_secret;
100
+ }
101
+ async fetchPlans() {
102
+ const res = await fetch(`${this.baseUrl}/v1/plans`, {
103
+ headers: { Authorization: `Bearer ${this.apiKey}` }
104
+ });
105
+ if (!res.ok) return [];
106
+ const data = await res.json();
107
+ return data.plans || [];
108
+ }
109
+ handleEvent(event) {
110
+ if (event.type === "usage.updated") {
111
+ const metric = event.metric;
112
+ const used = event.used;
113
+ const limit = event.limit;
114
+ const remaining = event.remaining;
115
+ this.updateUsage(metric, used, limit, remaining);
116
+ }
117
+ }
118
+ };
119
+ function connectCentrifugo(store, customerId, wsUrl) {
120
+ const centrifuge = new Centrifuge(wsUrl, {});
121
+ const sub = centrifuge.newSubscription(
122
+ `customer:${customerId}`
123
+ );
124
+ sub.on("publication", (ctx) => {
125
+ store.handleEvent(
126
+ ctx.data
127
+ );
128
+ });
129
+ sub.on("subscribed", () => {
130
+ store.setConnectionState("connected");
131
+ });
132
+ centrifuge.on("connected", () => {
133
+ store.setConnectionState("connected");
134
+ });
135
+ centrifuge.on("disconnected", () => {
136
+ store.setConnectionState("disconnected");
137
+ });
138
+ sub.subscribe();
139
+ centrifuge.connect();
140
+ store.setConnectionState("connecting");
141
+ return {
142
+ disconnect: () => {
143
+ sub.unsubscribe();
144
+ centrifuge.disconnect();
145
+ }
146
+ };
147
+ }
148
+ var BillingContext = createContext(null);
149
+ function BillingProvider({
150
+ apiKey,
151
+ customerId,
152
+ baseUrl = "http://localhost:8080",
153
+ wsUrl = "ws://localhost:8001/connection/websocket",
154
+ stripeKey,
155
+ features = [],
156
+ children
157
+ }) {
158
+ const storeRef = useRef(null);
159
+ if (!storeRef.current) {
160
+ storeRef.current = new BillingStore(baseUrl, apiKey, customerId);
161
+ }
162
+ const stripePromise = useMemo(
163
+ () => stripeKey ? loadStripe(stripeKey) : null,
164
+ [stripeKey]
165
+ );
166
+ useEffect(() => {
167
+ const store = storeRef.current;
168
+ for (const feature of features) {
169
+ store.fetchUsage(feature);
170
+ }
171
+ const connection = connectCentrifugo(
172
+ store,
173
+ customerId,
174
+ wsUrl
175
+ );
176
+ return () => {
177
+ connection.disconnect();
178
+ };
179
+ }, [apiKey, customerId, baseUrl, wsUrl, features]);
180
+ return /* @__PURE__ */ jsx(BillingContext.Provider, { value: { store: storeRef.current, stripePromise }, children });
181
+ }
182
+ function useBillingContext() {
183
+ const ctx = useContext(BillingContext);
184
+ if (!ctx) {
185
+ throw new Error("useBillingContext must be used within a BillingProvider");
186
+ }
187
+ return ctx;
188
+ }
189
+ function useCan(feature) {
190
+ const { store } = useBillingContext();
191
+ const state = useSyncExternalStore(
192
+ store.subscribe,
193
+ store.getSnapshot,
194
+ store.getServerSnapshot
195
+ );
196
+ if (state.error) {
197
+ return { allowed: false, isLoading: false, error: state.error };
198
+ }
199
+ if (state.entitlements === null) {
200
+ return { allowed: false, isLoading: true, error: null };
201
+ }
202
+ const featureState = state.entitlements.features[feature];
203
+ return {
204
+ allowed: featureState?.enabled ?? false,
205
+ isLoading: false,
206
+ error: null
207
+ };
208
+ }
209
+ function useUsage(metric) {
210
+ const { store } = useBillingContext();
211
+ const state = useSyncExternalStore(
212
+ store.subscribe,
213
+ store.getSnapshot,
214
+ store.getServerSnapshot
215
+ );
216
+ if (state.error) {
217
+ return { data: null, isLoading: false, error: state.error };
218
+ }
219
+ if (state.entitlements === null) {
220
+ return { data: null, isLoading: true, error: null };
221
+ }
222
+ const usage = state.usage[metric] ?? null;
223
+ return { data: usage, isLoading: false, error: null };
224
+ }
225
+ function usePlan() {
226
+ const { store } = useBillingContext();
227
+ const state = useSyncExternalStore(
228
+ store.subscribe,
229
+ store.getSnapshot,
230
+ store.getServerSnapshot
231
+ );
232
+ if (state.error) {
233
+ return { data: null, isLoading: false, error: state.error };
234
+ }
235
+ if (state.entitlements === null) {
236
+ return { data: null, isLoading: true, error: null };
237
+ }
238
+ return {
239
+ data: {
240
+ plan_slug: state.entitlements.plan_slug,
241
+ subscription_status: state.entitlements.subscription_status
242
+ },
243
+ isLoading: false,
244
+ error: null
245
+ };
246
+ }
247
+ function useCheckout() {
248
+ const { store, stripePromise } = useBillingContext();
249
+ const [isLoading, setIsLoading] = useState(false);
250
+ const [error, setError] = useState(null);
251
+ const fetchClientSecret = useCallback(
252
+ async (planCode, successUrl) => {
253
+ setIsLoading(true);
254
+ setError(null);
255
+ try {
256
+ return await store.fetchCheckoutSecret(planCode, successUrl);
257
+ } catch (err) {
258
+ const e = err instanceof Error ? err : new Error(String(err));
259
+ setError(e);
260
+ throw e;
261
+ } finally {
262
+ setIsLoading(false);
263
+ }
264
+ },
265
+ [store]
266
+ );
267
+ return { fetchClientSecret, stripePromise, isLoading, error };
268
+ }
269
+ function useSubscribe() {
270
+ const { store } = useBillingContext();
271
+ const [isLoading, setIsLoading] = useState(false);
272
+ const [error, setError] = useState(null);
273
+ const subscribe = useCallback(
274
+ async (planCode) => {
275
+ setIsLoading(true);
276
+ setError(null);
277
+ try {
278
+ const res = await fetch(`${store.baseUrl}/v1/subscribe`, {
279
+ method: "POST",
280
+ headers: {
281
+ Authorization: `Bearer ${store.apiKey}`,
282
+ "Content-Type": "application/json"
283
+ },
284
+ body: JSON.stringify({
285
+ plan_code: planCode,
286
+ customer_id: store.customerId
287
+ })
288
+ });
289
+ if (!res.ok) throw new Error("Failed to create subscription");
290
+ return await res.json();
291
+ } catch (err) {
292
+ const e = err instanceof Error ? err : new Error(String(err));
293
+ setError(e);
294
+ throw e;
295
+ } finally {
296
+ setIsLoading(false);
297
+ }
298
+ },
299
+ [store]
300
+ );
301
+ return { subscribe, isLoading, error };
302
+ }
303
+ function usePlans() {
304
+ const { store } = useBillingContext();
305
+ const [plans, setPlans] = useState([]);
306
+ const [isLoading, setIsLoading] = useState(true);
307
+ useEffect(() => {
308
+ store.fetchPlans().then(setPlans).finally(() => setIsLoading(false));
309
+ }, [store]);
310
+ return { plans, isLoading };
311
+ }
312
+ var fontFamily = 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
313
+ var levelColors = {
314
+ normal: {
315
+ fg: "#6366f1",
316
+ bg: "rgba(99, 102, 241, 0.08)",
317
+ trackBg: "#e5e7eb"
318
+ },
319
+ warning: {
320
+ fg: "#f59e0b",
321
+ bg: "rgba(245, 158, 11, 0.08)",
322
+ trackBg: "#e5e7eb"
323
+ },
324
+ critical: {
325
+ fg: "#ef4444",
326
+ bg: "rgba(239, 68, 68, 0.08)",
327
+ trackBg: "#e5e7eb"
328
+ }
329
+ };
330
+ var skeletonStyleInjected = false;
331
+ function ensureKeyframes() {
332
+ if (skeletonStyleInjected || typeof document === "undefined") return;
333
+ const sheet = document.createElement("style");
334
+ sheet.textContent = `@keyframes nozle-pulse{0%,100%{opacity:1}50%{opacity:.4}}`;
335
+ document.head.appendChild(sheet);
336
+ skeletonStyleInjected = true;
337
+ }
338
+ function UsageMeter({
339
+ metric,
340
+ variant = "bar",
341
+ className,
342
+ style,
343
+ ...props
344
+ }) {
345
+ const { data, isLoading, error } = useUsage(metric);
346
+ if (isLoading) {
347
+ ensureKeyframes();
348
+ if (variant === "minimal") {
349
+ return /* @__PURE__ */ jsx(
350
+ "span",
351
+ {
352
+ "data-nozle": "usage-meter",
353
+ "data-variant": "minimal",
354
+ className,
355
+ style: {
356
+ display: "inline-block",
357
+ width: 80,
358
+ height: 16,
359
+ borderRadius: 4,
360
+ background: "#e5e7eb",
361
+ animation: "nozle-pulse 1.5s ease-in-out infinite",
362
+ ...style
363
+ },
364
+ ...props
365
+ }
366
+ );
367
+ }
368
+ if (variant === "ring") {
369
+ return /* @__PURE__ */ jsx(
370
+ "div",
371
+ {
372
+ "data-nozle": "usage-meter",
373
+ "data-variant": "ring",
374
+ className,
375
+ style: {
376
+ width: 96,
377
+ height: 96,
378
+ borderRadius: "50%",
379
+ background: "#e5e7eb",
380
+ animation: "nozle-pulse 1.5s ease-in-out infinite",
381
+ ...style
382
+ },
383
+ ...props
384
+ }
385
+ );
386
+ }
387
+ return /* @__PURE__ */ jsx(
388
+ "div",
389
+ {
390
+ "data-nozle": "usage-meter",
391
+ "data-variant": "bar",
392
+ className,
393
+ style: {
394
+ maxWidth: 360,
395
+ fontFamily,
396
+ ...style
397
+ },
398
+ ...props,
399
+ children: /* @__PURE__ */ jsx(
400
+ "div",
401
+ {
402
+ style: {
403
+ height: 8,
404
+ borderRadius: 4,
405
+ background: "#e5e7eb",
406
+ animation: "nozle-pulse 1.5s ease-in-out infinite"
407
+ }
408
+ }
409
+ )
410
+ }
411
+ );
412
+ }
413
+ if (error || !data) return null;
414
+ const percent = data.limit > 0 ? Math.round(data.used / data.limit * 100) : 0;
415
+ const level = percent >= 90 ? "critical" : percent >= 75 ? "warning" : "normal";
416
+ const colors = levelColors[level];
417
+ if (variant === "minimal") {
418
+ const minimalStyle = {
419
+ fontFamily,
420
+ fontSize: 13,
421
+ fontWeight: 500,
422
+ color: colors.fg,
423
+ background: colors.bg,
424
+ padding: "3px 8px",
425
+ borderRadius: 6,
426
+ whiteSpace: "nowrap",
427
+ ...style
428
+ };
429
+ return /* @__PURE__ */ jsxs(
430
+ "span",
431
+ {
432
+ "data-nozle": "usage-meter",
433
+ "data-variant": "minimal",
434
+ "data-level": level,
435
+ className,
436
+ style: minimalStyle,
437
+ ...props,
438
+ children: [
439
+ data.used.toLocaleString(),
440
+ "/",
441
+ data.limit.toLocaleString()
442
+ ]
443
+ }
444
+ );
445
+ }
446
+ if (variant === "ring") {
447
+ const size = 96;
448
+ const strokeWidth = 7;
449
+ const radius = (size - strokeWidth) / 2;
450
+ const circumference = 2 * Math.PI * radius;
451
+ const offset = circumference - percent / 100 * circumference;
452
+ const containerStyle = {
453
+ position: "relative",
454
+ display: "inline-flex",
455
+ alignItems: "center",
456
+ justifyContent: "center",
457
+ width: size,
458
+ height: size,
459
+ fontFamily,
460
+ ...style
461
+ };
462
+ const labelStyle2 = {
463
+ position: "absolute",
464
+ inset: 0,
465
+ display: "flex",
466
+ flexDirection: "column",
467
+ alignItems: "center",
468
+ justifyContent: "center",
469
+ fontWeight: 700,
470
+ fontSize: 20,
471
+ lineHeight: 1,
472
+ color: colors.fg
473
+ };
474
+ return /* @__PURE__ */ jsxs(
475
+ "div",
476
+ {
477
+ "data-nozle": "usage-meter",
478
+ "data-variant": "ring",
479
+ "data-level": level,
480
+ className,
481
+ style: containerStyle,
482
+ ...props,
483
+ children: [
484
+ /* @__PURE__ */ jsxs(
485
+ "svg",
486
+ {
487
+ viewBox: `0 0 ${size} ${size}`,
488
+ width: size,
489
+ height: size,
490
+ style: { display: "block", transform: "rotate(-90deg)" },
491
+ children: [
492
+ /* @__PURE__ */ jsx(
493
+ "circle",
494
+ {
495
+ cx: size / 2,
496
+ cy: size / 2,
497
+ r: radius,
498
+ fill: "none",
499
+ stroke: colors.trackBg,
500
+ strokeWidth
501
+ }
502
+ ),
503
+ /* @__PURE__ */ jsx(
504
+ "circle",
505
+ {
506
+ cx: size / 2,
507
+ cy: size / 2,
508
+ r: radius,
509
+ fill: "none",
510
+ stroke: colors.fg,
511
+ strokeWidth,
512
+ strokeDasharray: circumference,
513
+ strokeDashoffset: offset,
514
+ strokeLinecap: "round",
515
+ style: { transition: "stroke-dashoffset 0.4s ease" }
516
+ }
517
+ )
518
+ ]
519
+ }
520
+ ),
521
+ /* @__PURE__ */ jsxs("span", { "data-nozle": "usage-meter-label", style: labelStyle2, children: [
522
+ /* @__PURE__ */ jsxs("span", { children: [
523
+ percent,
524
+ "%"
525
+ ] }),
526
+ /* @__PURE__ */ jsx(
527
+ "span",
528
+ {
529
+ style: {
530
+ fontSize: 10,
531
+ fontWeight: 400,
532
+ color: "#6b7280",
533
+ marginTop: 2
534
+ },
535
+ children: "used"
536
+ }
537
+ )
538
+ ] })
539
+ ]
540
+ }
541
+ );
542
+ }
543
+ const cardStyle = {
544
+ fontFamily,
545
+ maxWidth: 360,
546
+ background: "#ffffff",
547
+ border: "1px solid #e5e7eb",
548
+ borderRadius: 12,
549
+ padding: "16px 20px",
550
+ boxShadow: "0 1px 3px rgba(0,0,0,0.04), 0 1px 2px rgba(0,0,0,0.02)",
551
+ ...style
552
+ };
553
+ const headerStyle = {
554
+ display: "flex",
555
+ justifyContent: "space-between",
556
+ alignItems: "baseline",
557
+ marginBottom: 10
558
+ };
559
+ const labelStyle = {
560
+ fontSize: 13,
561
+ fontWeight: 500,
562
+ color: "#374151"
563
+ };
564
+ const valueStyle = {
565
+ fontSize: 13,
566
+ fontWeight: 600,
567
+ color: colors.fg
568
+ };
569
+ const trackStyle = {
570
+ height: 8,
571
+ borderRadius: 4,
572
+ background: colors.trackBg,
573
+ overflow: "hidden"
574
+ };
575
+ const fillStyle = {
576
+ height: "100%",
577
+ borderRadius: 4,
578
+ background: colors.fg,
579
+ width: `${Math.min(percent, 100)}%`,
580
+ transition: "width 0.4s ease"
581
+ };
582
+ return /* @__PURE__ */ jsxs(
583
+ "div",
584
+ {
585
+ "data-nozle": "usage-meter",
586
+ "data-variant": "bar",
587
+ "data-level": level,
588
+ className,
589
+ style: cardStyle,
590
+ ...props,
591
+ children: [
592
+ /* @__PURE__ */ jsxs("div", { style: headerStyle, children: [
593
+ /* @__PURE__ */ jsxs("span", { "data-nozle": "usage-meter-label", style: labelStyle, children: [
594
+ data.used.toLocaleString(),
595
+ " / ",
596
+ data.limit.toLocaleString()
597
+ ] }),
598
+ /* @__PURE__ */ jsxs("span", { style: valueStyle, children: [
599
+ percent,
600
+ "%"
601
+ ] })
602
+ ] }),
603
+ /* @__PURE__ */ jsx("div", { "data-nozle": "usage-meter-track", style: trackStyle, children: /* @__PURE__ */ jsx("div", { "data-nozle": "usage-meter-fill", style: fillStyle }) })
604
+ ]
605
+ }
606
+ );
607
+ }
608
+ var fontFamily2 = 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
609
+ var statusColorMap = {
610
+ active: { dot: "#22c55e", bg: "rgba(34, 197, 94, 0.08)", text: "#15803d" },
611
+ trialing: { dot: "#6366f1", bg: "rgba(99, 102, 241, 0.08)", text: "#4f46e5" },
612
+ past_due: { dot: "#f59e0b", bg: "rgba(245, 158, 11, 0.08)", text: "#b45309" },
613
+ canceled: { dot: "#6b7280", bg: "rgba(107, 114, 128, 0.08)", text: "#4b5563" },
614
+ unpaid: { dot: "#ef4444", bg: "rgba(239, 68, 68, 0.08)", text: "#dc2626" },
615
+ incomplete: { dot: "#f59e0b", bg: "rgba(245, 158, 11, 0.08)", text: "#b45309" }
616
+ };
617
+ var defaultStatusColors = { dot: "#6366f1", bg: "rgba(99, 102, 241, 0.08)", text: "#4f46e5" };
618
+ function getStatusColors(status) {
619
+ return statusColorMap[status] ?? defaultStatusColors;
620
+ }
621
+ var skeletonStyleInjected2 = false;
622
+ function ensureKeyframes2() {
623
+ if (skeletonStyleInjected2 || typeof document === "undefined") return;
624
+ const sheet = document.createElement("style");
625
+ sheet.textContent = `@keyframes nozle-pulse{0%,100%{opacity:1}50%{opacity:.4}}`;
626
+ document.head.appendChild(sheet);
627
+ skeletonStyleInjected2 = true;
628
+ }
629
+ function PlanBadge({
630
+ variant = "pill",
631
+ className,
632
+ style,
633
+ ...props
634
+ }) {
635
+ const { data, isLoading, error } = usePlan();
636
+ if (isLoading) {
637
+ ensureKeyframes2();
638
+ if (variant === "icon") {
639
+ return /* @__PURE__ */ jsx(
640
+ "span",
641
+ {
642
+ "data-nozle": "plan-badge",
643
+ "data-variant": "icon",
644
+ className,
645
+ style: {
646
+ display: "inline-block",
647
+ width: 32,
648
+ height: 32,
649
+ borderRadius: "50%",
650
+ background: "#e5e7eb",
651
+ animation: "nozle-pulse 1.5s ease-in-out infinite",
652
+ ...style
653
+ },
654
+ ...props
655
+ }
656
+ );
657
+ }
658
+ return /* @__PURE__ */ jsx(
659
+ "span",
660
+ {
661
+ "data-nozle": "plan-badge",
662
+ "data-variant": variant,
663
+ className,
664
+ style: {
665
+ display: "inline-block",
666
+ width: 72,
667
+ height: 22,
668
+ borderRadius: variant === "pill" ? 999 : 4,
669
+ background: "#e5e7eb",
670
+ animation: "nozle-pulse 1.5s ease-in-out infinite",
671
+ ...style
672
+ },
673
+ ...props
674
+ }
675
+ );
676
+ }
677
+ if (error || !data) return null;
678
+ const statusLabel = data.subscription_status.replace(/_/g, " ");
679
+ const colors = getStatusColors(data.subscription_status);
680
+ const planName = data.plan_slug;
681
+ if (variant === "text") {
682
+ const textStyle = {
683
+ fontFamily: fontFamily2,
684
+ display: "inline-flex",
685
+ alignItems: "center",
686
+ gap: 6,
687
+ fontSize: 14,
688
+ fontWeight: 500,
689
+ color: "#111827",
690
+ ...style
691
+ };
692
+ const dotStyle2 = {
693
+ width: 8,
694
+ height: 8,
695
+ borderRadius: "50%",
696
+ background: colors.dot,
697
+ flexShrink: 0
698
+ };
699
+ return /* @__PURE__ */ jsxs(
700
+ "span",
701
+ {
702
+ "data-nozle": "plan-badge",
703
+ "data-variant": "text",
704
+ "data-status": data.subscription_status,
705
+ className,
706
+ style: textStyle,
707
+ ...props,
708
+ children: [
709
+ /* @__PURE__ */ jsx("span", { style: dotStyle2, "aria-hidden": "true" }),
710
+ planName
711
+ ]
712
+ }
713
+ );
714
+ }
715
+ if (variant === "icon") {
716
+ const iconStyle = {
717
+ fontFamily: fontFamily2,
718
+ display: "inline-flex",
719
+ alignItems: "center",
720
+ justifyContent: "center",
721
+ width: 32,
722
+ height: 32,
723
+ borderRadius: "50%",
724
+ background: colors.bg,
725
+ color: colors.text,
726
+ fontSize: 14,
727
+ fontWeight: 700,
728
+ lineHeight: 1,
729
+ flexShrink: 0,
730
+ ...style
731
+ };
732
+ return /* @__PURE__ */ jsx(
733
+ "span",
734
+ {
735
+ "data-nozle": "plan-badge",
736
+ "data-variant": "icon",
737
+ "data-status": data.subscription_status,
738
+ className,
739
+ style: iconStyle,
740
+ title: `${planName} (${statusLabel})`,
741
+ ...props,
742
+ children: planName.charAt(0).toUpperCase()
743
+ }
744
+ );
745
+ }
746
+ const pillStyle = {
747
+ fontFamily: fontFamily2,
748
+ display: "inline-flex",
749
+ alignItems: "center",
750
+ gap: 6,
751
+ fontSize: 12,
752
+ fontWeight: 600,
753
+ lineHeight: 1.4,
754
+ color: colors.text,
755
+ background: colors.bg,
756
+ padding: "4px 12px",
757
+ borderRadius: 999,
758
+ whiteSpace: "nowrap",
759
+ ...style
760
+ };
761
+ const dotStyle = {
762
+ width: 6,
763
+ height: 6,
764
+ borderRadius: "50%",
765
+ background: colors.dot,
766
+ flexShrink: 0
767
+ };
768
+ return /* @__PURE__ */ jsxs(
769
+ "span",
770
+ {
771
+ "data-nozle": "plan-badge",
772
+ "data-variant": "pill",
773
+ "data-status": data.subscription_status,
774
+ className,
775
+ style: pillStyle,
776
+ ...props,
777
+ children: [
778
+ /* @__PURE__ */ jsx("span", { style: dotStyle, "aria-hidden": "true" }),
779
+ planName
780
+ ]
781
+ }
782
+ );
783
+ }
784
+ var fontFamily3 = 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
785
+ var primaryColor = "#6366f1";
786
+ var primaryHover = "#4f46e5";
787
+ function UpgradePrompt({
788
+ feature,
789
+ variant = "card",
790
+ upgradeUrl = "/upgrade",
791
+ message = "Upgrade your plan to unlock this feature",
792
+ className,
793
+ style,
794
+ ...props
795
+ }) {
796
+ const { allowed, isLoading } = useCan(feature ?? "");
797
+ if (isLoading || allowed || !feature) return null;
798
+ if (variant === "inline") {
799
+ const inlineStyle = {
800
+ fontFamily: fontFamily3,
801
+ fontSize: 13,
802
+ color: "#6b7280",
803
+ ...style
804
+ };
805
+ const linkStyle = {
806
+ color: primaryColor,
807
+ fontWeight: 600,
808
+ textDecoration: "none",
809
+ marginLeft: 4
810
+ };
811
+ return /* @__PURE__ */ jsxs(
812
+ "span",
813
+ {
814
+ "data-nozle": "upgrade-prompt",
815
+ "data-variant": "inline",
816
+ className,
817
+ style: inlineStyle,
818
+ ...props,
819
+ children: [
820
+ message,
821
+ /* @__PURE__ */ jsx(
822
+ "a",
823
+ {
824
+ href: upgradeUrl,
825
+ "data-nozle": "upgrade-prompt-link",
826
+ style: linkStyle,
827
+ onMouseEnter: (e) => {
828
+ e.currentTarget.style.textDecoration = "underline";
829
+ },
830
+ onMouseLeave: (e) => {
831
+ e.currentTarget.style.textDecoration = "none";
832
+ },
833
+ children: "Upgrade"
834
+ }
835
+ )
836
+ ]
837
+ }
838
+ );
839
+ }
840
+ if (variant === "banner") {
841
+ const bannerStyle = {
842
+ fontFamily: fontFamily3,
843
+ display: "flex",
844
+ alignItems: "center",
845
+ justifyContent: "space-between",
846
+ gap: 16,
847
+ background: "rgba(99, 102, 241, 0.06)",
848
+ border: "1px solid rgba(99, 102, 241, 0.15)",
849
+ borderRadius: 10,
850
+ padding: "12px 20px",
851
+ ...style
852
+ };
853
+ const msgStyle2 = {
854
+ fontSize: 14,
855
+ fontWeight: 500,
856
+ color: "#374151",
857
+ flex: 1
858
+ };
859
+ const btnStyle2 = {
860
+ display: "inline-flex",
861
+ alignItems: "center",
862
+ justifyContent: "center",
863
+ fontSize: 13,
864
+ fontWeight: 600,
865
+ color: "#ffffff",
866
+ background: primaryColor,
867
+ border: "none",
868
+ borderRadius: 8,
869
+ padding: "8px 18px",
870
+ textDecoration: "none",
871
+ cursor: "pointer",
872
+ whiteSpace: "nowrap",
873
+ transition: "background 0.15s ease"
874
+ };
875
+ return /* @__PURE__ */ jsxs(
876
+ "div",
877
+ {
878
+ "data-nozle": "upgrade-prompt",
879
+ "data-variant": "banner",
880
+ className,
881
+ style: bannerStyle,
882
+ ...props,
883
+ children: [
884
+ /* @__PURE__ */ jsx("span", { "data-nozle": "upgrade-prompt-message", style: msgStyle2, children: message }),
885
+ /* @__PURE__ */ jsx(
886
+ "a",
887
+ {
888
+ href: upgradeUrl,
889
+ "data-nozle": "upgrade-prompt-action",
890
+ style: btnStyle2,
891
+ onMouseEnter: (e) => {
892
+ e.currentTarget.style.background = primaryHover;
893
+ },
894
+ onMouseLeave: (e) => {
895
+ e.currentTarget.style.background = primaryColor;
896
+ },
897
+ children: "Upgrade"
898
+ }
899
+ )
900
+ ]
901
+ }
902
+ );
903
+ }
904
+ const cardStyle = {
905
+ fontFamily: fontFamily3,
906
+ maxWidth: 400,
907
+ background: "#ffffff",
908
+ border: "1px solid #e5e7eb",
909
+ borderRadius: 16,
910
+ padding: 24,
911
+ boxShadow: "0 1px 3px rgba(0,0,0,0.04), 0 1px 2px rgba(0,0,0,0.02)",
912
+ textAlign: "center",
913
+ ...style
914
+ };
915
+ const iconWrapStyle = {
916
+ display: "inline-flex",
917
+ alignItems: "center",
918
+ justifyContent: "center",
919
+ width: 48,
920
+ height: 48,
921
+ borderRadius: 12,
922
+ background: "rgba(99, 102, 241, 0.08)",
923
+ marginBottom: 16
924
+ };
925
+ const msgStyle = {
926
+ fontSize: 15,
927
+ fontWeight: 500,
928
+ color: "#374151",
929
+ lineHeight: 1.5,
930
+ margin: 0,
931
+ marginBottom: 20
932
+ };
933
+ const btnStyle = {
934
+ display: "inline-flex",
935
+ alignItems: "center",
936
+ justifyContent: "center",
937
+ fontSize: 14,
938
+ fontWeight: 600,
939
+ color: "#ffffff",
940
+ background: primaryColor,
941
+ border: "none",
942
+ borderRadius: 10,
943
+ padding: "10px 24px",
944
+ textDecoration: "none",
945
+ cursor: "pointer",
946
+ transition: "background 0.15s ease",
947
+ width: "100%"
948
+ };
949
+ return /* @__PURE__ */ jsxs(
950
+ "div",
951
+ {
952
+ "data-nozle": "upgrade-prompt",
953
+ "data-variant": "card",
954
+ className,
955
+ style: cardStyle,
956
+ ...props,
957
+ children: [
958
+ /* @__PURE__ */ jsxs("div", { "data-nozle": "upgrade-prompt-content", children: [
959
+ /* @__PURE__ */ jsx("div", { style: iconWrapStyle, children: /* @__PURE__ */ jsx(
960
+ "svg",
961
+ {
962
+ width: "24",
963
+ height: "24",
964
+ viewBox: "0 0 24 24",
965
+ fill: "none",
966
+ stroke: primaryColor,
967
+ strokeWidth: "2",
968
+ strokeLinecap: "round",
969
+ strokeLinejoin: "round",
970
+ children: /* @__PURE__ */ jsx("path", { d: "M13 2L3 14h9l-1 8 10-12h-9l1-8z" })
971
+ }
972
+ ) }),
973
+ /* @__PURE__ */ jsx("p", { "data-nozle": "upgrade-prompt-message", style: msgStyle, children: message })
974
+ ] }),
975
+ /* @__PURE__ */ jsx(
976
+ "a",
977
+ {
978
+ href: upgradeUrl,
979
+ "data-nozle": "upgrade-prompt-action",
980
+ style: btnStyle,
981
+ onMouseEnter: (e) => {
982
+ e.currentTarget.style.background = primaryHover;
983
+ },
984
+ onMouseLeave: (e) => {
985
+ e.currentTarget.style.background = primaryColor;
986
+ },
987
+ children: "Upgrade Now"
988
+ }
989
+ )
990
+ ]
991
+ }
992
+ );
993
+ }
994
+
995
+ export { BillingProvider, PlanBadge, UpgradePrompt, UsageMeter, useBillingContext, useCan, useCheckout, usePlan, usePlans, useSubscribe, useUsage };
996
+ //# sourceMappingURL=index.js.map
997
+ //# sourceMappingURL=index.js.map