@solvapay/react 1.0.8-preview.6 → 1.0.8-preview.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -95
- package/dist/CancelPlanButton-Bb7cvym9.d.cts +1105 -0
- package/dist/CancelPlanButton-CieT9swn.d.cts +1102 -0
- package/dist/CancelPlanButton-Cq117t7h.d.ts +1105 -0
- package/dist/CancelPlanButton-f56UlQN-.d.ts +1102 -0
- package/dist/PaymentForm-B5dw0i1X.d.ts +934 -0
- package/dist/PaymentForm-CesoRQWu.d.cts +934 -0
- package/dist/chunk-C7SKOENW.js +1889 -0
- package/dist/chunk-ISYAH4ZL.js +1889 -0
- package/dist/chunk-MOP3ZBGC.js +4749 -0
- package/dist/chunk-Q7GPX6X4.js +4748 -0
- package/dist/chunk-TP4JPKE2.js +3126 -0
- package/dist/chunk-VTGIXJQU.js +4755 -0
- package/dist/chunk-XMQ4GBWQ.js +4752 -0
- package/dist/index-DLl3UwO3.d.ts +798 -0
- package/dist/index-Sj2lHLl8.d.cts +798 -0
- package/dist/index.cjs +11 -25
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/primitives/index.cjs +4 -21
- package/dist/primitives/index.d.cts +2 -2
- package/dist/primitives/index.d.ts +2 -2
- package/dist/primitives/index.js +1 -1
- package/dist/src/PaymentForm.d.ts +56 -0
- package/dist/src/PaymentForm.d.ts.map +1 -0
- package/dist/src/PaymentForm.js +212 -0
- package/dist/src/PaymentForm.js.map +1 -0
- package/dist/src/SolvaPayProvider.d.ts +84 -0
- package/dist/src/SolvaPayProvider.d.ts.map +1 -0
- package/dist/src/SolvaPayProvider.js +537 -0
- package/dist/src/SolvaPayProvider.js.map +1 -0
- package/dist/src/adapters/auth.d.ts +43 -0
- package/dist/src/adapters/auth.d.ts.map +1 -0
- package/dist/src/adapters/auth.js +38 -0
- package/dist/src/adapters/auth.js.map +1 -0
- package/dist/src/components/PlanBadge.d.ts +31 -0
- package/dist/src/components/PlanBadge.d.ts.map +1 -0
- package/dist/src/components/PlanBadge.js +119 -0
- package/dist/src/components/PlanBadge.js.map +1 -0
- package/dist/src/components/PlanSelector.d.ts +49 -0
- package/dist/src/components/PlanSelector.d.ts.map +1 -0
- package/dist/src/components/PlanSelector.js +82 -0
- package/dist/src/components/PlanSelector.js.map +1 -0
- package/dist/src/components/Spinner.d.ts +10 -0
- package/dist/src/components/Spinner.d.ts.map +1 -0
- package/dist/src/components/Spinner.js +14 -0
- package/dist/src/components/Spinner.js.map +1 -0
- package/dist/src/components/StripePaymentFormWrapper.d.ts +17 -0
- package/dist/src/components/StripePaymentFormWrapper.d.ts.map +1 -0
- package/dist/src/components/StripePaymentFormWrapper.js +158 -0
- package/dist/src/components/StripePaymentFormWrapper.js.map +1 -0
- package/dist/src/components/SubscriptionGate.d.ts +21 -0
- package/dist/src/components/SubscriptionGate.d.ts.map +1 -0
- package/dist/src/components/SubscriptionGate.js +32 -0
- package/dist/src/components/SubscriptionGate.js.map +1 -0
- package/dist/src/hooks/__tests__/useSubscription.test.d.ts +2 -0
- package/dist/src/hooks/__tests__/useSubscription.test.d.ts.map +1 -0
- package/dist/src/hooks/__tests__/useSubscription.test.js +554 -0
- package/dist/src/hooks/__tests__/useSubscription.test.js.map +1 -0
- package/dist/src/hooks/__tests__/useSubscriptionStatus.test.d.ts +2 -0
- package/dist/src/hooks/__tests__/useSubscriptionStatus.test.d.ts.map +1 -0
- package/dist/src/hooks/__tests__/useSubscriptionStatus.test.js +719 -0
- package/dist/src/hooks/__tests__/useSubscriptionStatus.test.js.map +1 -0
- package/dist/src/hooks/useCheckout.d.ts +59 -0
- package/dist/src/hooks/useCheckout.d.ts.map +1 -0
- package/dist/src/hooks/useCheckout.js +138 -0
- package/dist/src/hooks/useCheckout.js.map +1 -0
- package/dist/src/hooks/useCustomer.d.ts +43 -0
- package/dist/src/hooks/useCustomer.d.ts.map +1 -0
- package/dist/src/hooks/useCustomer.js +31 -0
- package/dist/src/hooks/useCustomer.js.map +1 -0
- package/dist/src/hooks/usePlans.d.ts +28 -0
- package/dist/src/hooks/usePlans.d.ts.map +1 -0
- package/dist/src/hooks/usePlans.js +181 -0
- package/dist/src/hooks/usePlans.js.map +1 -0
- package/dist/src/hooks/useSolvaPay.d.ts +44 -0
- package/dist/src/hooks/useSolvaPay.d.ts.map +1 -0
- package/dist/src/hooks/useSolvaPay.js +52 -0
- package/dist/src/hooks/useSolvaPay.js.map +1 -0
- package/dist/src/hooks/useSubscription.d.ts +46 -0
- package/dist/src/hooks/useSubscription.d.ts.map +1 -0
- package/dist/src/hooks/useSubscription.js +50 -0
- package/dist/src/hooks/useSubscription.js.map +1 -0
- package/dist/src/hooks/useSubscriptionStatus.d.ts +19 -0
- package/dist/src/hooks/useSubscriptionStatus.d.ts.map +1 -0
- package/dist/src/hooks/useSubscriptionStatus.js +68 -0
- package/dist/src/hooks/useSubscriptionStatus.js.map +1 -0
- package/dist/src/index.d.ts +24 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +26 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/types/index.d.ts +339 -0
- package/dist/src/types/index.d.ts.map +1 -0
- package/dist/src/types/index.js +5 -0
- package/dist/src/types/index.js.map +1 -0
- package/dist/src/utils/__tests__/subscriptions.test.d.ts +2 -0
- package/dist/src/utils/__tests__/subscriptions.test.d.ts.map +1 -0
- package/dist/src/utils/__tests__/subscriptions.test.js +121 -0
- package/dist/src/utils/__tests__/subscriptions.test.js.map +1 -0
- package/dist/src/utils/subscriptions.d.ts +57 -0
- package/dist/src/utils/subscriptions.d.ts.map +1 -0
- package/dist/src/utils/subscriptions.js +85 -0
- package/dist/src/utils/subscriptions.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { PlanBadgeProps } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Headless Plan Badge Component
|
|
5
|
+
*
|
|
6
|
+
* Displays subscription status with complete styling control.
|
|
7
|
+
* Supports render props, custom components, or className patterns.
|
|
8
|
+
*
|
|
9
|
+
* Prevents flickering by hiding the badge during initial load and when no subscription exists.
|
|
10
|
+
* Shows the badge once loading completes AND an active subscription exists (paid or free).
|
|
11
|
+
* Badge only updates when the plan name actually changes (prevents unnecessary re-renders).
|
|
12
|
+
*
|
|
13
|
+
* Displays the primary active subscription (paid or free) to show current plan status.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* // Render prop pattern
|
|
18
|
+
* <PlanBadge>
|
|
19
|
+
* {({ subscriptions, loading, displayPlan, shouldShow }) => (
|
|
20
|
+
* shouldShow ? (
|
|
21
|
+
* <div>{displayPlan}</div>
|
|
22
|
+
* ) : null
|
|
23
|
+
* )}
|
|
24
|
+
* </PlanBadge>
|
|
25
|
+
*
|
|
26
|
+
* //ClassName pattern
|
|
27
|
+
* <PlanBadge className="badge badge-primary" />
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare const PlanBadge: React.FC<PlanBadgeProps>;
|
|
31
|
+
//# sourceMappingURL=PlanBadge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PlanBadge.d.ts","sourceRoot":"","sources":["../../../src/components/PlanBadge.tsx"],"names":[],"mappings":"AACA,OAAO,KAAsC,MAAM,OAAO,CAAA;AAE1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,CAsH9C,CAAA"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useEffect, useRef } from 'react';
|
|
4
|
+
import { useSubscription } from '../hooks/useSubscription';
|
|
5
|
+
/**
|
|
6
|
+
* Headless Plan Badge Component
|
|
7
|
+
*
|
|
8
|
+
* Displays subscription status with complete styling control.
|
|
9
|
+
* Supports render props, custom components, or className patterns.
|
|
10
|
+
*
|
|
11
|
+
* Prevents flickering by hiding the badge during initial load and when no subscription exists.
|
|
12
|
+
* Shows the badge once loading completes AND an active subscription exists (paid or free).
|
|
13
|
+
* Badge only updates when the plan name actually changes (prevents unnecessary re-renders).
|
|
14
|
+
*
|
|
15
|
+
* Displays the primary active subscription (paid or free) to show current plan status.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```tsx
|
|
19
|
+
* // Render prop pattern
|
|
20
|
+
* <PlanBadge>
|
|
21
|
+
* {({ subscriptions, loading, displayPlan, shouldShow }) => (
|
|
22
|
+
* shouldShow ? (
|
|
23
|
+
* <div>{displayPlan}</div>
|
|
24
|
+
* ) : null
|
|
25
|
+
* )}
|
|
26
|
+
* </PlanBadge>
|
|
27
|
+
*
|
|
28
|
+
* //ClassName pattern
|
|
29
|
+
* <PlanBadge className="badge badge-primary" />
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export const PlanBadge = ({ children, as: Component = 'div', className, }) => {
|
|
33
|
+
const { subscriptions, loading, hasPaidSubscription, activeSubscription } = useSubscription();
|
|
34
|
+
const [displayPlan, setDisplayPlan] = useState(null);
|
|
35
|
+
const [hasLoadedOnce, setHasLoadedOnce] = useState(false);
|
|
36
|
+
// Refs for tracking previous values and preventing race conditions
|
|
37
|
+
const lastPlanRef = useRef(null);
|
|
38
|
+
const lastLoadingRef = useRef(true); // Start as true (initial loading state)
|
|
39
|
+
const previousSubscriptionsRef = useRef(subscriptions);
|
|
40
|
+
// Use activeSubscription from hook (primary subscription - paid or free)
|
|
41
|
+
const currentPlanName = activeSubscription?.planName || null;
|
|
42
|
+
const effectivePlanName = currentPlanName;
|
|
43
|
+
// Track when loading completes (not when subscriptions exist)
|
|
44
|
+
// This handles the case where loading finishes but subscriptions array is empty
|
|
45
|
+
// Also handles the case where loading starts as false (already loaded)
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
// Simple: if loading is false, we've loaded (either completed or started as false)
|
|
48
|
+
// Only set once to avoid unnecessary re-renders
|
|
49
|
+
if (!loading && !hasLoadedOnce) {
|
|
50
|
+
setHasLoadedOnce(true);
|
|
51
|
+
}
|
|
52
|
+
// Track loading state for potential future use
|
|
53
|
+
lastLoadingRef.current = loading;
|
|
54
|
+
}, [loading, hasLoadedOnce]);
|
|
55
|
+
// Detect customerRef changes (when subscriptions array reference changes)
|
|
56
|
+
// Reset state when customer changes to prevent stale data
|
|
57
|
+
// Note: Subscriptions array reference changes when customerRef changes OR when refetching
|
|
58
|
+
// We reset state conservatively - if loading, state will be restored when loading completes
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
const previousSubs = previousSubscriptionsRef.current;
|
|
61
|
+
// Check if subscriptions array reference changed
|
|
62
|
+
// This happens when customerRef changes OR when subscriptions are refetched
|
|
63
|
+
if (previousSubs !== subscriptions) {
|
|
64
|
+
// Only reset hasLoadedOnce if we're currently loading (new fetch in progress)
|
|
65
|
+
// If not loading, keep hasLoadedOnce true to prevent flickering on refetches
|
|
66
|
+
if (loading) {
|
|
67
|
+
setHasLoadedOnce(false);
|
|
68
|
+
}
|
|
69
|
+
// Always reset displayPlan and lastPlanRef when subscriptions change
|
|
70
|
+
// This ensures we show the latest plan name
|
|
71
|
+
setDisplayPlan(null);
|
|
72
|
+
lastPlanRef.current = null;
|
|
73
|
+
previousSubscriptionsRef.current = subscriptions;
|
|
74
|
+
}
|
|
75
|
+
}, [subscriptions, loading]);
|
|
76
|
+
// Update display plan when plan name changes
|
|
77
|
+
// Only update when effectivePlanName actually changes (prevents unnecessary re-renders)
|
|
78
|
+
// Also handle case where effectivePlanName becomes null
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
const currentPlan = effectivePlanName;
|
|
81
|
+
const previousPlan = lastPlanRef.current;
|
|
82
|
+
if (currentPlan !== previousPlan) {
|
|
83
|
+
// Plan name changed - update displayPlan
|
|
84
|
+
if (currentPlan !== null) {
|
|
85
|
+
lastPlanRef.current = currentPlan;
|
|
86
|
+
setDisplayPlan(currentPlan);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
// Plan name became null - clear displayPlan
|
|
90
|
+
lastPlanRef.current = null;
|
|
91
|
+
setDisplayPlan(null);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
else if (currentPlan !== null && displayPlan === null) {
|
|
95
|
+
// Initialize displayPlan if it's null but we have an effectivePlanName
|
|
96
|
+
// This handles the case where displayPlan was reset but effectivePlanName is still valid
|
|
97
|
+
setDisplayPlan(currentPlan);
|
|
98
|
+
}
|
|
99
|
+
}, [effectivePlanName, displayPlan]);
|
|
100
|
+
// Determine if badge should be shown
|
|
101
|
+
// Show if: loading has completed AND we have an active subscription (paid or free)
|
|
102
|
+
// This ensures badge only appears after initial load completes (prevents flickering)
|
|
103
|
+
const shouldShow = effectivePlanName !== null && hasLoadedOnce;
|
|
104
|
+
// Use displayPlan if available, otherwise fall back to effectivePlanName
|
|
105
|
+
// displayPlan is stable (only changes when plan name actually changes)
|
|
106
|
+
const planToDisplay = displayPlan ?? effectivePlanName;
|
|
107
|
+
// If using render prop pattern
|
|
108
|
+
if (children) {
|
|
109
|
+
return _jsx(_Fragment, { children: children({ subscriptions, loading, displayPlan: planToDisplay, shouldShow }) });
|
|
110
|
+
}
|
|
111
|
+
// Hide badge if we shouldn't show it
|
|
112
|
+
if (!shouldShow) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
// Determine className
|
|
116
|
+
const computedClassName = typeof className === 'function' ? className({ subscriptions }) : className;
|
|
117
|
+
return (_jsx(Component, { className: computedClassName, "data-loading": loading, "data-has-subscription": !!activeSubscription, "data-has-paid-subscription": hasPaidSubscription, role: "status", "aria-live": "polite", "aria-busy": loading, "aria-label": `Current plan: ${planToDisplay}`, children: planToDisplay }));
|
|
118
|
+
};
|
|
119
|
+
//# sourceMappingURL=PlanBadge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PlanBadge.js","sourceRoot":"","sources":["../../../src/components/PlanBadge.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { PlanSelectorProps } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Headless Plan Selector Component
|
|
5
|
+
*
|
|
6
|
+
* Provides plan selection logic with complete styling control via render props.
|
|
7
|
+
* Integrates plan fetching, subscription status, and selection state management.
|
|
8
|
+
*
|
|
9
|
+
* Features:
|
|
10
|
+
* - Fetches and manages plans
|
|
11
|
+
* - Tracks selected plan
|
|
12
|
+
* - Provides helpers for checking if plan is current/paid
|
|
13
|
+
* - Integrates with subscription context
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* <PlanSelector
|
|
18
|
+
* agentRef="agent_123"
|
|
19
|
+
* fetcher={async (agentRef) => {
|
|
20
|
+
* const res = await fetch(`/api/list-plans?agentRef=${agentRef}`);
|
|
21
|
+
* const data = await res.json();
|
|
22
|
+
* return data.plans;
|
|
23
|
+
* }}
|
|
24
|
+
* sortBy={(a, b) => (a.price || 0) - (b.price || 0)}
|
|
25
|
+
* autoSelectFirstPaid
|
|
26
|
+
* >
|
|
27
|
+
* {({ plans, selectedPlan, setSelectedPlanIndex, loading, isPaidPlan, isCurrentPlan }) => (
|
|
28
|
+
* <div>
|
|
29
|
+
* {loading ? (
|
|
30
|
+
* <div>Loading plans...</div>
|
|
31
|
+
* ) : (
|
|
32
|
+
* plans.map((plan, index) => (
|
|
33
|
+
* <button
|
|
34
|
+
* key={plan.reference}
|
|
35
|
+
* onClick={() => setSelectedPlanIndex(index)}
|
|
36
|
+
* disabled={!isPaidPlan(plan.name)}
|
|
37
|
+
* >
|
|
38
|
+
* {plan.name} - ${plan.price}
|
|
39
|
+
* {isCurrentPlan(plan.name) && ' (Current)'}
|
|
40
|
+
* </button>
|
|
41
|
+
* ))
|
|
42
|
+
* )}
|
|
43
|
+
* </div>
|
|
44
|
+
* )}
|
|
45
|
+
* </PlanSelector>
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export declare const PlanSelector: React.FC<PlanSelectorProps>;
|
|
49
|
+
//# sourceMappingURL=PlanSelector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PlanSelector.d.ts","sourceRoot":"","sources":["../../../src/components/PlanSelector.tsx"],"names":[],"mappings":"AACA,OAAO,KAA+B,MAAM,OAAO,CAAA;AAGnD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAEjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAyDpD,CAAA"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback, useMemo } from 'react';
|
|
4
|
+
import { usePlans } from '../hooks/usePlans';
|
|
5
|
+
import { useSubscription } from '../hooks/useSubscription';
|
|
6
|
+
/**
|
|
7
|
+
* Headless Plan Selector Component
|
|
8
|
+
*
|
|
9
|
+
* Provides plan selection logic with complete styling control via render props.
|
|
10
|
+
* Integrates plan fetching, subscription status, and selection state management.
|
|
11
|
+
*
|
|
12
|
+
* Features:
|
|
13
|
+
* - Fetches and manages plans
|
|
14
|
+
* - Tracks selected plan
|
|
15
|
+
* - Provides helpers for checking if plan is current/paid
|
|
16
|
+
* - Integrates with subscription context
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* <PlanSelector
|
|
21
|
+
* agentRef="agent_123"
|
|
22
|
+
* fetcher={async (agentRef) => {
|
|
23
|
+
* const res = await fetch(`/api/list-plans?agentRef=${agentRef}`);
|
|
24
|
+
* const data = await res.json();
|
|
25
|
+
* return data.plans;
|
|
26
|
+
* }}
|
|
27
|
+
* sortBy={(a, b) => (a.price || 0) - (b.price || 0)}
|
|
28
|
+
* autoSelectFirstPaid
|
|
29
|
+
* >
|
|
30
|
+
* {({ plans, selectedPlan, setSelectedPlanIndex, loading, isPaidPlan, isCurrentPlan }) => (
|
|
31
|
+
* <div>
|
|
32
|
+
* {loading ? (
|
|
33
|
+
* <div>Loading plans...</div>
|
|
34
|
+
* ) : (
|
|
35
|
+
* plans.map((plan, index) => (
|
|
36
|
+
* <button
|
|
37
|
+
* key={plan.reference}
|
|
38
|
+
* onClick={() => setSelectedPlanIndex(index)}
|
|
39
|
+
* disabled={!isPaidPlan(plan.name)}
|
|
40
|
+
* >
|
|
41
|
+
* {plan.name} - ${plan.price}
|
|
42
|
+
* {isCurrentPlan(plan.name) && ' (Current)'}
|
|
43
|
+
* </button>
|
|
44
|
+
* ))
|
|
45
|
+
* )}
|
|
46
|
+
* </div>
|
|
47
|
+
* )}
|
|
48
|
+
* </PlanSelector>
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export const PlanSelector = ({ agentRef, fetcher, filter, sortBy, autoSelectFirstPaid, children, }) => {
|
|
52
|
+
const { subscriptions } = useSubscription();
|
|
53
|
+
const plansHook = usePlans({
|
|
54
|
+
agentRef,
|
|
55
|
+
fetcher,
|
|
56
|
+
filter,
|
|
57
|
+
sortBy,
|
|
58
|
+
autoSelectFirstPaid,
|
|
59
|
+
});
|
|
60
|
+
const { plans } = plansHook;
|
|
61
|
+
// Helper to check if a plan is paid
|
|
62
|
+
const isPaidPlan = useCallback((planName) => {
|
|
63
|
+
const plan = plans.find(p => p.name === planName);
|
|
64
|
+
return plan ? (plan.price ?? 0) > 0 && !plan.isFreeTier : true;
|
|
65
|
+
}, [plans]);
|
|
66
|
+
// Get active subscription plan name
|
|
67
|
+
const activeSubscription = useMemo(() => {
|
|
68
|
+
const activeSubs = subscriptions.filter(sub => sub.status === 'active');
|
|
69
|
+
return (activeSubs.sort((a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime())[0] || null);
|
|
70
|
+
}, [subscriptions]);
|
|
71
|
+
// Helper to check if a plan is the current subscription
|
|
72
|
+
const isCurrentPlan = useCallback((planName) => {
|
|
73
|
+
return activeSubscription?.planName === planName;
|
|
74
|
+
}, [activeSubscription]);
|
|
75
|
+
return (_jsx(_Fragment, { children: children({
|
|
76
|
+
...plansHook,
|
|
77
|
+
subscriptions,
|
|
78
|
+
isPaidPlan,
|
|
79
|
+
isCurrentPlan,
|
|
80
|
+
}) }));
|
|
81
|
+
};
|
|
82
|
+
//# sourceMappingURL=PlanSelector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PlanSelector.js","sourceRoot":"","sources":["../../../src/components/PlanSelector.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AACZ,OAAc,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAG1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,MAAM,CAAC,MAAM,YAAY,GAAgC,CAAC,EACxD,QAAQ,EACR,OAAO,EACP,MAAM,EACN,MAAM,EACN,mBAAmB,EACnB,QAAQ,GACT,EAAE,EAAE;IACH,MAAM,EAAE,aAAa,EAAE,GAAG,eAAe,EAAE,CAAA;IAE3C,MAAM,SAAS,GAAG,QAAQ,CAAC;QACzB,QAAQ;QACR,OAAO;QACP,MAAM;QACN,MAAM;QACN,mBAAmB;KACpB,CAAC,CAAA;IAEF,MAAM,EAAE,KAAK,EAAE,GAAG,SAAS,CAAA;IAE3B,oCAAoC;IACpC,MAAM,UAAU,GAAG,WAAW,CAC5B,CAAC,QAAgB,EAAW,EAAE;QAC5B,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAA;QACjD,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAA;IAChE,CAAC,EACD,CAAC,KAAK,CAAC,CACR,CAAA;IAED,oCAAoC;IACpC,MAAM,kBAAkB,GAAG,OAAO,CAAC,GAAG,EAAE;QACtC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAA;QACvE,OAAO,CACL,UAAU,CAAC,IAAI,CACb,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAC5E,CAAC,CAAC,CAAC,IAAI,IAAI,CACb,CAAA;IACH,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAA;IAEnB,wDAAwD;IACxD,MAAM,aAAa,GAAG,WAAW,CAC/B,CAAC,QAAgB,EAAW,EAAE;QAC5B,OAAO,kBAAkB,EAAE,QAAQ,KAAK,QAAQ,CAAA;IAClD,CAAC,EACD,CAAC,kBAAkB,CAAC,CACrB,CAAA;IAED,OAAO,CACL,4BACG,QAAQ,CAAC;YACR,GAAG,SAAS;YACZ,aAAa;YACb,UAAU;YACV,aAAa;SACd,CAAC,GACD,CACJ,CAAA;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* SVG-based spinner component using CSS animations
|
|
4
|
+
* Uses the same spinner design as the rest of the site
|
|
5
|
+
*/
|
|
6
|
+
export declare const Spinner: React.FC<{
|
|
7
|
+
className?: string;
|
|
8
|
+
size?: 'sm' | 'md' | 'lg';
|
|
9
|
+
}>;
|
|
10
|
+
//# sourceMappingURL=Spinner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Spinner.d.ts","sourceRoot":"","sources":["../../../src/components/Spinner.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB;;;GAGG;AACH,eAAO,MAAM,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;CAAE,CA2B/E,CAAA"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* SVG-based spinner component using CSS animations
|
|
4
|
+
* Uses the same spinner design as the rest of the site
|
|
5
|
+
*/
|
|
6
|
+
export const Spinner = ({ className = '', size = 'md', }) => {
|
|
7
|
+
const sizeClasses = {
|
|
8
|
+
sm: 'h-4 w-4',
|
|
9
|
+
md: 'h-5 w-5',
|
|
10
|
+
lg: 'h-6 w-6',
|
|
11
|
+
};
|
|
12
|
+
return (_jsxs("svg", { className: `animate-spin ${sizeClasses[size]} ${className}`, xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", role: "status", "aria-busy": "true", children: [_jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), _jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" })] }));
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=Spinner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Spinner.js","sourceRoot":"","sources":["../../../src/components/Spinner.tsx"],"names":[],"mappings":";AAEA;;;GAGG;AACH,MAAM,CAAC,MAAM,OAAO,GAAgE,CAAC,EACnF,SAAS,GAAG,EAAE,EACd,IAAI,GAAG,IAAI,GACZ,EAAE,EAAE;IACH,MAAM,WAAW,GAAG;QAClB,EAAE,EAAE,SAAS;QACb,EAAE,EAAE,SAAS;QACb,EAAE,EAAE,SAAS;KACd,CAAA;IAED,OAAO,CACL,eACE,SAAS,EAAE,gBAAgB,WAAW,CAAC,IAAI,CAAC,IAAI,SAAS,EAAE,EAC3D,KAAK,EAAC,4BAA4B,EAClC,IAAI,EAAC,MAAM,EACX,OAAO,EAAC,WAAW,EACnB,IAAI,EAAC,QAAQ,eACH,MAAM,aAEhB,iBAAQ,SAAS,EAAC,YAAY,EAAC,EAAE,EAAC,IAAI,EAAC,EAAE,EAAC,IAAI,EAAC,CAAC,EAAC,IAAI,EAAC,MAAM,EAAC,cAAc,EAAC,WAAW,EAAC,GAAG,GAAG,EAC9F,eACE,SAAS,EAAC,YAAY,EACtB,IAAI,EAAC,cAAc,EACnB,CAAC,EAAC,iHAAiH,GACnH,IACE,CACP,CAAA;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface StripePaymentFormWrapperProps {
|
|
3
|
+
onSuccess?: (paymentIntent: any) => void | Promise<void>;
|
|
4
|
+
onError?: (error: Error) => void;
|
|
5
|
+
returnUrl?: string;
|
|
6
|
+
submitButtonText?: string;
|
|
7
|
+
buttonClassName?: string;
|
|
8
|
+
clientSecret: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Stripe Payment Form Wrapper Component
|
|
12
|
+
* Renders inside Stripe Elements context and handles the payment flow
|
|
13
|
+
* All hooks are called unconditionally to comply with React Rules of Hooks
|
|
14
|
+
*/
|
|
15
|
+
export declare const StripePaymentFormWrapper: React.FC<StripePaymentFormWrapperProps>;
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=StripePaymentFormWrapper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StripePaymentFormWrapper.d.ts","sourceRoot":"","sources":["../../../src/components/StripePaymentFormWrapper.tsx"],"names":[],"mappings":"AACA,OAAO,KAA8B,MAAM,OAAO,CAAA;AAKlD,UAAU,6BAA6B;IACrC,SAAS,CAAC,EAAE,CAAC,aAAa,EAAE,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACxD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;IAChC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,YAAY,EAAE,MAAM,CAAA;CACrB;AAED;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,EAAE,KAAK,CAAC,EAAE,CAAC,6BAA6B,CAuM5E,CAAA"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { useStripe, useElements, CardElement } from '@stripe/react-stripe-js';
|
|
5
|
+
import { useCustomer } from '../hooks/useCustomer';
|
|
6
|
+
import { Spinner } from './Spinner';
|
|
7
|
+
/**
|
|
8
|
+
* Stripe Payment Form Wrapper Component
|
|
9
|
+
* Renders inside Stripe Elements context and handles the payment flow
|
|
10
|
+
* All hooks are called unconditionally to comply with React Rules of Hooks
|
|
11
|
+
*/
|
|
12
|
+
export const StripePaymentFormWrapper = ({ onSuccess, onError, returnUrl, submitButtonText = 'Pay Now', buttonClassName, clientSecret, }) => {
|
|
13
|
+
// Always call hooks unconditionally
|
|
14
|
+
const stripe = useStripe();
|
|
15
|
+
const elements = useElements();
|
|
16
|
+
const customer = useCustomer();
|
|
17
|
+
const [isProcessing, setIsProcessing] = useState(false);
|
|
18
|
+
const [message, setMessage] = useState(null);
|
|
19
|
+
const [cardComplete, setCardComplete] = useState(false);
|
|
20
|
+
const handleSubmit = async (event) => {
|
|
21
|
+
event.preventDefault();
|
|
22
|
+
// Double-check Stripe availability (defensive programming)
|
|
23
|
+
if (!stripe || !elements) {
|
|
24
|
+
const errorMessage = 'Stripe is not available. Please refresh the page.';
|
|
25
|
+
setMessage(errorMessage);
|
|
26
|
+
if (onError) {
|
|
27
|
+
onError(new Error(errorMessage));
|
|
28
|
+
}
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (!clientSecret) {
|
|
32
|
+
const errorMessage = 'Payment intent not available. Please refresh the page.';
|
|
33
|
+
setMessage(errorMessage);
|
|
34
|
+
if (onError) {
|
|
35
|
+
onError(new Error(errorMessage));
|
|
36
|
+
}
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
setIsProcessing(true);
|
|
40
|
+
setMessage(null);
|
|
41
|
+
try {
|
|
42
|
+
const cardElement = elements.getElement(CardElement);
|
|
43
|
+
if (!cardElement) {
|
|
44
|
+
const errorMessage = 'Card element not found';
|
|
45
|
+
setMessage(errorMessage);
|
|
46
|
+
setIsProcessing(false);
|
|
47
|
+
if (onError) {
|
|
48
|
+
onError(new Error(errorMessage));
|
|
49
|
+
}
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
// Confirm the payment using the clientSecret from the payment intent
|
|
53
|
+
const { error, paymentIntent } = await stripe.confirmCardPayment(clientSecret, {
|
|
54
|
+
payment_method: {
|
|
55
|
+
card: cardElement,
|
|
56
|
+
billing_details: {
|
|
57
|
+
email: customer.email,
|
|
58
|
+
name: customer.name,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
if (error) {
|
|
63
|
+
// Show error to customer (e.g., payment details are invalid)
|
|
64
|
+
const errorMessage = error.message || 'An unexpected error occurred.';
|
|
65
|
+
setMessage(errorMessage);
|
|
66
|
+
setIsProcessing(false);
|
|
67
|
+
if (onError) {
|
|
68
|
+
onError(new Error(errorMessage));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else if (paymentIntent && paymentIntent.status === 'succeeded') {
|
|
72
|
+
// Payment succeeded on Stripe side - now wait for backend processing
|
|
73
|
+
// Don't show any message during processing - wait for backend to confirm
|
|
74
|
+
setMessage(null);
|
|
75
|
+
if (onSuccess) {
|
|
76
|
+
try {
|
|
77
|
+
// Wait for backend processing to complete
|
|
78
|
+
await onSuccess(paymentIntent);
|
|
79
|
+
// Backend processing completed - now show success
|
|
80
|
+
setMessage('Payment successful!');
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
// Backend processing failed
|
|
84
|
+
const error = err instanceof Error ? err : new Error('Payment processing failed');
|
|
85
|
+
setMessage('Payment processing failed. Please try again or contact support.');
|
|
86
|
+
setIsProcessing(false);
|
|
87
|
+
if (onError) {
|
|
88
|
+
onError(error);
|
|
89
|
+
}
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
// No onSuccess callback - show success immediately
|
|
95
|
+
setMessage('Payment successful!');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
else if (paymentIntent && paymentIntent.status === 'requires_action') {
|
|
99
|
+
// Payment requires additional action (3D Secure, etc.)
|
|
100
|
+
setMessage('Payment requires additional authentication. Please complete the verification.');
|
|
101
|
+
setIsProcessing(false);
|
|
102
|
+
}
|
|
103
|
+
else if (paymentIntent) {
|
|
104
|
+
// Payment is processing or requires additional action
|
|
105
|
+
setMessage(`Payment status: ${paymentIntent.status || 'processing'}`);
|
|
106
|
+
setIsProcessing(false);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
const error = err instanceof Error ? err : new Error('Unknown error occurred');
|
|
111
|
+
setMessage(error.message);
|
|
112
|
+
if (onError) {
|
|
113
|
+
onError(error);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
setIsProcessing(false);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
const isSuccess = message?.includes('successful') ?? false;
|
|
121
|
+
const isReady = !!(stripe && elements);
|
|
122
|
+
// Card element options for minimal styling
|
|
123
|
+
const cardElementOptions = {
|
|
124
|
+
style: {
|
|
125
|
+
base: {
|
|
126
|
+
fontSize: '16px',
|
|
127
|
+
color: '#424770',
|
|
128
|
+
'::placeholder': {
|
|
129
|
+
color: '#aab7c4',
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
invalid: {
|
|
133
|
+
color: '#9e2146',
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
hidePostalCode: true,
|
|
137
|
+
};
|
|
138
|
+
return (_jsxs(_Fragment, { children: [isReady ? (_jsx(CardElement, { options: cardElementOptions, onChange: e => {
|
|
139
|
+
if (e.error) {
|
|
140
|
+
setMessage(e.error.message);
|
|
141
|
+
setCardComplete(false);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
setMessage(null);
|
|
145
|
+
setCardComplete(e.complete);
|
|
146
|
+
}
|
|
147
|
+
} })) : (_jsx("div", { style: {
|
|
148
|
+
padding: '12px',
|
|
149
|
+
border: '1px solid #cbd5e1',
|
|
150
|
+
borderRadius: '0.375rem',
|
|
151
|
+
backgroundColor: 'white',
|
|
152
|
+
display: 'flex',
|
|
153
|
+
alignItems: 'center',
|
|
154
|
+
justifyContent: 'center',
|
|
155
|
+
minHeight: '40px',
|
|
156
|
+
}, children: _jsx(Spinner, { size: "sm" }) })), message && !isSuccess && (_jsx("div", { role: "alert", "aria-live": "assertive", "aria-atomic": "true", children: message })), _jsx("button", { type: "submit", disabled: !isReady || !cardComplete || isProcessing || !clientSecret, className: buttonClassName, "aria-busy": isProcessing, "aria-disabled": !isReady || !cardComplete || isProcessing || !clientSecret, onClick: handleSubmit, children: isProcessing ? (_jsxs(_Fragment, { children: [_jsx(Spinner, { size: "sm" }), _jsx("span", { children: "Processing..." })] })) : (submitButtonText) })] }));
|
|
157
|
+
};
|
|
158
|
+
//# sourceMappingURL=StripePaymentFormWrapper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StripePaymentFormWrapper.js","sourceRoot":"","sources":["../../../src/components/StripePaymentFormWrapper.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AACZ,OAAc,EAAE,QAAQ,EAAa,MAAM,OAAO,CAAA;AAClD,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAC7E,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAWnC;;;;GAIG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAA4C,CAAC,EAChF,SAAS,EACT,OAAO,EACP,SAAS,EACT,gBAAgB,GAAG,SAAS,EAC5B,eAAe,EACf,YAAY,GACb,EAAE,EAAE;IACH,oCAAoC;IACpC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAC1B,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAA;IAC9B,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAA;IAC9B,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IACvD,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAA;IAC3D,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAEvD,MAAM,YAAY,GAAG,KAAK,EAAE,KAA0C,EAAE,EAAE;QACxE,KAAK,CAAC,cAAc,EAAE,CAAA;QAEtB,2DAA2D;QAC3D,IAAI,CAAC,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;YACzB,MAAM,YAAY,GAAG,mDAAmD,CAAA;YACxE,UAAU,CAAC,YAAY,CAAC,CAAA;YACxB,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO,CAAC,IAAI,KAAK,CAAC,YAAY,CAAC,CAAC,CAAA;YAClC,CAAC;YACD,OAAM;QACR,CAAC;QAED,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,MAAM,YAAY,GAAG,wDAAwD,CAAA;YAC7E,UAAU,CAAC,YAAY,CAAC,CAAA;YACxB,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO,CAAC,IAAI,KAAK,CAAC,YAAY,CAAC,CAAC,CAAA;YAClC,CAAC;YACD,OAAM;QACR,CAAC;QAED,eAAe,CAAC,IAAI,CAAC,CAAA;QACrB,UAAU,CAAC,IAAI,CAAC,CAAA;QAEhB,IAAI,CAAC;YACH,MAAM,WAAW,GAAG,QAAQ,CAAC,UAAU,CAAC,WAAW,CAAC,CAAA;YACpD,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,MAAM,YAAY,GAAG,wBAAwB,CAAA;gBAC7C,UAAU,CAAC,YAAY,CAAC,CAAA;gBACxB,eAAe,CAAC,KAAK,CAAC,CAAA;gBACtB,IAAI,OAAO,EAAE,CAAC;oBACZ,OAAO,CAAC,IAAI,KAAK,CAAC,YAAY,CAAC,CAAC,CAAA;gBAClC,CAAC;gBACD,OAAM;YACR,CAAC;YAED,qEAAqE;YACrE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,YAAY,EAAE;gBAC7E,cAAc,EAAE;oBACd,IAAI,EAAE,WAAW;oBACjB,eAAe,EAAE;wBACf,KAAK,EAAE,QAAQ,CAAC,KAAK;wBACrB,IAAI,EAAE,QAAQ,CAAC,IAAI;qBACpB;iBACF;aACF,CAAC,CAAA;YAEF,IAAI,KAAK,EAAE,CAAC;gBACV,6DAA6D;gBAC7D,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,IAAI,+BAA+B,CAAA;gBACrE,UAAU,CAAC,YAAY,CAAC,CAAA;gBACxB,eAAe,CAAC,KAAK,CAAC,CAAA;gBAEtB,IAAI,OAAO,EAAE,CAAC;oBACZ,OAAO,CAAC,IAAI,KAAK,CAAC,YAAY,CAAC,CAAC,CAAA;gBAClC,CAAC;YACH,CAAC;iBAAM,IAAI,aAAa,IAAI,aAAa,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;gBACjE,qEAAqE;gBACrE,yEAAyE;gBACzE,UAAU,CAAC,IAAI,CAAC,CAAA;gBAEhB,IAAI,SAAS,EAAE,CAAC;oBACd,IAAI,CAAC;wBACH,0CAA0C;wBAC1C,MAAM,SAAS,CAAC,aAAa,CAAC,CAAA;wBAC9B,kDAAkD;wBAClD,UAAU,CAAC,qBAAqB,CAAC,CAAA;oBACnC,CAAC;oBAAC,OAAO,GAAG,EAAE,CAAC;wBACb,4BAA4B;wBAC5B,MAAM,KAAK,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAA;wBACjF,UAAU,CAAC,iEAAiE,CAAC,CAAA;wBAC7E,eAAe,CAAC,KAAK,CAAC,CAAA;wBAEtB,IAAI,OAAO,EAAE,CAAC;4BACZ,OAAO,CAAC,KAAK,CAAC,CAAA;wBAChB,CAAC;wBACD,OAAM;oBACR,CAAC;gBACH,CAAC;qBAAM,CAAC;oBACN,mDAAmD;oBACnD,UAAU,CAAC,qBAAqB,CAAC,CAAA;gBACnC,CAAC;YACH,CAAC;iBAAM,IAAI,aAAa,IAAI,aAAa,CAAC,MAAM,KAAK,iBAAiB,EAAE,CAAC;gBACvE,uDAAuD;gBACvD,UAAU,CAAC,+EAA+E,CAAC,CAAA;gBAC3F,eAAe,CAAC,KAAK,CAAC,CAAA;YACxB,CAAC;iBAAM,IAAI,aAAa,EAAE,CAAC;gBACzB,sDAAsD;gBACtD,UAAU,CAAC,mBAAmB,aAAa,CAAC,MAAM,IAAI,YAAY,EAAE,CAAC,CAAA;gBACrE,eAAe,CAAC,KAAK,CAAC,CAAA;YACxB,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,KAAK,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAA;YAC9E,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;YAEzB,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO,CAAC,KAAK,CAAC,CAAA;YAChB,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,eAAe,CAAC,KAAK,CAAC,CAAA;QACxB,CAAC;IACH,CAAC,CAAA;IAED,MAAM,SAAS,GAAG,OAAO,EAAE,QAAQ,CAAC,YAAY,CAAC,IAAI,KAAK,CAAA;IAE1D,MAAM,OAAO,GAAG,CAAC,CAAC,CAAC,MAAM,IAAI,QAAQ,CAAC,CAAA;IAEtC,2CAA2C;IAC3C,MAAM,kBAAkB,GAAG;QACzB,KAAK,EAAE;YACL,IAAI,EAAE;gBACJ,QAAQ,EAAE,MAAM;gBAChB,KAAK,EAAE,SAAS;gBAChB,eAAe,EAAE;oBACf,KAAK,EAAE,SAAS;iBACjB;aACF;YACD,OAAO,EAAE;gBACP,KAAK,EAAE,SAAS;aACjB;SACF;QACD,cAAc,EAAE,IAAI;KACrB,CAAA;IAED,OAAO,CACL,8BAEG,OAAO,CAAC,CAAC,CAAC,CACT,KAAC,WAAW,IACV,OAAO,EAAE,kBAAkB,EAC3B,QAAQ,EAAE,CAAC,CAAC,EAAE;oBACZ,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;wBACZ,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;wBAC3B,eAAe,CAAC,KAAK,CAAC,CAAA;oBACxB,CAAC;yBAAM,CAAC;wBACN,UAAU,CAAC,IAAI,CAAC,CAAA;wBAChB,eAAe,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAA;oBAC7B,CAAC;gBACH,CAAC,GACD,CACH,CAAC,CAAC,CAAC,CACF,cACE,KAAK,EAAE;oBACL,OAAO,EAAE,MAAM;oBACf,MAAM,EAAE,mBAAmB;oBAC3B,YAAY,EAAE,UAAU;oBACxB,eAAe,EAAE,OAAO;oBACxB,OAAO,EAAE,MAAM;oBACf,UAAU,EAAE,QAAQ;oBACpB,cAAc,EAAE,QAAQ;oBACxB,SAAS,EAAE,MAAM;iBAClB,YAED,KAAC,OAAO,IAAC,IAAI,EAAC,IAAI,GAAG,GACjB,CACP,EAEA,OAAO,IAAI,CAAC,SAAS,IAAI,CACxB,cAAK,IAAI,EAAC,OAAO,eAAW,WAAW,iBAAa,MAAM,YACvD,OAAO,GACJ,CACP,EAED,iBACE,IAAI,EAAC,QAAQ,EACb,QAAQ,EAAE,CAAC,OAAO,IAAI,CAAC,YAAY,IAAI,YAAY,IAAI,CAAC,YAAY,EACpE,SAAS,EAAE,eAAe,eACf,YAAY,mBACR,CAAC,OAAO,IAAI,CAAC,YAAY,IAAI,YAAY,IAAI,CAAC,YAAY,EACzE,OAAO,EAAE,YAAY,YAEpB,YAAY,CAAC,CAAC,CAAC,CACd,8BACE,KAAC,OAAO,IAAC,IAAI,EAAC,IAAI,GAAG,EACrB,2CAA0B,IACzB,CACJ,CAAC,CAAC,CAAC,CACF,gBAAgB,CACjB,GACM,IACR,CACJ,CAAA;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { SubscriptionGateProps } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Headless Subscription Gate Component
|
|
5
|
+
*
|
|
6
|
+
* Controls access to content based on subscription status.
|
|
7
|
+
* Uses render props to give developers full control over locked/unlocked states.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```tsx
|
|
11
|
+
* <SubscriptionGate requirePlan="Pro Plan">
|
|
12
|
+
* {({ hasAccess, loading }) => {
|
|
13
|
+
* if (loading) return <Skeleton />;
|
|
14
|
+
* if (!hasAccess) return <Paywall />;
|
|
15
|
+
* return <PremiumContent />;
|
|
16
|
+
* }}
|
|
17
|
+
* </SubscriptionGate>
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare const SubscriptionGate: React.FC<SubscriptionGateProps>;
|
|
21
|
+
//# sourceMappingURL=SubscriptionGate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SubscriptionGate.d.ts","sourceRoot":"","sources":["../../../src/components/SubscriptionGate.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAA;AAErD;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CAgB5D,CAAA"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useSubscription } from '../hooks/useSubscription';
|
|
4
|
+
/**
|
|
5
|
+
* Headless Subscription Gate Component
|
|
6
|
+
*
|
|
7
|
+
* Controls access to content based on subscription status.
|
|
8
|
+
* Uses render props to give developers full control over locked/unlocked states.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```tsx
|
|
12
|
+
* <SubscriptionGate requirePlan="Pro Plan">
|
|
13
|
+
* {({ hasAccess, loading }) => {
|
|
14
|
+
* if (loading) return <Skeleton />;
|
|
15
|
+
* if (!hasAccess) return <Paywall />;
|
|
16
|
+
* return <PremiumContent />;
|
|
17
|
+
* }}
|
|
18
|
+
* </SubscriptionGate>
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export const SubscriptionGate = ({ requirePlan, children }) => {
|
|
22
|
+
const { subscriptions, loading, hasPlan } = useSubscription();
|
|
23
|
+
const hasAccess = requirePlan
|
|
24
|
+
? hasPlan(requirePlan)
|
|
25
|
+
: subscriptions.some(sub => sub.status === 'active');
|
|
26
|
+
return (_jsx(_Fragment, { children: children({
|
|
27
|
+
hasAccess,
|
|
28
|
+
subscriptions,
|
|
29
|
+
loading,
|
|
30
|
+
}) }));
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=SubscriptionGate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SubscriptionGate.js","sourceRoot":"","sources":["../../../src/components/SubscriptionGate.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAG1D;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAoC,CAAC,EAAE,WAAW,EAAE,QAAQ,EAAE,EAAE,EAAE;IAC7F,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,eAAe,EAAE,CAAA;IAE7D,MAAM,SAAS,GAAG,WAAW;QAC3B,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC;QACtB,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAA;IAEtD,OAAO,CACL,4BACG,QAAQ,CAAC;YACR,SAAS;YACT,aAAa;YACb,OAAO;SACR,CAAC,GACD,CACJ,CAAA;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSubscription.test.d.ts","sourceRoot":"","sources":["../../../../src/hooks/__tests__/useSubscription.test.tsx"],"names":[],"mappings":""}
|