@nozle-js/react 0.5.1 → 0.5.2

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.d.cts CHANGED
@@ -124,6 +124,27 @@ interface BillingPortalProviderProps {
124
124
  */
125
125
  declare function BillingPortalProvider({ customerId, apiKey, children, }: BillingPortalProviderProps): React.ReactElement;
126
126
 
127
+ interface PricingPlan {
128
+ code: string;
129
+ name: string;
130
+ amount_cents: number;
131
+ amount_currency: string;
132
+ interval: string;
133
+ description?: string;
134
+ }
135
+ interface PricingTableProps {
136
+ customerId?: string;
137
+ currentPlanCode?: string;
138
+ plans?: PricingPlan[];
139
+ features?: string[][];
140
+ onSelect?: (plan: PricingPlan) => void;
141
+ showToggle?: boolean;
142
+ highlightPlan?: string;
143
+ className?: string;
144
+ enterpriseEmail?: string;
145
+ }
146
+ declare function PricingTable({ customerId, currentPlanCode, plans: propPlans, features, onSelect, showToggle, highlightPlan, className, enterpriseEmail, }: PricingTableProps): React.ReactElement;
147
+
127
148
  /**
128
149
  * PlanCard — Individual pricing plan card component.
129
150
  * UI-01: Shows plan name, price, interval, features list, and CTA button.
@@ -150,32 +171,6 @@ interface PlanCardProps extends Plan {
150
171
  */
151
172
  declare function PlanCard({ id, name, monthlyPrice, annualPrice, features, description, isAnnual, isCurrent, onSelect, }: PlanCardProps): React.ReactElement;
152
173
 
153
- /**
154
- * PricingTable — Renders plan cards with monthly/annual toggle.
155
- * UI-01: Responsive grid, monthly/annual toggle, highlights currentPlanId.
156
- * Uses CSS variable theming with --nozle-* namespace.
157
- */
158
-
159
- interface PricingTableProps {
160
- plans: Plan[];
161
- currentPlanId?: string;
162
- onSelect?: (planId: string) => void;
163
- }
164
- /**
165
- * PricingTable renders plan cards in a responsive grid with monthly/annual toggle.
166
- * The current plan is highlighted with aria-current="true" and border-primary class.
167
- *
168
- * Usage:
169
- * ```tsx
170
- * <PricingTable
171
- * plans={plans}
172
- * currentPlanId="pro"
173
- * onSelect={(planId) => handleUpgrade(planId)}
174
- * />
175
- * ```
176
- */
177
- declare function PricingTable({ plans, currentPlanId, onSelect, }: PricingTableProps): React.ReactElement;
178
-
179
174
  /**
180
175
  * PlanComparison — Side-by-side feature comparison table.
181
176
  * UI-01: Renders shadcn-style table with plan names as headers and features as rows.
@@ -633,4 +628,4 @@ interface CancelSubscriptionButtonProps {
633
628
  }
634
629
  declare function CancelSubscriptionButton({ subscriptionId, onCancelled, }: CancelSubscriptionButtonProps): react_jsx_runtime.JSX.Element;
635
630
 
636
- export { BillingContext, type BillingContextValue, BillingPortal, type BillingPortalContextValue, type BillingPortalProps, BillingPortalProvider, type BillingPortalProviderProps, BillingProvider, type BillingProviderProps, CANCEL_REASONS, type CanState, CancelSubscriptionButton, type CancelSubscriptionButtonProps, Checkout, CheckoutButton, type CheckoutButtonProps, type CheckoutProps, type ComparisonFeature, CreditBalance, type CreditBalanceProps, CreditHistory, type CreditHistoryProps, CreditTopUpButton, type CreditTopUpButtonProps, type CreditTransaction, type CreditsState, CurrentPlan, type CurrentPlanData, type CurrentPlanProps, FeatureGate, type FeatureGateProps, type Invoice, InvoiceList, type InvoiceListProps, LockedOverlay, type LockedOverlayProps, type NozleClient, type PaymentMethod, PaymentMethodDisplay, type PaymentMethodDisplayProps, type Plan, PlanBadge, type PlanBadgeProps, PlanCard, type PlanCardProps, PlanComparison, type PlanComparisonProps, PlanGate, type PlanGateProps, PricingTable, type PricingTableProps, type ProrationPreview, UpgradeButton, type UpgradeButtonProps, UpgradeModal, type UpgradeModalProps, UpgradePrompt, type UpgradePromptProps, UsageAlert, type UsageAlertFeature, type UsageAlertProps, UsageDashboard, type UsageDashboardFeature, type UsageDashboardProps, UsageGate, type UsageGateProps, UsageMeter, type UsageMeterProps, type UsageState, type UseCheckoutResult, getUsageMeterColor, useBillingContext, useBillingPortal, useCan, useCheckout, useCredits, useNozleClient, useUsage };
631
+ export { BillingContext, type BillingContextValue, BillingPortal, type BillingPortalContextValue, type BillingPortalProps, BillingPortalProvider, type BillingPortalProviderProps, BillingProvider, type BillingProviderProps, CANCEL_REASONS, type CanState, CancelSubscriptionButton, type CancelSubscriptionButtonProps, Checkout, CheckoutButton, type CheckoutButtonProps, type CheckoutProps, type ComparisonFeature, CreditBalance, type CreditBalanceProps, CreditHistory, type CreditHistoryProps, CreditTopUpButton, type CreditTopUpButtonProps, type CreditTransaction, type CreditsState, CurrentPlan, type CurrentPlanData, type CurrentPlanProps, FeatureGate, type FeatureGateProps, type Invoice, InvoiceList, type InvoiceListProps, LockedOverlay, type LockedOverlayProps, type NozleClient, type PaymentMethod, PaymentMethodDisplay, type PaymentMethodDisplayProps, type Plan, PlanBadge, type PlanBadgeProps, PlanCard, type PlanCardProps, PlanComparison, type PlanComparisonProps, PlanGate, type PlanGateProps, type PricingPlan, PricingTable, type PricingTableProps, type ProrationPreview, UpgradeButton, type UpgradeButtonProps, UpgradeModal, type UpgradeModalProps, UpgradePrompt, type UpgradePromptProps, UsageAlert, type UsageAlertFeature, type UsageAlertProps, UsageDashboard, type UsageDashboardFeature, type UsageDashboardProps, UsageGate, type UsageGateProps, UsageMeter, type UsageMeterProps, type UsageState, type UseCheckoutResult, getUsageMeterColor, useBillingContext, useBillingPortal, useCan, useCheckout, useCredits, useNozleClient, useUsage };
package/dist/index.d.ts CHANGED
@@ -124,6 +124,27 @@ interface BillingPortalProviderProps {
124
124
  */
125
125
  declare function BillingPortalProvider({ customerId, apiKey, children, }: BillingPortalProviderProps): React.ReactElement;
126
126
 
127
+ interface PricingPlan {
128
+ code: string;
129
+ name: string;
130
+ amount_cents: number;
131
+ amount_currency: string;
132
+ interval: string;
133
+ description?: string;
134
+ }
135
+ interface PricingTableProps {
136
+ customerId?: string;
137
+ currentPlanCode?: string;
138
+ plans?: PricingPlan[];
139
+ features?: string[][];
140
+ onSelect?: (plan: PricingPlan) => void;
141
+ showToggle?: boolean;
142
+ highlightPlan?: string;
143
+ className?: string;
144
+ enterpriseEmail?: string;
145
+ }
146
+ declare function PricingTable({ customerId, currentPlanCode, plans: propPlans, features, onSelect, showToggle, highlightPlan, className, enterpriseEmail, }: PricingTableProps): React.ReactElement;
147
+
127
148
  /**
128
149
  * PlanCard — Individual pricing plan card component.
129
150
  * UI-01: Shows plan name, price, interval, features list, and CTA button.
@@ -150,32 +171,6 @@ interface PlanCardProps extends Plan {
150
171
  */
151
172
  declare function PlanCard({ id, name, monthlyPrice, annualPrice, features, description, isAnnual, isCurrent, onSelect, }: PlanCardProps): React.ReactElement;
152
173
 
153
- /**
154
- * PricingTable — Renders plan cards with monthly/annual toggle.
155
- * UI-01: Responsive grid, monthly/annual toggle, highlights currentPlanId.
156
- * Uses CSS variable theming with --nozle-* namespace.
157
- */
158
-
159
- interface PricingTableProps {
160
- plans: Plan[];
161
- currentPlanId?: string;
162
- onSelect?: (planId: string) => void;
163
- }
164
- /**
165
- * PricingTable renders plan cards in a responsive grid with monthly/annual toggle.
166
- * The current plan is highlighted with aria-current="true" and border-primary class.
167
- *
168
- * Usage:
169
- * ```tsx
170
- * <PricingTable
171
- * plans={plans}
172
- * currentPlanId="pro"
173
- * onSelect={(planId) => handleUpgrade(planId)}
174
- * />
175
- * ```
176
- */
177
- declare function PricingTable({ plans, currentPlanId, onSelect, }: PricingTableProps): React.ReactElement;
178
-
179
174
  /**
180
175
  * PlanComparison — Side-by-side feature comparison table.
181
176
  * UI-01: Renders shadcn-style table with plan names as headers and features as rows.
@@ -633,4 +628,4 @@ interface CancelSubscriptionButtonProps {
633
628
  }
634
629
  declare function CancelSubscriptionButton({ subscriptionId, onCancelled, }: CancelSubscriptionButtonProps): react_jsx_runtime.JSX.Element;
635
630
 
636
- export { BillingContext, type BillingContextValue, BillingPortal, type BillingPortalContextValue, type BillingPortalProps, BillingPortalProvider, type BillingPortalProviderProps, BillingProvider, type BillingProviderProps, CANCEL_REASONS, type CanState, CancelSubscriptionButton, type CancelSubscriptionButtonProps, Checkout, CheckoutButton, type CheckoutButtonProps, type CheckoutProps, type ComparisonFeature, CreditBalance, type CreditBalanceProps, CreditHistory, type CreditHistoryProps, CreditTopUpButton, type CreditTopUpButtonProps, type CreditTransaction, type CreditsState, CurrentPlan, type CurrentPlanData, type CurrentPlanProps, FeatureGate, type FeatureGateProps, type Invoice, InvoiceList, type InvoiceListProps, LockedOverlay, type LockedOverlayProps, type NozleClient, type PaymentMethod, PaymentMethodDisplay, type PaymentMethodDisplayProps, type Plan, PlanBadge, type PlanBadgeProps, PlanCard, type PlanCardProps, PlanComparison, type PlanComparisonProps, PlanGate, type PlanGateProps, PricingTable, type PricingTableProps, type ProrationPreview, UpgradeButton, type UpgradeButtonProps, UpgradeModal, type UpgradeModalProps, UpgradePrompt, type UpgradePromptProps, UsageAlert, type UsageAlertFeature, type UsageAlertProps, UsageDashboard, type UsageDashboardFeature, type UsageDashboardProps, UsageGate, type UsageGateProps, UsageMeter, type UsageMeterProps, type UsageState, type UseCheckoutResult, getUsageMeterColor, useBillingContext, useBillingPortal, useCan, useCheckout, useCredits, useNozleClient, useUsage };
631
+ export { BillingContext, type BillingContextValue, BillingPortal, type BillingPortalContextValue, type BillingPortalProps, BillingPortalProvider, type BillingPortalProviderProps, BillingProvider, type BillingProviderProps, CANCEL_REASONS, type CanState, CancelSubscriptionButton, type CancelSubscriptionButtonProps, Checkout, CheckoutButton, type CheckoutButtonProps, type CheckoutProps, type ComparisonFeature, CreditBalance, type CreditBalanceProps, CreditHistory, type CreditHistoryProps, CreditTopUpButton, type CreditTopUpButtonProps, type CreditTransaction, type CreditsState, CurrentPlan, type CurrentPlanData, type CurrentPlanProps, FeatureGate, type FeatureGateProps, type Invoice, InvoiceList, type InvoiceListProps, LockedOverlay, type LockedOverlayProps, type NozleClient, type PaymentMethod, PaymentMethodDisplay, type PaymentMethodDisplayProps, type Plan, PlanBadge, type PlanBadgeProps, PlanCard, type PlanCardProps, PlanComparison, type PlanComparisonProps, PlanGate, type PlanGateProps, type PricingPlan, PricingTable, type PricingTableProps, type ProrationPreview, UpgradeButton, type UpgradeButtonProps, UpgradeModal, type UpgradeModalProps, UpgradePrompt, type UpgradePromptProps, UsageAlert, type UsageAlertFeature, type UsageAlertProps, UsageDashboard, type UsageDashboardFeature, type UsageDashboardProps, UsageGate, type UsageGateProps, UsageMeter, type UsageMeterProps, type UsageState, type UseCheckoutResult, getUsageMeterColor, useBillingContext, useBillingPortal, useCan, useCheckout, useCredits, useNozleClient, useUsage };
package/dist/index.js CHANGED
@@ -297,6 +297,335 @@ function BillingPortalProvider({
297
297
  }) {
298
298
  return /* @__PURE__ */ jsx(BillingPortalContext.Provider, { value: { customerId, apiKey }, children });
299
299
  }
