@umituz/react-native-subscription 2.27.42 → 2.27.44
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/package.json +1 -1
- package/src/domains/paywall/components/PaywallContainer.tsx +7 -19
- package/src/domains/paywall/components/PaywallContainer.types.ts +6 -14
- package/src/domains/paywall/components/PaywallModal.tsx +2 -0
- package/src/domains/paywall/components/PlanCard.tsx +2 -0
- package/src/domains/paywall/components/index.ts +0 -2
- package/src/domains/paywall/entities/types.ts +0 -22
- package/src/domains/paywall/hooks/index.ts +0 -1
- package/src/utils/index.ts +0 -1
- package/src/domains/paywall/components/CreditCard.tsx +0 -120
- package/src/domains/paywall/components/PaywallTabBar.tsx +0 -77
- package/src/domains/paywall/hooks/usePaywall.ts +0 -54
- package/src/utils/packageFilter.ts +0 -62
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-subscription",
|
|
3
|
-
"version": "2.27.
|
|
3
|
+
"version": "2.27.44",
|
|
4
4
|
"description": "Complete subscription management with RevenueCat, paywall UI, and credits system for React Native apps",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -8,8 +8,6 @@ import React, { useMemo, useEffect } from "react";
|
|
|
8
8
|
import { usePaywallVisibility } from "../../../presentation/hooks/usePaywallVisibility";
|
|
9
9
|
import { useSubscriptionPackages } from "../../../revenuecat/presentation/hooks/useSubscriptionPackages";
|
|
10
10
|
import { useRevenueCatTrialEligibility } from "../../../revenuecat/presentation/hooks/useRevenueCatTrialEligibility";
|
|
11
|
-
import { filterPackagesByMode } from "../../../utils/packageFilter";
|
|
12
|
-
import { createCreditAmountsFromPackages } from "../../../utils/creditMapper";
|
|
13
11
|
import { PaywallModal, type TrialEligibilityInfo } from "./PaywallModal";
|
|
14
12
|
import { usePaywallActions } from "../hooks/usePaywallActions";
|
|
15
13
|
import type { PaywallContainerProps } from "./PaywallContainer.types";
|
|
@@ -17,15 +15,12 @@ import type { PaywallContainerProps } from "./PaywallContainer.types";
|
|
|
17
15
|
export const PaywallContainer: React.FC<PaywallContainerProps> = (props) => {
|
|
18
16
|
const {
|
|
19
17
|
translations,
|
|
20
|
-
mode = "subscription",
|
|
21
18
|
legalUrls,
|
|
22
19
|
features,
|
|
23
20
|
heroImage,
|
|
24
21
|
bestValueIdentifier,
|
|
25
|
-
creditsLabel,
|
|
26
22
|
creditAmounts,
|
|
27
|
-
|
|
28
|
-
packageFilterConfig,
|
|
23
|
+
creditsLabel,
|
|
29
24
|
source,
|
|
30
25
|
onPurchaseSuccess,
|
|
31
26
|
onPurchaseError,
|
|
@@ -41,7 +36,7 @@ export const PaywallContainer: React.FC<PaywallContainerProps> = (props) => {
|
|
|
41
36
|
|
|
42
37
|
const purchaseSource = source ?? currentSource ?? "settings";
|
|
43
38
|
|
|
44
|
-
const { data:
|
|
39
|
+
const { data: packages = [], isLoading } = useSubscriptionPackages();
|
|
45
40
|
const { eligibilityMap, checkEligibility } = useRevenueCatTrialEligibility();
|
|
46
41
|
const { handlePurchase, handleRestore } = usePaywallActions({
|
|
47
42
|
source: purchaseSource,
|
|
@@ -54,10 +49,10 @@ export const PaywallContainer: React.FC<PaywallContainerProps> = (props) => {
|
|
|
54
49
|
// Check trial eligibility only if trialConfig is enabled
|
|
55
50
|
useEffect(() => {
|
|
56
51
|
if (!trialConfig?.enabled) return;
|
|
57
|
-
if (
|
|
52
|
+
if (packages.length === 0) return;
|
|
58
53
|
|
|
59
54
|
// Get all actual product IDs from packages
|
|
60
|
-
const allProductIds =
|
|
55
|
+
const allProductIds = packages.map((pkg) => pkg.product.identifier);
|
|
61
56
|
|
|
62
57
|
// If eligibleProductIds are provided, filter to matching packages (partial match)
|
|
63
58
|
// e.g., "yearly" matches "futureus.yearly"
|
|
@@ -75,7 +70,7 @@ export const PaywallContainer: React.FC<PaywallContainerProps> = (props) => {
|
|
|
75
70
|
if (productIdsToCheck.length > 0) {
|
|
76
71
|
checkEligibility(productIdsToCheck);
|
|
77
72
|
}
|
|
78
|
-
}, [
|
|
73
|
+
}, [packages, checkEligibility, trialConfig?.enabled, trialConfig?.eligibleProductIds]);
|
|
79
74
|
|
|
80
75
|
// Convert eligibility map to format expected by PaywallModal
|
|
81
76
|
// Only process if trial is enabled
|
|
@@ -92,13 +87,6 @@ export const PaywallContainer: React.FC<PaywallContainerProps> = (props) => {
|
|
|
92
87
|
return result;
|
|
93
88
|
}, [eligibilityMap, trialConfig?.enabled, trialConfig?.durationDays]);
|
|
94
89
|
|
|
95
|
-
const { filteredPackages, computedCreditAmounts } = useMemo(() => ({
|
|
96
|
-
filteredPackages: filterPackagesByMode(allPackages, mode, packageFilterConfig),
|
|
97
|
-
computedCreditAmounts: mode !== "subscription" && !creditAmounts
|
|
98
|
-
? createCreditAmountsFromPackages(allPackages, packageAllocations)
|
|
99
|
-
: creditAmounts
|
|
100
|
-
}), [allPackages, mode, packageFilterConfig, creditAmounts, packageAllocations]);
|
|
101
|
-
|
|
102
90
|
if (!isVisible) return null;
|
|
103
91
|
|
|
104
92
|
return (
|
|
@@ -106,14 +94,14 @@ export const PaywallContainer: React.FC<PaywallContainerProps> = (props) => {
|
|
|
106
94
|
visible={isVisible}
|
|
107
95
|
onClose={handleClose}
|
|
108
96
|
translations={translations}
|
|
109
|
-
packages={
|
|
97
|
+
packages={packages}
|
|
110
98
|
isLoading={isLoading}
|
|
111
99
|
legalUrls={legalUrls}
|
|
112
100
|
features={features ? [...features] : undefined}
|
|
113
101
|
heroImage={heroImage}
|
|
114
102
|
bestValueIdentifier={bestValueIdentifier}
|
|
103
|
+
creditAmounts={creditAmounts}
|
|
115
104
|
creditsLabel={creditsLabel}
|
|
116
|
-
creditAmounts={computedCreditAmounts}
|
|
117
105
|
onPurchase={handlePurchase}
|
|
118
106
|
onRestore={handleRestore}
|
|
119
107
|
trialEligibility={trialEligibility}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PaywallContainer Types
|
|
3
|
-
* Props for
|
|
3
|
+
* Props for subscription paywall
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { ImageSourcePropType } from "react-native";
|
|
7
|
-
import type {
|
|
8
|
-
import type {
|
|
9
|
-
import type { PurchaseSource, PackageAllocationMap } from "../../../domain/entities/Credits";
|
|
7
|
+
import type { PaywallTranslations, PaywallLegalUrls, SubscriptionFeature } from "../entities";
|
|
8
|
+
import type { PurchaseSource } from "../../../domain/entities/Credits";
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
11
|
* Trial display configuration
|
|
@@ -26,8 +25,6 @@ export interface TrialConfig {
|
|
|
26
25
|
export interface PaywallContainerProps {
|
|
27
26
|
/** Paywall translations - no defaults, must be provided */
|
|
28
27
|
readonly translations: PaywallTranslations;
|
|
29
|
-
/** Paywall mode - subscription, credits, or hybrid */
|
|
30
|
-
readonly mode?: PaywallMode;
|
|
31
28
|
/** Legal URLs for privacy and terms */
|
|
32
29
|
readonly legalUrls?: PaywallLegalUrls;
|
|
33
30
|
/** Feature list to display */
|
|
@@ -36,14 +33,10 @@ export interface PaywallContainerProps {
|
|
|
36
33
|
readonly heroImage?: ImageSourcePropType;
|
|
37
34
|
/** Best value package identifier for badge */
|
|
38
35
|
readonly bestValueIdentifier?: string;
|
|
39
|
-
/**
|
|
40
|
-
readonly creditsLabel?: string;
|
|
41
|
-
/** Credit amounts per package identifier */
|
|
36
|
+
/** Credit amounts per product identifier */
|
|
42
37
|
readonly creditAmounts?: Record<string, number>;
|
|
43
|
-
/**
|
|
44
|
-
readonly
|
|
45
|
-
/** Custom filter config for package categorization */
|
|
46
|
-
readonly packageFilterConfig?: PackageFilterConfig;
|
|
38
|
+
/** Credits label text (e.g., "credits") */
|
|
39
|
+
readonly creditsLabel?: string;
|
|
47
40
|
/** Source of the paywall - affects pending purchase handling */
|
|
48
41
|
readonly source?: PurchaseSource;
|
|
49
42
|
/** Callback when purchase succeeds */
|
|
@@ -59,4 +52,3 @@ export interface PaywallContainerProps {
|
|
|
59
52
|
/** Trial display configuration (Apple-compliant) */
|
|
60
53
|
readonly trialConfig?: TrialConfig;
|
|
61
54
|
}
|
|
62
|
-
|
|
@@ -33,7 +33,9 @@ export interface PaywallModalProps {
|
|
|
33
33
|
isLoading?: boolean;
|
|
34
34
|
legalUrls?: PaywallLegalUrls;
|
|
35
35
|
bestValueIdentifier?: string;
|
|
36
|
+
/** Credit amounts per product identifier */
|
|
36
37
|
creditAmounts?: Record<string, number>;
|
|
38
|
+
/** Credits label text (e.g., "credits") */
|
|
37
39
|
creditsLabel?: string;
|
|
38
40
|
heroImage?: ImageSourcePropType;
|
|
39
41
|
onPurchase?: (pkg: PurchasesPackage) => Promise<void | boolean>;
|
|
@@ -21,7 +21,9 @@ interface PlanCardProps {
|
|
|
21
21
|
onSelect: () => void;
|
|
22
22
|
/** Badge text (e.g., "Best Value") - NOT for trial */
|
|
23
23
|
badge?: string;
|
|
24
|
+
/** Credit amount for this plan */
|
|
24
25
|
creditAmount?: number;
|
|
26
|
+
/** Credits label text (e.g., "credits") */
|
|
25
27
|
creditsLabel?: string;
|
|
26
28
|
/** Whether this plan has a free trial (Apple-compliant display) */
|
|
27
29
|
hasFreeTrial?: boolean;
|
|
@@ -9,9 +9,7 @@ export { PaywallModal } from "./PaywallModal";
|
|
|
9
9
|
export type { PaywallModalProps } from "./PaywallModal";
|
|
10
10
|
|
|
11
11
|
export { PaywallHeader } from "./PaywallHeader";
|
|
12
|
-
export { PaywallTabBar } from "./PaywallTabBar";
|
|
13
12
|
export { PaywallFooter } from "./PaywallFooter";
|
|
14
13
|
export { FeatureList } from "./FeatureList";
|
|
15
14
|
export { FeatureItem } from "./FeatureItem";
|
|
16
15
|
export { PlanCard } from "./PlanCard";
|
|
17
|
-
export { CreditCard } from "./CreditCard";
|
|
@@ -3,25 +3,6 @@
|
|
|
3
3
|
* All paywall-related type definitions
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
export type PaywallMode = "subscription" | "credits" | "hybrid";
|
|
7
|
-
|
|
8
|
-
export type PaywallTabType = "credits" | "subscription";
|
|
9
|
-
|
|
10
|
-
export interface PaywallTab {
|
|
11
|
-
id: PaywallTabType;
|
|
12
|
-
label: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface CreditsPackage {
|
|
16
|
-
id: string;
|
|
17
|
-
credits: number;
|
|
18
|
-
price: number;
|
|
19
|
-
currency: string;
|
|
20
|
-
bonus?: number;
|
|
21
|
-
badge?: string;
|
|
22
|
-
description?: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
6
|
export interface SubscriptionFeature {
|
|
26
7
|
icon: string;
|
|
27
8
|
text: string;
|
|
@@ -30,10 +11,7 @@ export interface SubscriptionFeature {
|
|
|
30
11
|
export interface PaywallTranslations {
|
|
31
12
|
title: string;
|
|
32
13
|
subtitle?: string;
|
|
33
|
-
creditsTabLabel?: string;
|
|
34
|
-
subscriptionTabLabel?: string;
|
|
35
14
|
purchaseButtonText: string;
|
|
36
|
-
subscribeButtonText?: string;
|
|
37
15
|
restoreButtonText: string;
|
|
38
16
|
loadingText: string;
|
|
39
17
|
emptyText: string;
|
package/src/utils/index.ts
CHANGED
|
@@ -2,7 +2,6 @@ export * from "./aiCreditHelpers";
|
|
|
2
2
|
export * from "./authUtils";
|
|
3
3
|
export * from "./creditChecker";
|
|
4
4
|
export * from "./creditMapper";
|
|
5
|
-
export * from "./packageFilter";
|
|
6
5
|
export * from "./packagePeriodUtils";
|
|
7
6
|
export * from "./packageTypeDetector";
|
|
8
7
|
export * from "./premiumStatusUtils";
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Credit Card
|
|
3
|
-
* Credit package selection card
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import React from "react";
|
|
7
|
-
import { View, TouchableOpacity, StyleSheet } from "react-native";
|
|
8
|
-
import { AtomicText, AtomicIcon, AtomicBadge, useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
9
|
-
import type { CreditsPackage } from '../entities';
|
|
10
|
-
|
|
11
|
-
import { formatPrice } from '../../../utils/priceUtils';
|
|
12
|
-
|
|
13
|
-
interface CreditCardProps {
|
|
14
|
-
pkg: CreditsPackage;
|
|
15
|
-
isSelected: boolean;
|
|
16
|
-
onSelect: () => void;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export const CreditCard: React.FC<CreditCardProps> = React.memo(({ pkg, isSelected, onSelect }) => {
|
|
20
|
-
const tokens = useAppDesignTokens();
|
|
21
|
-
const totalCredits = pkg.credits + (pkg.bonus ?? 0);
|
|
22
|
-
const price = formatPrice(pkg.price, pkg.currency);
|
|
23
|
-
|
|
24
|
-
return (
|
|
25
|
-
<TouchableOpacity onPress={onSelect} activeOpacity={0.7} style={styles.touchable}>
|
|
26
|
-
<View
|
|
27
|
-
style={[
|
|
28
|
-
styles.container,
|
|
29
|
-
{
|
|
30
|
-
backgroundColor: tokens.colors.surface,
|
|
31
|
-
borderColor: isSelected ? tokens.colors.primary : tokens.colors.border,
|
|
32
|
-
borderWidth: isSelected ? 2 : 1,
|
|
33
|
-
},
|
|
34
|
-
]}
|
|
35
|
-
>
|
|
36
|
-
{pkg.badge && (
|
|
37
|
-
<View style={styles.badgeContainer}>
|
|
38
|
-
<AtomicBadge text={pkg.badge} variant="warning" size="sm" />
|
|
39
|
-
</View>
|
|
40
|
-
)}
|
|
41
|
-
|
|
42
|
-
<View style={styles.content}>
|
|
43
|
-
<View style={styles.leftSection}>
|
|
44
|
-
<AtomicIcon name="flash" size="md" color={isSelected ? "primary" : "secondary"} />
|
|
45
|
-
<AtomicText
|
|
46
|
-
type="headlineSmall"
|
|
47
|
-
style={[styles.credits, { color: isSelected ? tokens.colors.primary : tokens.colors.textPrimary }]}
|
|
48
|
-
>
|
|
49
|
-
{totalCredits.toLocaleString()}
|
|
50
|
-
</AtomicText>
|
|
51
|
-
</View>
|
|
52
|
-
|
|
53
|
-
<View style={styles.rightSection}>
|
|
54
|
-
<AtomicText
|
|
55
|
-
type="titleLarge"
|
|
56
|
-
style={[styles.price, { color: isSelected ? tokens.colors.primary : tokens.colors.textPrimary }]}
|
|
57
|
-
>
|
|
58
|
-
{price}
|
|
59
|
-
</AtomicText>
|
|
60
|
-
{isSelected && <AtomicIcon name="checkmark-circle" size="md" color="primary" />}
|
|
61
|
-
</View>
|
|
62
|
-
</View>
|
|
63
|
-
|
|
64
|
-
{(pkg.bonus ?? 0) > 0 && (
|
|
65
|
-
<View style={styles.bonusRow}>
|
|
66
|
-
<AtomicIcon name="gift-outline" size="sm" color="success" />
|
|
67
|
-
<AtomicText type="bodySmall" style={{ color: tokens.colors.success, marginLeft: 4 }}>
|
|
68
|
-
+{pkg.bonus}
|
|
69
|
-
</AtomicText>
|
|
70
|
-
</View>
|
|
71
|
-
)}
|
|
72
|
-
</View>
|
|
73
|
-
</TouchableOpacity>
|
|
74
|
-
);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
CreditCard.displayName = "CreditCard";
|
|
78
|
-
|
|
79
|
-
const styles = StyleSheet.create({
|
|
80
|
-
touchable: {
|
|
81
|
-
marginBottom: 10,
|
|
82
|
-
marginHorizontal: 24,
|
|
83
|
-
},
|
|
84
|
-
container: {
|
|
85
|
-
borderRadius: 16,
|
|
86
|
-
padding: 16,
|
|
87
|
-
position: "relative",
|
|
88
|
-
},
|
|
89
|
-
badgeContainer: {
|
|
90
|
-
position: "absolute",
|
|
91
|
-
top: -10,
|
|
92
|
-
right: 16,
|
|
93
|
-
},
|
|
94
|
-
content: {
|
|
95
|
-
flexDirection: "row",
|
|
96
|
-
justifyContent: "space-between",
|
|
97
|
-
alignItems: "center",
|
|
98
|
-
},
|
|
99
|
-
leftSection: {
|
|
100
|
-
flexDirection: "row",
|
|
101
|
-
alignItems: "center",
|
|
102
|
-
},
|
|
103
|
-
credits: {
|
|
104
|
-
fontWeight: "700",
|
|
105
|
-
marginLeft: 8,
|
|
106
|
-
},
|
|
107
|
-
rightSection: {
|
|
108
|
-
flexDirection: "row",
|
|
109
|
-
alignItems: "center",
|
|
110
|
-
},
|
|
111
|
-
price: {
|
|
112
|
-
fontWeight: "700",
|
|
113
|
-
marginRight: 8,
|
|
114
|
-
},
|
|
115
|
-
bonusRow: {
|
|
116
|
-
flexDirection: "row",
|
|
117
|
-
alignItems: "center",
|
|
118
|
-
marginTop: 8,
|
|
119
|
-
},
|
|
120
|
-
});
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Paywall Tab Bar
|
|
3
|
-
* Segmented control for hybrid mode
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import React from "react";
|
|
7
|
-
import { View, TouchableOpacity, StyleSheet } from "react-native";
|
|
8
|
-
import { AtomicText, useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
9
|
-
import type { PaywallTabType } from '../entities';
|
|
10
|
-
|
|
11
|
-
interface PaywallTabBarProps {
|
|
12
|
-
activeTab: PaywallTabType;
|
|
13
|
-
onTabChange: (tab: PaywallTabType) => void;
|
|
14
|
-
creditsLabel: string;
|
|
15
|
-
subscriptionLabel: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export const PaywallTabBar: React.FC<PaywallTabBarProps> = React.memo(
|
|
19
|
-
({ activeTab, onTabChange, creditsLabel, subscriptionLabel }) => {
|
|
20
|
-
const tokens = useAppDesignTokens();
|
|
21
|
-
|
|
22
|
-
const renderTab = (tab: PaywallTabType, label: string) => {
|
|
23
|
-
const isActive = activeTab === tab;
|
|
24
|
-
|
|
25
|
-
return (
|
|
26
|
-
<TouchableOpacity
|
|
27
|
-
key={tab}
|
|
28
|
-
style={[
|
|
29
|
-
styles.tab,
|
|
30
|
-
isActive ? { backgroundColor: tokens.colors.surface } : undefined,
|
|
31
|
-
]}
|
|
32
|
-
onPress={() => onTabChange(tab)}
|
|
33
|
-
activeOpacity={0.7}
|
|
34
|
-
>
|
|
35
|
-
<AtomicText
|
|
36
|
-
type="labelLarge"
|
|
37
|
-
style={[
|
|
38
|
-
styles.tabText,
|
|
39
|
-
{ color: isActive ? tokens.colors.primary : tokens.colors.textSecondary },
|
|
40
|
-
]}
|
|
41
|
-
>
|
|
42
|
-
{label}
|
|
43
|
-
</AtomicText>
|
|
44
|
-
</TouchableOpacity>
|
|
45
|
-
);
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
return (
|
|
49
|
-
<View style={[styles.container, { backgroundColor: tokens.colors.surfaceSecondary }]}>
|
|
50
|
-
{renderTab("credits", creditsLabel)}
|
|
51
|
-
{renderTab("subscription", subscriptionLabel)}
|
|
52
|
-
</View>
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
PaywallTabBar.displayName = "PaywallTabBar";
|
|
58
|
-
|
|
59
|
-
const styles = StyleSheet.create({
|
|
60
|
-
container: {
|
|
61
|
-
flexDirection: "row",
|
|
62
|
-
borderRadius: 12,
|
|
63
|
-
padding: 4,
|
|
64
|
-
marginHorizontal: 24,
|
|
65
|
-
marginBottom: 16,
|
|
66
|
-
height: 44,
|
|
67
|
-
},
|
|
68
|
-
tab: {
|
|
69
|
-
flex: 1,
|
|
70
|
-
alignItems: "center",
|
|
71
|
-
justifyContent: "center",
|
|
72
|
-
borderRadius: 8,
|
|
73
|
-
},
|
|
74
|
-
tabText: {
|
|
75
|
-
fontWeight: "600",
|
|
76
|
-
},
|
|
77
|
-
});
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { useState, useCallback } from "react";
|
|
2
|
-
import type { PurchasesPackage } from "react-native-purchases";
|
|
3
|
-
import type { PaywallTabType } from "../entities";
|
|
4
|
-
|
|
5
|
-
interface UsePaywallProps {
|
|
6
|
-
initialTab?: PaywallTabType;
|
|
7
|
-
onCreditsPurchase: (packageId: string) => Promise<void>;
|
|
8
|
-
onSubscriptionPurchase: (pkg: PurchasesPackage) => Promise<void>;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const usePaywall = ({
|
|
12
|
-
initialTab = "credits",
|
|
13
|
-
onCreditsPurchase,
|
|
14
|
-
onSubscriptionPurchase,
|
|
15
|
-
}: UsePaywallProps) => {
|
|
16
|
-
const [activeTab, setActiveTab] = useState<PaywallTabType>(initialTab);
|
|
17
|
-
const [selectedCreditsPackageId, setSelectedCreditsPackageId] = useState<string | null>(null);
|
|
18
|
-
const [selectedSubscriptionPkg, setSelectedSubscriptionPkg] = useState<PurchasesPackage | null>(null);
|
|
19
|
-
|
|
20
|
-
const handleTabChange = useCallback((tab: PaywallTabType) => {
|
|
21
|
-
setActiveTab(tab);
|
|
22
|
-
}, []);
|
|
23
|
-
|
|
24
|
-
const handleCreditsPackageSelect = useCallback((packageId: string) => {
|
|
25
|
-
setSelectedCreditsPackageId(packageId);
|
|
26
|
-
}, []);
|
|
27
|
-
|
|
28
|
-
const handleSubscriptionPackageSelect = useCallback((pkg: PurchasesPackage) => {
|
|
29
|
-
setSelectedSubscriptionPkg(pkg);
|
|
30
|
-
}, []);
|
|
31
|
-
|
|
32
|
-
const handleCreditsPurchase = useCallback(async () => {
|
|
33
|
-
if (selectedCreditsPackageId) {
|
|
34
|
-
await onCreditsPurchase(selectedCreditsPackageId);
|
|
35
|
-
}
|
|
36
|
-
}, [selectedCreditsPackageId, onCreditsPurchase]);
|
|
37
|
-
|
|
38
|
-
const handleSubscriptionPurchase = useCallback(async () => {
|
|
39
|
-
if (selectedSubscriptionPkg) {
|
|
40
|
-
await onSubscriptionPurchase(selectedSubscriptionPkg);
|
|
41
|
-
}
|
|
42
|
-
}, [selectedSubscriptionPkg, onSubscriptionPurchase]);
|
|
43
|
-
|
|
44
|
-
return {
|
|
45
|
-
activeTab,
|
|
46
|
-
selectedCreditsPackageId,
|
|
47
|
-
selectedSubscriptionPkg,
|
|
48
|
-
handleTabChange,
|
|
49
|
-
handleCreditsPackageSelect,
|
|
50
|
-
handleSubscriptionPackageSelect,
|
|
51
|
-
handleCreditsPurchase,
|
|
52
|
-
handleSubscriptionPurchase,
|
|
53
|
-
};
|
|
54
|
-
};
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Package Filter Utility
|
|
3
|
-
* Filters RevenueCat packages by type (credits vs subscription)
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { PurchasesPackage } from "react-native-purchases";
|
|
7
|
-
|
|
8
|
-
export type PackageCategory = "credits" | "subscription";
|
|
9
|
-
|
|
10
|
-
export interface PackageFilterConfig {
|
|
11
|
-
creditIdentifierPattern?: RegExp;
|
|
12
|
-
subscriptionIdentifierPattern?: RegExp;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const DEFAULT_CONFIG: PackageFilterConfig = {
|
|
16
|
-
creditIdentifierPattern: /credit/i,
|
|
17
|
-
subscriptionIdentifierPattern: /(monthly|yearly|annual|weekly|premium|subscription)/i,
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export function getPackageCategory(
|
|
21
|
-
pkg: PurchasesPackage,
|
|
22
|
-
config: PackageFilterConfig = DEFAULT_CONFIG
|
|
23
|
-
): PackageCategory {
|
|
24
|
-
const identifier = pkg.identifier.toLowerCase();
|
|
25
|
-
const productIdentifier = pkg.product.identifier.toLowerCase();
|
|
26
|
-
|
|
27
|
-
const isCreditPackage =
|
|
28
|
-
config.creditIdentifierPattern?.test(identifier) ||
|
|
29
|
-
config.creditIdentifierPattern?.test(productIdentifier);
|
|
30
|
-
|
|
31
|
-
return isCreditPackage ? "credits" : "subscription";
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function filterPackagesByMode(
|
|
35
|
-
packages: PurchasesPackage[],
|
|
36
|
-
mode: "credits" | "subscription" | "hybrid",
|
|
37
|
-
config: PackageFilterConfig = DEFAULT_CONFIG
|
|
38
|
-
): PurchasesPackage[] {
|
|
39
|
-
if (mode === "hybrid") {
|
|
40
|
-
return packages;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return packages.filter((pkg) => getPackageCategory(pkg, config) === mode);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function separatePackages(
|
|
47
|
-
packages: PurchasesPackage[],
|
|
48
|
-
config: PackageFilterConfig = DEFAULT_CONFIG
|
|
49
|
-
): { creditPackages: PurchasesPackage[]; subscriptionPackages: PurchasesPackage[] } {
|
|
50
|
-
const creditPackages: PurchasesPackage[] = [];
|
|
51
|
-
const subscriptionPackages: PurchasesPackage[] = [];
|
|
52
|
-
|
|
53
|
-
for (const pkg of packages) {
|
|
54
|
-
if (getPackageCategory(pkg, config) === "credits") {
|
|
55
|
-
creditPackages.push(pkg);
|
|
56
|
-
} else {
|
|
57
|
-
subscriptionPackages.push(pkg);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return { creditPackages, subscriptionPackages };
|
|
62
|
-
}
|