300
+ var SYMBOLS = { USD: "$", EUR: "\u20AC", GBP: "\xA3", JPY: "\xA5" };
301
+ function fmtPrice(cents, cur) {
302
+ const s = SYMBOLS[cur] ?? cur + " ";
303
+ const val = cents % 100 === 0 ? String(cents / 100) : (cents / 100).toFixed(2);
304
+ return `${s}${val}`;
305
+ }
306
+ function isEnterprise(p) {
307
+ return /enterprise|custom/i.test(p.name + p.code);
308
+ }
309
+ function v(name, fb) {
310
+ return `var(${name}, ${fb})`;
311
+ }
312
+ var C = {
313
+ bg: v("--nozle-pricing-bg", v("--nozle-background", "#ffffff")),
314
+ cardBg: v("--nozle-pricing-card-bg", v("--nozle-card", "#ffffff")),
315
+ hl: v("--nozle-pricing-highlight", v("--nozle-primary", "#6366f1")),
316
+ border: v("--nozle-pricing-border", v("--nozle-border", "#e5e7eb")),
317
+ radius: v("--nozle-pricing-radius", v("--nozle-radius", "12px")),
318
+ text: v("--nozle-foreground", "#111827"),
319
+ muted: v("--nozle-muted-foreground", "#6b7280"),
320
+ mutedBg: v("--nozle-muted", "#f3f4f6"),
321
+ hlFg: v("--nozle-primary-foreground", "#ffffff")
322
+ };
323
+ function Check() {
324
+ return /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", style: { flexShrink: 0 }, children: /* @__PURE__ */ jsx("path", { d: "M3.5 8.5L6.5 11.5L12.5 4.5", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round" }) });
325
+ }
326
+ function Toggle({ isAnnual, onChange }) {
327
+ const pill = {
328
+ display: "inline-flex",
329
+ borderRadius: "9999px",
330
+ padding: "4px",
331
+ background: C.mutedBg,
332
+ gap: "2px"
333
+ };
334
+ const tab = (active) => ({
335
+ padding: "8px 20px",
336
+ borderRadius: "9999px",
337
+ border: "none",
338
+ background: active ? C.cardBg : "transparent",
339
+ color: active ? C.text : C.muted,
340
+ fontWeight: active ? 600 : 400,
341
+ fontSize: "0.875rem",
342
+ cursor: "pointer",
343
+ boxShadow: active ? "0 1px 3px rgba(0,0,0,0.08)" : "none",
344
+ transition: "all 0.15s ease"
345
+ });
346
+ return /* @__PURE__ */ jsx("div", { style: { display: "flex", justifyContent: "center", marginBottom: "2.5rem" }, children: /* @__PURE__ */ jsxs("div", { style: pill, children: [
347
+ /* @__PURE__ */ jsx("button", { onClick: () => onChange(false), style: tab(!isAnnual), "aria-pressed": !isAnnual, children: "Monthly" }),
348
+ /* @__PURE__ */ jsxs("button", { onClick: () => onChange(true), style: tab(isAnnual), "aria-pressed": isAnnual, children: [
349
+ "Annual",
350
+ /* @__PURE__ */ jsx("span", { style: {
351
+ marginLeft: "6px",
352
+ fontSize: "0.7rem",
353
+ fontWeight: 600,
354
+ color: isAnnual ? C.hl : C.muted
355
+ }, children: "Save 20%" })
356
+ ] })
357
+ ] }) });
358
+ }
359
+ function Card({
360
+ plan,
361
+ features,
362
+ isCurrent,
363
+ isHighlighted,
364
+ isEnt,
365
+ enterpriseEmail,
366
+ onSelect
367
+ }) {
368
+ const [hover, setHover] = useState(false);
369
+ const shadow = isHighlighted ? "0 8px 28px rgba(99,102,241,0.12), 0 2px 8px rgba(99,102,241,0.06)" : hover ? "0 8px 24px rgba(0,0,0,0.06), 0 2px 8px rgba(0,0,0,0.03)" : "0 1px 3px rgba(0,0,0,0.04), 0 1px 2px rgba(0,0,0,0.03)";
370
+ const interval = plan.interval === "yearly" ? "/year" : "/month";
371
+ const handleClick = () => {
372
+ if (isCurrent) return;
373
+ if (isEnt && enterpriseEmail) {
374
+ window.location.href = `mailto:${enterpriseEmail}?subject=Enterprise Plan Inquiry`;
375
+ return;
376
+ }
377
+ onSelect?.();
378
+ };
379
+ return /* @__PURE__ */ jsxs(
380
+ "div",
381
+ {
382
+ onMouseEnter: () => setHover(true),
383
+ onMouseLeave: () => setHover(false),
384
+ style: {
385
+ position: "relative",
386
+ display: "flex",
387
+ flexDirection: "column",
388
+ background: C.cardBg,
389
+ border: `1px solid ${isHighlighted ? C.hl : C.border}`,
390
+ borderTopWidth: isHighlighted ? "3px" : "1px",
391
+ borderRadius: C.radius,
392
+ padding: "2rem",
393
+ transition: "box-shadow 0.2s ease, transform 0.2s ease",
394
+ boxShadow: shadow,
395
+ transform: hover && !isCurrent ? "translateY(-2px)" : "none"
396
+ },
397
+ children: [
398
+ isHighlighted && /* @__PURE__ */ jsx("div", { style: {
399
+ position: "absolute",
400
+ top: "-1px",
401
+ left: "50%",
402
+ transform: "translate(-50%, -50%)",
403
+ background: C.hl,
404
+ color: C.hlFg,
405
+ fontSize: "0.7rem",
406
+ fontWeight: 700,
407
+ padding: "4px 12px",
408
+ borderRadius: "9999px",
409
+ letterSpacing: "0.025em",
410
+ textTransform: "uppercase",
411
+ whiteSpace: "nowrap"
412
+ }, children: isCurrent ? "Current Plan" : "Most Popular" }),
413
+ isCurrent && !isHighlighted && /* @__PURE__ */ jsx("div", { style: {
414
+ position: "absolute",
415
+ top: "0",
416
+ left: "50%",
417
+ transform: "translate(-50%, -50%)",
418
+ background: C.mutedBg,
419
+ color: C.muted,
420
+ fontSize: "0.7rem",
421
+ fontWeight: 700,
422
+ padding: "4px 12px",
423
+ borderRadius: "9999px",
424
+ letterSpacing: "0.025em",
425
+ textTransform: "uppercase",
426
+ whiteSpace: "nowrap"
427
+ }, children: "Current Plan" }),
428
+ /* @__PURE__ */ jsxs("div", { style: { marginBottom: "1.25rem", marginTop: isHighlighted || isCurrent ? "0.5rem" : 0 }, children: [
429
+ /* @__PURE__ */ jsx("h3", { style: {
430
+ fontSize: "1.25rem",
431
+ fontWeight: 700,
432
+ color: C.text,
433
+ margin: 0,
434
+ lineHeight: 1.3
435
+ }, children: plan.name }),
436
+ plan.description && /* @__PURE__ */ jsx("p", { style: {
437
+ fontSize: "0.875rem",
438
+ color: C.muted,
439
+ margin: "6px 0 0",
440
+ lineHeight: 1.5
441
+ }, children: plan.description })
442
+ ] }),
443
+ /* @__PURE__ */ jsx("div", { style: { marginBottom: "1.5rem" }, children: isEnt ? /* @__PURE__ */ jsx("span", { style: { fontSize: "2.25rem", fontWeight: 700, color: C.text, lineHeight: 1 }, children: "Custom" }) : /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "baseline", gap: "2px" }, children: [
444
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "2.75rem", fontWeight: 700, color: C.text, lineHeight: 1, letterSpacing: "-0.025em" }, children: fmtPrice(plan.amount_cents, plan.amount_currency) }),
445
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "0.9375rem", color: C.muted, marginLeft: "4px" }, children: interval })
446
+ ] }) }),
447
+ features && features.length > 0 && /* @__PURE__ */ jsx("ul", { style: {
448
+ listStyle: "none",
449
+ padding: 0,
450
+ margin: "0 0 2rem",
451
+ display: "flex",
452
+ flexDirection: "column",
453
+ gap: "0.75rem",
454
+ flex: 1
455
+ }, children: features.map((feat) => /* @__PURE__ */ jsxs("li", { style: {
456
+ display: "flex",
457
+ alignItems: "flex-start",
458
+ gap: "10px",
459
+ fontSize: "0.875rem",
460
+ color: C.text,
461
+ lineHeight: 1.5
462
+ }, children: [
463
+ /* @__PURE__ */ jsx("span", { style: { color: C.hl, marginTop: "2px" }, children: /* @__PURE__ */ jsx(Check, {}) }),
464
+ /* @__PURE__ */ jsx("span", { children: feat })
465
+ ] }, feat)) }),
466
+ (!features || features.length === 0) && /* @__PURE__ */ jsx("div", { style: { flex: 1 } }),
467
+ onSelect && /* @__PURE__ */ jsx(
468
+ CTAButton,
469
+ {
470
+ onClick: handleClick,
471
+ disabled: isCurrent,
472
+ variant: isCurrent ? "current" : isEnt ? "outline" : isHighlighted ? "primary" : "secondary",
473
+ children: isCurrent ? "Current Plan" : isEnt ? "Contact Sales" : `Get ${plan.name}`
474
+ }
475
+ )
476
+ ]
477
+ }
478
+ );
479
+ }
480
+ function CTAButton({
481
+ children,
482
+ onClick,
483
+ disabled,
484
+ variant
485
+ }) {
486
+ const [hover, setHover] = useState(false);
487
+ const base = {
488
+ width: "100%",
489
+ padding: "12px 24px",
490
+ borderRadius: "10px",
491
+ fontSize: "0.9375rem",
492
+ fontWeight: 600,
493
+ cursor: disabled ? "default" : "pointer",
494
+ border: "none",
495
+ transition: "all 0.15s ease",
496
+ lineHeight: 1.4
497
+ };
498
+ const variants = {
499
+ primary: {
500
+ background: hover && !disabled ? "#4f46e5" : C.hl,
501
+ color: C.hlFg
502
+ },
503
+ secondary: {
504
+ background: hover && !disabled ? "#f0f0f3" : C.mutedBg,
505
+ color: C.text,
506
+ border: `1px solid ${C.border}`
507
+ },
508
+ outline: {
509
+ background: hover && !disabled ? "rgba(99,102,241,0.04)" : "transparent",
510
+ color: C.hl,
511
+ border: `1.5px solid ${C.hl}`
512
+ },
513
+ current: {
514
+ background: C.mutedBg,
515
+ color: C.muted
516
+ }
517
+ };
518
+ return /* @__PURE__ */ jsx(
519
+ "button",
520
+ {
521
+ onClick: disabled ? void 0 : onClick,
522
+ disabled,
523
+ onMouseEnter: () => setHover(true),
524
+ onMouseLeave: () => setHover(false),
525
+ style: { ...base, ...variants[variant] },
526
+ children
527
+ }
528
+ );
529
+ }
530
+ function Skeleton() {
531
+ return /* @__PURE__ */ jsx("div", { style: {
532
+ display: "grid",
533
+ gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
534
+ gap: "1.5rem"
535
+ }, children: [0, 1, 2].map((i) => /* @__PURE__ */ jsxs("div", { style: {
536
+ background: C.cardBg,
537
+ border: `1px solid ${C.border}`,
538
+ borderRadius: C.radius,
539
+ padding: "2rem",
540
+ height: "380px"
541
+ }, children: [
542
+ /* @__PURE__ */ jsx("div", { style: { height: 20, width: "40%", borderRadius: 6, background: "#f0f0f0", marginBottom: 12 } }),
543
+ /* @__PURE__ */ jsx("div", { style: { height: 40, width: "50%", borderRadius: 6, background: "#f0f0f0", marginBottom: 24 } }),
544
+ /* @__PURE__ */ jsx("div", { style: { height: 14, width: "80%", borderRadius: 6, background: "#f0f0f0", marginBottom: 10 } }),
545
+ /* @__PURE__ */ jsx("div", { style: { height: 14, width: "70%", borderRadius: 6, background: "#f0f0f0", marginBottom: 10 } }),
546
+ /* @__PURE__ */ jsx("div", { style: { height: 14, width: "60%", borderRadius: 6, background: "#f0f0f0" } })
547
+ ] }, i)) });
548
+ }
549
+ function PricingTable({
550
+ customerId,
551
+ currentPlanCode,
552
+ plans: propPlans,
553
+ features,
554
+ onSelect,
555
+ showToggle = true,
556
+ highlightPlan,
557
+ className,
558
+ enterpriseEmail
559
+ }) {
560
+ const client = useNozleClient();
561
+ const [plans, setPlans] = useState(propPlans ?? []);
562
+ const [currentPlan, setCurrentPlan] = useState(currentPlanCode ?? null);
563
+ const [isAnnual, setIsAnnual] = useState(false);
564
+ const [loading, setLoading] = useState(!propPlans);
565
+ useEffect(() => {
566
+ if (propPlans) {
567
+ setPlans(propPlans);
568
+ return;
569
+ }
570
+ let cancelled = false;
571
+ client.fetch("/api/v1/plans").then(async (res) => {
572
+ if (!cancelled && res.ok) {
573
+ const data = await res.json();
574
+ setPlans(data.plans || []);
575
+ }
576
+ }).finally(() => {
577
+ if (!cancelled) setLoading(false);
578
+ });
579
+ return () => {
580
+ cancelled = true;
581
+ };
582
+ }, [client, propPlans]);
583
+ useEffect(() => {
584
+ if (currentPlanCode !== void 0) {
585
+ setCurrentPlan(currentPlanCode);
586
+ return;
587
+ }
588
+ if (!customerId) return;
589
+ let cancelled = false;
590
+ client.fetch(`/api/v1/billing/status?customer_id=${encodeURIComponent(customerId)}`).then(async (res) => {
591
+ if (!cancelled && res.ok) {
592
+ const data = await res.json();
593
+ setCurrentPlan(data.plan ?? data.planName ?? null);
594
+ }
595
+ }).catch(() => {
596
+ });
597
+ return () => {
598
+ cancelled = true;
599
+ };
600
+ }, [client, customerId, currentPlanCode]);
601
+ const hasAnnual = plans.some((p) => p.interval === "yearly");
602
+ const filtered = showToggle && hasAnnual ? plans.filter((p) => p.interval === (isAnnual ? "yearly" : "monthly") || isEnterprise(p)) : plans;
603
+ if (loading) return /* @__PURE__ */ jsx(Skeleton, {});
604
+ return /* @__PURE__ */ jsxs("div", { className, style: {
605
+ fontFamily: "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
606
+ color: C.text,
607
+ background: C.bg
608
+ }, children: [
609
+ showToggle && hasAnnual && /* @__PURE__ */ jsx(Toggle, { isAnnual, onChange: setIsAnnual }),
610
+ /* @__PURE__ */ jsx("div", { style: {
611
+ display: "grid",
612
+ gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
613
+ gap: "1.5rem"
614
+ }, children: filtered.map((plan, i) => /* @__PURE__ */ jsx(
615
+ Card,
616
+ {
617
+ plan,
618
+ features: features?.[i],
619
+ isCurrent: currentPlan === plan.code,
620
+ isHighlighted: highlightPlan === plan.code,
621
+ isEnt: isEnterprise(plan),
622
+ enterpriseEmail,
623
+ onSelect: onSelect ? () => onSelect(plan) : void 0
624
+ },
625
+ plan.code
626
+ )) })
627
+ ] });
628
+ }
300
629
  function PlanCard({
301
630
  id,
302
631
  name,
@@ -441,91 +770,6 @@ function PlanCard({
441
770
  }
442
771
  );
443
772
  }
444
- function PricingTable({
445
- plans,
446
- currentPlanId,
447
- onSelect
448
- }) {
449
- const [isAnnual, setIsAnnual] = useState(false);
450
- return /* @__PURE__ */ jsxs(
451
- "div",
452
- {
453
- style: {
454
- background: "var(--nozle-background, var(--background))",
455
- color: "var(--nozle-foreground, var(--foreground))"
456
- },
457
- children: [
458
- /* @__PURE__ */ jsxs(
459
- "div",
460
- {
461
- style: {
462
- display: "flex",
463
- gap: "0.5rem",
464
- justifyContent: "center",
465
- marginBottom: "1.5rem"
466
- },
467
- children: [
468
- /* @__PURE__ */ jsx(
469
- "button",
470
- {
471
- onClick: () => setIsAnnual(false),
472
- style: {
473
- padding: "0.5rem 1rem",
474
- borderRadius: "var(--nozle-radius, 0.5rem)",
475
- border: "1px solid var(--nozle-border, var(--border))",
476
- background: !isAnnual ? "var(--nozle-primary, var(--primary))" : "transparent",
477
- color: !isAnnual ? "var(--nozle-primary-foreground, var(--primary-foreground))" : "var(--nozle-foreground, var(--foreground))",
478
- cursor: "pointer",
479
- fontWeight: !isAnnual ? 600 : 400
480
- },
481
- "aria-pressed": !isAnnual,
482
- children: "Monthly"
483
- }
484
- ),
485
- /* @__PURE__ */ jsx(
486
- "button",
487
- {
488
- onClick: () => setIsAnnual(true),
489
- style: {
490
- padding: "0.5rem 1rem",
491
- borderRadius: "var(--nozle-radius, 0.5rem)",
492
- border: "1px solid var(--nozle-border, var(--border))",
493
- background: isAnnual ? "var(--nozle-primary, var(--primary))" : "transparent",
494
- color: isAnnual ? "var(--nozle-primary-foreground, var(--primary-foreground))" : "var(--nozle-foreground, var(--foreground))",
495
- cursor: "pointer",
496
- fontWeight: isAnnual ? 600 : 400
497
- },
498
- "aria-pressed": isAnnual,
499
- children: "Annual"
500
- }
501
- )
502
- ]
503
- }
504
- ),
505
- /* @__PURE__ */ jsx(
506
- "div",
507
- {
508
- style: {
509
- display: "grid",
510
- gridTemplateColumns: `repeat(${Math.min(plans.length, 3)}, 1fr)`,
511
- gap: "1rem"
512
- },
513
- children: plans.map((plan) => /* @__PURE__ */ jsx(
514
- PlanCard,
515
- {
516
- ...plan,
517
- isAnnual,
518
- isCurrent: plan.id === currentPlanId,
519
- onSelect: onSelect ? () => onSelect(plan.id) : void 0
520
- },
521
- plan.id
522
- ))
523
- }
524
- )
525
- ]
526
- }
527
- );
528
- }
529
773
  function PlanComparison({
530
774
  plans,
531
775
  features