@hayanmind/monetai-react-native 0.3.0 → 0.4.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/lib/commonjs/MonetaiProvider.js +45 -23
- package/lib/commonjs/MonetaiProvider.js.map +1 -1
- package/lib/commonjs/PaywallManager.js +63 -0
- package/lib/commonjs/PaywallManager.js.map +1 -0
- package/lib/commonjs/components/Banner.js +141 -0
- package/lib/commonjs/components/Banner.js.map +1 -0
- package/lib/commonjs/components/Paywall.js +236 -0
- package/lib/commonjs/components/Paywall.js.map +1 -0
- package/lib/commonjs/components/paywall-wrappers/CompactPaywallWrapper.js +61 -0
- package/lib/commonjs/components/paywall-wrappers/CompactPaywallWrapper.js.map +1 -0
- package/lib/commonjs/components/paywall-wrappers/HighlightBenefitsPaywallWrapper.js +33 -0
- package/lib/commonjs/components/paywall-wrappers/HighlightBenefitsPaywallWrapper.js.map +1 -0
- package/lib/commonjs/components/paywall-wrappers/KeyFeatureSummaryPaywallWrapper.js +33 -0
- package/lib/commonjs/components/paywall-wrappers/KeyFeatureSummaryPaywallWrapper.js.map +1 -0
- package/lib/commonjs/components/paywall-wrappers/TextFocusedPaywallWrapper.js +33 -0
- package/lib/commonjs/components/paywall-wrappers/TextFocusedPaywallWrapper.js.map +1 -0
- package/lib/commonjs/components/paywall-wrappers/index.js +41 -0
- package/lib/commonjs/components/paywall-wrappers/index.js.map +1 -0
- package/lib/commonjs/constants/paywall.js +11 -0
- package/lib/commonjs/constants/paywall.js.map +1 -0
- package/lib/commonjs/hooks/useBanner.js +84 -0
- package/lib/commonjs/hooks/useBanner.js.map +1 -0
- package/lib/commonjs/hooks/useInterval.js +34 -0
- package/lib/commonjs/hooks/useInterval.js.map +1 -0
- package/lib/commonjs/hooks/usePaywall.js +60 -0
- package/lib/commonjs/hooks/usePaywall.js.map +1 -0
- package/lib/commonjs/index.js +54 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/lib.js +2 -2
- package/lib/module/MonetaiProvider.js +48 -25
- package/lib/module/MonetaiProvider.js.map +1 -1
- package/lib/module/PaywallManager.js +58 -0
- package/lib/module/PaywallManager.js.map +1 -0
- package/lib/module/components/Banner.js +136 -0
- package/lib/module/components/Banner.js.map +1 -0
- package/lib/module/components/Paywall.js +230 -0
- package/lib/module/components/Paywall.js.map +1 -0
- package/lib/module/components/paywall-wrappers/CompactPaywallWrapper.js +55 -0
- package/lib/module/components/paywall-wrappers/CompactPaywallWrapper.js.map +1 -0
- package/lib/module/components/paywall-wrappers/HighlightBenefitsPaywallWrapper.js +27 -0
- package/lib/module/components/paywall-wrappers/HighlightBenefitsPaywallWrapper.js.map +1 -0
- package/lib/module/components/paywall-wrappers/KeyFeatureSummaryPaywallWrapper.js +27 -0
- package/lib/module/components/paywall-wrappers/KeyFeatureSummaryPaywallWrapper.js.map +1 -0
- package/lib/module/components/paywall-wrappers/TextFocusedPaywallWrapper.js +27 -0
- package/lib/module/components/paywall-wrappers/TextFocusedPaywallWrapper.js.map +1 -0
- package/lib/module/components/paywall-wrappers/index.js +11 -0
- package/lib/module/components/paywall-wrappers/index.js.map +1 -0
- package/lib/module/constants/paywall.js +7 -0
- package/lib/module/constants/paywall.js.map +1 -0
- package/lib/module/hooks/useBanner.js +79 -0
- package/lib/module/hooks/useBanner.js.map +1 -0
- package/lib/module/hooks/useInterval.js +30 -0
- package/lib/module/hooks/useInterval.js.map +1 -0
- package/lib/module/hooks/usePaywall.js +55 -0
- package/lib/module/hooks/usePaywall.js.map +1 -0
- package/lib/module/index.js +8 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/lib.js +2 -2
- package/lib/typescript/commonjs/src/MonetaiProvider.d.ts +3 -7
- package/lib/typescript/commonjs/src/MonetaiProvider.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/PaywallManager.d.ts +7 -0
- package/lib/typescript/commonjs/src/PaywallManager.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/components/Banner.d.ts +10 -0
- package/lib/typescript/commonjs/src/components/Banner.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/components/Paywall.d.ts +14 -0
- package/lib/typescript/commonjs/src/components/Paywall.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/components/paywall-wrappers/CompactPaywallWrapper.d.ts +8 -0
- package/lib/typescript/commonjs/src/components/paywall-wrappers/CompactPaywallWrapper.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/components/paywall-wrappers/HighlightBenefitsPaywallWrapper.d.ts +8 -0
- package/lib/typescript/commonjs/src/components/paywall-wrappers/HighlightBenefitsPaywallWrapper.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/components/paywall-wrappers/KeyFeatureSummaryPaywallWrapper.d.ts +8 -0
- package/lib/typescript/commonjs/src/components/paywall-wrappers/KeyFeatureSummaryPaywallWrapper.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/components/paywall-wrappers/TextFocusedPaywallWrapper.d.ts +8 -0
- package/lib/typescript/commonjs/src/components/paywall-wrappers/TextFocusedPaywallWrapper.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/components/paywall-wrappers/index.d.ts +6 -0
- package/lib/typescript/commonjs/src/components/paywall-wrappers/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/constants/paywall.d.ts +3 -0
- package/lib/typescript/commonjs/src/constants/paywall.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/hooks/useBanner.d.ts +14 -0
- package/lib/typescript/commonjs/src/hooks/useBanner.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/hooks/useInterval.d.ts +7 -0
- package/lib/typescript/commonjs/src/hooks/useInterval.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/hooks/usePaywall.d.ts +15 -0
- package/lib/typescript/commonjs/src/hooks/usePaywall.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/index.d.ts +7 -1
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/types.d.ts +48 -2
- package/lib/typescript/commonjs/src/types.d.ts.map +1 -1
- package/lib/typescript/module/src/MonetaiProvider.d.ts +3 -7
- package/lib/typescript/module/src/MonetaiProvider.d.ts.map +1 -1
- package/lib/typescript/module/src/PaywallManager.d.ts +7 -0
- package/lib/typescript/module/src/PaywallManager.d.ts.map +1 -0
- package/lib/typescript/module/src/components/Banner.d.ts +10 -0
- package/lib/typescript/module/src/components/Banner.d.ts.map +1 -0
- package/lib/typescript/module/src/components/Paywall.d.ts +14 -0
- package/lib/typescript/module/src/components/Paywall.d.ts.map +1 -0
- package/lib/typescript/module/src/components/paywall-wrappers/CompactPaywallWrapper.d.ts +8 -0
- package/lib/typescript/module/src/components/paywall-wrappers/CompactPaywallWrapper.d.ts.map +1 -0
- package/lib/typescript/module/src/components/paywall-wrappers/HighlightBenefitsPaywallWrapper.d.ts +8 -0
- package/lib/typescript/module/src/components/paywall-wrappers/HighlightBenefitsPaywallWrapper.d.ts.map +1 -0
- package/lib/typescript/module/src/components/paywall-wrappers/KeyFeatureSummaryPaywallWrapper.d.ts +8 -0
- package/lib/typescript/module/src/components/paywall-wrappers/KeyFeatureSummaryPaywallWrapper.d.ts.map +1 -0
- package/lib/typescript/module/src/components/paywall-wrappers/TextFocusedPaywallWrapper.d.ts +8 -0
- package/lib/typescript/module/src/components/paywall-wrappers/TextFocusedPaywallWrapper.d.ts.map +1 -0
- package/lib/typescript/module/src/components/paywall-wrappers/index.d.ts +6 -0
- package/lib/typescript/module/src/components/paywall-wrappers/index.d.ts.map +1 -0
- package/lib/typescript/module/src/constants/paywall.d.ts +3 -0
- package/lib/typescript/module/src/constants/paywall.d.ts.map +1 -0
- package/lib/typescript/module/src/hooks/useBanner.d.ts +14 -0
- package/lib/typescript/module/src/hooks/useBanner.d.ts.map +1 -0
- package/lib/typescript/module/src/hooks/useInterval.d.ts +7 -0
- package/lib/typescript/module/src/hooks/useInterval.d.ts.map +1 -0
- package/lib/typescript/module/src/hooks/usePaywall.d.ts +15 -0
- package/lib/typescript/module/src/hooks/usePaywall.d.ts.map +1 -0
- package/lib/typescript/module/src/index.d.ts +7 -1
- package/lib/typescript/module/src/index.d.ts.map +1 -1
- package/lib/typescript/module/src/types.d.ts +48 -2
- package/lib/typescript/module/src/types.d.ts.map +1 -1
- package/package.json +4 -2
- package/src/MonetaiProvider.tsx +72 -29
- package/src/PaywallManager.tsx +61 -0
- package/src/components/Banner.tsx +152 -0
- package/src/components/Paywall.tsx +254 -0
- package/src/components/paywall-wrappers/CompactPaywallWrapper.tsx +58 -0
- package/src/components/paywall-wrappers/HighlightBenefitsPaywallWrapper.tsx +30 -0
- package/src/components/paywall-wrappers/KeyFeatureSummaryPaywallWrapper.tsx +30 -0
- package/src/components/paywall-wrappers/TextFocusedPaywallWrapper.tsx +30 -0
- package/src/components/paywall-wrappers/index.ts +8 -0
- package/src/constants/paywall.ts +4 -0
- package/src/hooks/useBanner.ts +112 -0
- package/src/hooks/useInterval.ts +29 -0
- package/src/hooks/usePaywall.ts +73 -0
- package/src/index.tsx +36 -1
- package/src/types.ts +65 -2
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
ActivityIndicator,
|
|
4
|
+
StyleSheet,
|
|
5
|
+
View,
|
|
6
|
+
Text,
|
|
7
|
+
TouchableOpacity,
|
|
8
|
+
} from 'react-native';
|
|
9
|
+
import { WebView } from 'react-native-webview';
|
|
10
|
+
import type { PaywallParams } from '../types';
|
|
11
|
+
import { PAYWELL_BASE_URL, WEBVIEW_USER_AGENT } from '../constants/paywall';
|
|
12
|
+
import CompactPaywallWrapper from './paywall-wrappers/CompactPaywallWrapper';
|
|
13
|
+
import HighlightBenefitsPaywallWrapper from './paywall-wrappers/HighlightBenefitsPaywallWrapper';
|
|
14
|
+
import KeyFeatureSummaryPaywallWrapper from './paywall-wrappers/KeyFeatureSummaryPaywallWrapper';
|
|
15
|
+
import TextFocusedPaywallWrapper from './paywall-wrappers/TextFocusedPaywallWrapper';
|
|
16
|
+
|
|
17
|
+
const MESSAGE_KEY_PURCHASE_BUTTON = 'CLICK_PURCHASE_BUTTON';
|
|
18
|
+
const MESSAGE_KEY_CLOSE_BUTTON = 'CLICK_CLOSE_BUTTON';
|
|
19
|
+
const MESSAGE_KEY_TERMS_OF_SERVICE = 'CLICK_TERMS_OF_SERVICE';
|
|
20
|
+
const MESSAGE_KEY_PRIVACY_POLICY = 'CLICK_PRIVACY_POLICY';
|
|
21
|
+
|
|
22
|
+
interface PaywallProps {
|
|
23
|
+
visible: boolean;
|
|
24
|
+
// preload가 true면 보이지 않아도 마운트하여 WebView를 미리 로드
|
|
25
|
+
preload?: boolean;
|
|
26
|
+
onClose: () => void;
|
|
27
|
+
onPurchase: () => void;
|
|
28
|
+
onTermsOfService?: () => void;
|
|
29
|
+
onPrivacyPolicy?: () => void;
|
|
30
|
+
params: PaywallParams;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const Paywall: React.FC<PaywallProps> = ({
|
|
34
|
+
visible,
|
|
35
|
+
preload,
|
|
36
|
+
onClose,
|
|
37
|
+
onPurchase,
|
|
38
|
+
onTermsOfService,
|
|
39
|
+
onPrivacyPolicy,
|
|
40
|
+
params,
|
|
41
|
+
}) => {
|
|
42
|
+
const [isLoaded, setIsLoaded] = useState(false);
|
|
43
|
+
const [hasWebViewError, setHasWebViewError] = useState(false);
|
|
44
|
+
const handleWebViewMessage = (event: any) => {
|
|
45
|
+
switch (event.nativeEvent.data) {
|
|
46
|
+
case MESSAGE_KEY_PURCHASE_BUTTON:
|
|
47
|
+
onPurchase();
|
|
48
|
+
break;
|
|
49
|
+
case MESSAGE_KEY_CLOSE_BUTTON:
|
|
50
|
+
onClose();
|
|
51
|
+
break;
|
|
52
|
+
case MESSAGE_KEY_TERMS_OF_SERVICE:
|
|
53
|
+
onTermsOfService?.();
|
|
54
|
+
break;
|
|
55
|
+
case MESSAGE_KEY_PRIVACY_POLICY:
|
|
56
|
+
onPrivacyPolicy?.();
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const getPaywallWebViewUrl = () => {
|
|
62
|
+
// monetai-web의 현재 구조에 맞춰 URL 생성
|
|
63
|
+
// 각 스타일별로 별도의 페이지가 있으므로 직접 스타일을 URL에 포함
|
|
64
|
+
const baseUrl = `${PAYWELL_BASE_URL}/paywall`;
|
|
65
|
+
const paywallStyle = params.style;
|
|
66
|
+
const searchParams = new URLSearchParams();
|
|
67
|
+
|
|
68
|
+
if (params.discountPercent) {
|
|
69
|
+
searchParams.append('discount', params.discountPercent);
|
|
70
|
+
}
|
|
71
|
+
if (params.endedAt) {
|
|
72
|
+
searchParams.append('endedAt', params.endedAt);
|
|
73
|
+
}
|
|
74
|
+
if (params.regularPrice) {
|
|
75
|
+
searchParams.append('regularPrice', params.regularPrice);
|
|
76
|
+
}
|
|
77
|
+
if (params.discountedPrice) {
|
|
78
|
+
searchParams.append('discountedPrice', params.discountedPrice);
|
|
79
|
+
}
|
|
80
|
+
if (params.locale) {
|
|
81
|
+
searchParams.append('locale', params.locale);
|
|
82
|
+
}
|
|
83
|
+
if (params.features) {
|
|
84
|
+
searchParams.append('features', JSON.stringify(params.features));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const queryString = searchParams.toString();
|
|
88
|
+
const url = `${baseUrl}/${paywallStyle}${queryString ? `?${queryString}` : ''}`;
|
|
89
|
+
|
|
90
|
+
return url;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// 프리로드 옵션이 없고 보이지도 않으면 렌더하지 않음
|
|
94
|
+
if (!visible && !preload) return null;
|
|
95
|
+
|
|
96
|
+
// 스타일에 따른 래퍼 선택
|
|
97
|
+
const getPaywallWrapper = () => {
|
|
98
|
+
const style = params.style;
|
|
99
|
+
const url = getPaywallWebViewUrl();
|
|
100
|
+
|
|
101
|
+
// 공통 WebView 컴포넌트
|
|
102
|
+
const commonWebView = (
|
|
103
|
+
<>
|
|
104
|
+
{!isLoaded && (
|
|
105
|
+
<View style={styles.loadingContainer} pointerEvents="none">
|
|
106
|
+
<ActivityIndicator size="small" />
|
|
107
|
+
</View>
|
|
108
|
+
)}
|
|
109
|
+
<WebView
|
|
110
|
+
source={{ uri: url }}
|
|
111
|
+
onMessage={handleWebViewMessage}
|
|
112
|
+
onLoadStart={() => {
|
|
113
|
+
setIsLoaded(false);
|
|
114
|
+
}}
|
|
115
|
+
onLoadEnd={() => {
|
|
116
|
+
setIsLoaded(true);
|
|
117
|
+
}}
|
|
118
|
+
onError={() => {
|
|
119
|
+
console.error('[MonetaiSDK] Paywall WebView onError');
|
|
120
|
+
setIsLoaded(true);
|
|
121
|
+
setHasWebViewError(true);
|
|
122
|
+
}}
|
|
123
|
+
onHttpError={() => {
|
|
124
|
+
console.error('[MonetaiSDK] Paywall WebView onHttpError');
|
|
125
|
+
setIsLoaded(true);
|
|
126
|
+
setHasWebViewError(true);
|
|
127
|
+
}}
|
|
128
|
+
javaScriptEnabled={true}
|
|
129
|
+
userAgent={WEBVIEW_USER_AGENT}
|
|
130
|
+
style={[styles.webView, !isLoaded && styles.webViewHidden]}
|
|
131
|
+
/>
|
|
132
|
+
{hasWebViewError && (
|
|
133
|
+
<View style={styles.errorOverlay} pointerEvents="auto">
|
|
134
|
+
<View style={styles.errorCard}>
|
|
135
|
+
<Text style={styles.errorTitle}>Failed to load</Text>
|
|
136
|
+
<Text style={styles.errorMessage}>
|
|
137
|
+
An unexpected error occurred while loading the paywall.
|
|
138
|
+
</Text>
|
|
139
|
+
<TouchableOpacity style={styles.errorButton} onPress={onClose}>
|
|
140
|
+
<Text style={styles.errorButtonText}>Close</Text>
|
|
141
|
+
</TouchableOpacity>
|
|
142
|
+
</View>
|
|
143
|
+
</View>
|
|
144
|
+
)}
|
|
145
|
+
</>
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
switch (style) {
|
|
149
|
+
case 'compact':
|
|
150
|
+
return (
|
|
151
|
+
<CompactPaywallWrapper onClose={onClose}>
|
|
152
|
+
{commonWebView}
|
|
153
|
+
</CompactPaywallWrapper>
|
|
154
|
+
);
|
|
155
|
+
case 'highlight-benefits':
|
|
156
|
+
return (
|
|
157
|
+
<HighlightBenefitsPaywallWrapper onClose={onClose}>
|
|
158
|
+
{commonWebView}
|
|
159
|
+
</HighlightBenefitsPaywallWrapper>
|
|
160
|
+
);
|
|
161
|
+
case 'key-feature-summary':
|
|
162
|
+
return (
|
|
163
|
+
<KeyFeatureSummaryPaywallWrapper onClose={onClose}>
|
|
164
|
+
{commonWebView}
|
|
165
|
+
</KeyFeatureSummaryPaywallWrapper>
|
|
166
|
+
);
|
|
167
|
+
case 'text-focused':
|
|
168
|
+
default:
|
|
169
|
+
return (
|
|
170
|
+
<TextFocusedPaywallWrapper onClose={onClose}>
|
|
171
|
+
{commonWebView}
|
|
172
|
+
</TextFocusedPaywallWrapper>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
<View
|
|
179
|
+
style={[
|
|
180
|
+
styles.overlayContainer,
|
|
181
|
+
params.zIndex != null && { zIndex: params.zIndex },
|
|
182
|
+
params.elevation != null && { elevation: params.elevation },
|
|
183
|
+
!visible && styles.overlayHidden,
|
|
184
|
+
]}
|
|
185
|
+
pointerEvents={visible ? 'auto' : 'none'}
|
|
186
|
+
>
|
|
187
|
+
{getPaywallWrapper()}
|
|
188
|
+
</View>
|
|
189
|
+
);
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const styles = StyleSheet.create({
|
|
193
|
+
webView: {
|
|
194
|
+
flex: 1,
|
|
195
|
+
},
|
|
196
|
+
webViewHidden: {
|
|
197
|
+
opacity: 0,
|
|
198
|
+
},
|
|
199
|
+
overlayContainer: {
|
|
200
|
+
position: 'absolute',
|
|
201
|
+
top: 0,
|
|
202
|
+
left: 0,
|
|
203
|
+
right: 0,
|
|
204
|
+
bottom: 0,
|
|
205
|
+
zIndex: 2000,
|
|
206
|
+
elevation: 16,
|
|
207
|
+
},
|
|
208
|
+
overlayHidden: {
|
|
209
|
+
opacity: 0,
|
|
210
|
+
},
|
|
211
|
+
loadingContainer: {
|
|
212
|
+
...StyleSheet.absoluteFillObject,
|
|
213
|
+
alignItems: 'center',
|
|
214
|
+
justifyContent: 'center',
|
|
215
|
+
backgroundColor: 'white',
|
|
216
|
+
},
|
|
217
|
+
errorOverlay: {
|
|
218
|
+
...StyleSheet.absoluteFillObject,
|
|
219
|
+
backgroundColor: 'rgba(0,0,0,0.4)',
|
|
220
|
+
alignItems: 'center',
|
|
221
|
+
justifyContent: 'center',
|
|
222
|
+
},
|
|
223
|
+
errorCard: {
|
|
224
|
+
width: '80%',
|
|
225
|
+
backgroundColor: 'white',
|
|
226
|
+
borderRadius: 12,
|
|
227
|
+
padding: 16,
|
|
228
|
+
alignItems: 'center',
|
|
229
|
+
},
|
|
230
|
+
errorTitle: {
|
|
231
|
+
fontSize: 16,
|
|
232
|
+
fontWeight: '600',
|
|
233
|
+
marginBottom: 8,
|
|
234
|
+
},
|
|
235
|
+
errorMessage: {
|
|
236
|
+
fontSize: 13,
|
|
237
|
+
color: '#666',
|
|
238
|
+
textAlign: 'center',
|
|
239
|
+
marginBottom: 12,
|
|
240
|
+
},
|
|
241
|
+
errorButton: {
|
|
242
|
+
marginTop: 4,
|
|
243
|
+
backgroundColor: '#111827',
|
|
244
|
+
borderRadius: 8,
|
|
245
|
+
paddingHorizontal: 16,
|
|
246
|
+
paddingVertical: 10,
|
|
247
|
+
},
|
|
248
|
+
errorButtonText: {
|
|
249
|
+
color: 'white',
|
|
250
|
+
fontWeight: '600',
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
export default Paywall;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { StyleSheet, TouchableOpacity, View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
interface CompactPaywallWrapperProps {
|
|
5
|
+
onClose: () => void;
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Compact 스타일 래퍼: 하단에 붙어서 372px 높이, dim background 클릭 시 닫기
|
|
10
|
+
export const CompactPaywallWrapper: React.FC<CompactPaywallWrapperProps> = ({
|
|
11
|
+
onClose,
|
|
12
|
+
children,
|
|
13
|
+
}) => {
|
|
14
|
+
return (
|
|
15
|
+
<View style={styles.compactPaywallContainer}>
|
|
16
|
+
{/* Dim background */}
|
|
17
|
+
<TouchableOpacity
|
|
18
|
+
style={styles.dimBackground}
|
|
19
|
+
activeOpacity={1}
|
|
20
|
+
onPress={onClose}
|
|
21
|
+
/>
|
|
22
|
+
|
|
23
|
+
{/* Paywall content - 하단에 붙음 */}
|
|
24
|
+
<View style={styles.compactPaywallContent}>{children}</View>
|
|
25
|
+
</View>
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const styles = StyleSheet.create({
|
|
30
|
+
compactPaywallContainer: {
|
|
31
|
+
position: 'absolute',
|
|
32
|
+
top: 0,
|
|
33
|
+
left: 0,
|
|
34
|
+
right: 0,
|
|
35
|
+
bottom: 0,
|
|
36
|
+
zIndex: 2000,
|
|
37
|
+
},
|
|
38
|
+
dimBackground: {
|
|
39
|
+
position: 'absolute',
|
|
40
|
+
top: 0,
|
|
41
|
+
left: 0,
|
|
42
|
+
right: 0,
|
|
43
|
+
bottom: 0,
|
|
44
|
+
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
45
|
+
},
|
|
46
|
+
compactPaywallContent: {
|
|
47
|
+
position: 'absolute',
|
|
48
|
+
bottom: 0, // 하단에 붙음
|
|
49
|
+
left: 0, // 여백 제거
|
|
50
|
+
right: 0, // 여백 제거
|
|
51
|
+
height: 372,
|
|
52
|
+
backgroundColor: 'white',
|
|
53
|
+
borderRadius: 12,
|
|
54
|
+
overflow: 'hidden',
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
export default CompactPaywallWrapper;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { StyleSheet, SafeAreaView } from 'react-native';
|
|
3
|
+
|
|
4
|
+
interface HighlightBenefitsPaywallWrapperProps {
|
|
5
|
+
onClose: () => void;
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Highlight Benefits 스타일 래퍼: 전체 화면을 차지
|
|
10
|
+
export const HighlightBenefitsPaywallWrapper: React.FC<
|
|
11
|
+
HighlightBenefitsPaywallWrapperProps
|
|
12
|
+
> = ({ children }) => {
|
|
13
|
+
return (
|
|
14
|
+
<SafeAreaView style={styles.fullScreenContainer}>{children}</SafeAreaView>
|
|
15
|
+
);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const styles = StyleSheet.create({
|
|
19
|
+
fullScreenContainer: {
|
|
20
|
+
position: 'absolute',
|
|
21
|
+
top: 0,
|
|
22
|
+
left: 0,
|
|
23
|
+
right: 0,
|
|
24
|
+
bottom: 0,
|
|
25
|
+
backgroundColor: 'white',
|
|
26
|
+
zIndex: 2000,
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export default HighlightBenefitsPaywallWrapper;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { StyleSheet, SafeAreaView } from 'react-native';
|
|
3
|
+
|
|
4
|
+
interface KeyFeatureSummaryPaywallWrapperProps {
|
|
5
|
+
onClose: () => void;
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Key Feature Summary 스타일 래퍼: 전체 화면을 차지
|
|
10
|
+
export const KeyFeatureSummaryPaywallWrapper: React.FC<
|
|
11
|
+
KeyFeatureSummaryPaywallWrapperProps
|
|
12
|
+
> = ({ children }) => {
|
|
13
|
+
return (
|
|
14
|
+
<SafeAreaView style={styles.fullScreenContainer}>{children}</SafeAreaView>
|
|
15
|
+
);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const styles = StyleSheet.create({
|
|
19
|
+
fullScreenContainer: {
|
|
20
|
+
position: 'absolute',
|
|
21
|
+
top: 0,
|
|
22
|
+
left: 0,
|
|
23
|
+
right: 0,
|
|
24
|
+
bottom: 0,
|
|
25
|
+
backgroundColor: 'white',
|
|
26
|
+
zIndex: 2000,
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export default KeyFeatureSummaryPaywallWrapper;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { StyleSheet, SafeAreaView } from 'react-native';
|
|
3
|
+
|
|
4
|
+
interface TextFocusedPaywallWrapperProps {
|
|
5
|
+
onClose: () => void;
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Text Focused 스타일 래퍼: 전체 화면을 차지
|
|
10
|
+
export const TextFocusedPaywallWrapper: React.FC<
|
|
11
|
+
TextFocusedPaywallWrapperProps
|
|
12
|
+
> = ({ children }) => {
|
|
13
|
+
return (
|
|
14
|
+
<SafeAreaView style={styles.fullScreenContainer}>{children}</SafeAreaView>
|
|
15
|
+
);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const styles = StyleSheet.create({
|
|
19
|
+
fullScreenContainer: {
|
|
20
|
+
position: 'absolute',
|
|
21
|
+
top: 0,
|
|
22
|
+
left: 0,
|
|
23
|
+
right: 0,
|
|
24
|
+
bottom: 0,
|
|
25
|
+
backgroundColor: 'white',
|
|
26
|
+
zIndex: 2000,
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export default TextFocusedPaywallWrapper;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Paywall Wrappers
|
|
2
|
+
export { default as CompactPaywallWrapper } from './CompactPaywallWrapper';
|
|
3
|
+
export { default as HighlightBenefitsPaywallWrapper } from './HighlightBenefitsPaywallWrapper';
|
|
4
|
+
export { default as KeyFeatureSummaryPaywallWrapper } from './KeyFeatureSummaryPaywallWrapper';
|
|
5
|
+
export { default as TextFocusedPaywallWrapper } from './TextFocusedPaywallWrapper';
|
|
6
|
+
|
|
7
|
+
// Default export for backward compatibility
|
|
8
|
+
export { default } from './CompactPaywallWrapper';
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import dayjs from 'dayjs';
|
|
3
|
+
import isBetween from 'dayjs/plugin/isBetween';
|
|
4
|
+
import type { BannerParams, DiscountInfo, PaywallConfig } from '../types';
|
|
5
|
+
import { useInterval } from './useInterval';
|
|
6
|
+
|
|
7
|
+
// dayjs 플러그인 확장
|
|
8
|
+
dayjs.extend(isBetween);
|
|
9
|
+
|
|
10
|
+
interface UseBannerProps {
|
|
11
|
+
paywallConfig?: PaywallConfig; // PaywallConfig를 optional로 변경
|
|
12
|
+
discountInfo: DiscountInfo | null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface UseBannerReturn {
|
|
16
|
+
bannerVisible: boolean; // 배너 표시 여부
|
|
17
|
+
openBanner: () => void; // 배너를 보여주는 함수
|
|
18
|
+
closeBanner: () => void; // 배너를 닫는 함수
|
|
19
|
+
bannerParams: BannerParams | null; // null일 수 있도록 변경
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const useBanner = ({
|
|
23
|
+
paywallConfig, // PaywallConfig로 변경
|
|
24
|
+
discountInfo,
|
|
25
|
+
}: UseBannerProps): UseBannerReturn => {
|
|
26
|
+
const [bannerVisible, setBannerVisible] = useState(false);
|
|
27
|
+
const [bannerParams, setBannerParams] = useState<BannerParams | null>(null); // 데이터 준비 시 보관
|
|
28
|
+
|
|
29
|
+
// 할인 활성 상태 체크 함수
|
|
30
|
+
const checkDiscountActive = useCallback(() => {
|
|
31
|
+
if (
|
|
32
|
+
bannerParams == null ||
|
|
33
|
+
bannerParams.enabled === false ||
|
|
34
|
+
bannerParams.isSubscriber === true ||
|
|
35
|
+
discountInfo == null
|
|
36
|
+
) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const now = dayjs();
|
|
41
|
+
return now.isBetween(
|
|
42
|
+
dayjs(discountInfo.startedAt),
|
|
43
|
+
dayjs(discountInfo.endedAt),
|
|
44
|
+
null,
|
|
45
|
+
'[]'
|
|
46
|
+
);
|
|
47
|
+
}, [discountInfo, bannerParams]);
|
|
48
|
+
|
|
49
|
+
const closeBanner = useCallback(() => {
|
|
50
|
+
setBannerVisible(false);
|
|
51
|
+
}, []);
|
|
52
|
+
|
|
53
|
+
const openBanner = useCallback(() => {
|
|
54
|
+
if (!bannerParams) {
|
|
55
|
+
console.error(
|
|
56
|
+
'[MonetaiSDK] useBanner: Cannot open banner - bannerParams is null (data not ready)'
|
|
57
|
+
);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (checkDiscountActive()) {
|
|
61
|
+
setBannerVisible(true);
|
|
62
|
+
}
|
|
63
|
+
}, [bannerParams, checkDiscountActive]);
|
|
64
|
+
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (!paywallConfig || !discountInfo) {
|
|
67
|
+
setBannerParams(null);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const baseZIndex = paywallConfig.bannerZIndex ?? 1000;
|
|
71
|
+
const baseElevation = paywallConfig.bannerElevation ?? 8;
|
|
72
|
+
|
|
73
|
+
const computed: BannerParams = {
|
|
74
|
+
enabled: paywallConfig.enabled,
|
|
75
|
+
isSubscriber: paywallConfig.isSubscriber,
|
|
76
|
+
locale: paywallConfig.locale,
|
|
77
|
+
discountPercent: paywallConfig.discountPercent,
|
|
78
|
+
bottom: paywallConfig.bannerBottom,
|
|
79
|
+
style: paywallConfig.style,
|
|
80
|
+
endedAt: discountInfo.endedAt,
|
|
81
|
+
zIndex: baseZIndex,
|
|
82
|
+
elevation: baseElevation,
|
|
83
|
+
};
|
|
84
|
+
setBannerParams(computed);
|
|
85
|
+
}, [paywallConfig, discountInfo]);
|
|
86
|
+
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
const isActive = checkDiscountActive();
|
|
89
|
+
if (isActive) {
|
|
90
|
+
setBannerVisible(true);
|
|
91
|
+
} else {
|
|
92
|
+
setBannerVisible(false);
|
|
93
|
+
}
|
|
94
|
+
}, [checkDiscountActive]);
|
|
95
|
+
|
|
96
|
+
useInterval(
|
|
97
|
+
() => {
|
|
98
|
+
const isActive = checkDiscountActive();
|
|
99
|
+
if (!isActive) {
|
|
100
|
+
setBannerVisible(false);
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
bannerVisible ? 1000 : null
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
bannerVisible,
|
|
108
|
+
openBanner,
|
|
109
|
+
closeBanner,
|
|
110
|
+
bannerParams, // state로 관리되는 bannerParams 반환
|
|
111
|
+
};
|
|
112
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Custom hook for setting up an interval that can be paused/resumed
|
|
5
|
+
* @param callback Function to call on each interval
|
|
6
|
+
* @param delay Delay in milliseconds, null to pause
|
|
7
|
+
*/
|
|
8
|
+
export const useInterval = (callback: () => void, delay: number | null) => {
|
|
9
|
+
const savedCallback = useRef<() => void>(() => {});
|
|
10
|
+
|
|
11
|
+
// Remember the latest callback
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
savedCallback.current = callback;
|
|
14
|
+
}, [callback]);
|
|
15
|
+
|
|
16
|
+
// Set up the interval
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
// Don't schedule if no delay is specified
|
|
19
|
+
if (delay === null) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const id = setInterval(() => {
|
|
24
|
+
savedCallback.current?.();
|
|
25
|
+
}, delay);
|
|
26
|
+
|
|
27
|
+
return () => clearInterval(id);
|
|
28
|
+
}, [delay]);
|
|
29
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { useState, useCallback, useEffect } from 'react';
|
|
2
|
+
import type { PaywallParams, PaywallConfig, DiscountInfo } from '../types';
|
|
3
|
+
|
|
4
|
+
interface UsePaywallProps {
|
|
5
|
+
paywallConfig?: PaywallConfig; // PaywallConfig를 optional로 변경
|
|
6
|
+
discountInfo: DiscountInfo | null;
|
|
7
|
+
onClose?: () => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface UsePaywallReturn {
|
|
11
|
+
paywallVisible: boolean;
|
|
12
|
+
paywallParams: PaywallParams | null;
|
|
13
|
+
openPaywall: () => void;
|
|
14
|
+
closePaywall: () => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const usePaywall = ({
|
|
18
|
+
paywallConfig, // 타입 변경
|
|
19
|
+
discountInfo, // discountInfo 추가
|
|
20
|
+
onClose,
|
|
21
|
+
}: UsePaywallProps): UsePaywallReturn => {
|
|
22
|
+
const [paywallVisible, setPaywallVisible] = useState(false);
|
|
23
|
+
const [paywallParams, setPaywallParams] = useState<PaywallParams | null>(
|
|
24
|
+
null
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (!discountInfo || !paywallConfig) {
|
|
29
|
+
setPaywallParams(null);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const paywallZ = paywallConfig.paywallZIndex ?? 2000;
|
|
34
|
+
const paywallElevation = paywallConfig.paywallElevation ?? 16;
|
|
35
|
+
const computedParams: PaywallParams = {
|
|
36
|
+
discountPercent: paywallConfig.discountPercent.toString(),
|
|
37
|
+
endedAt: discountInfo.endedAt.toISOString(),
|
|
38
|
+
regularPrice: paywallConfig.regularPrice,
|
|
39
|
+
discountedPrice: paywallConfig.discountedPrice,
|
|
40
|
+
locale: paywallConfig.locale,
|
|
41
|
+
features: paywallConfig.features,
|
|
42
|
+
style: paywallConfig.style,
|
|
43
|
+
zIndex: paywallZ,
|
|
44
|
+
elevation: paywallElevation,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
setPaywallParams(computedParams);
|
|
48
|
+
}, [discountInfo, paywallConfig]);
|
|
49
|
+
|
|
50
|
+
// 페이월 열기
|
|
51
|
+
const openPaywall = useCallback(() => {
|
|
52
|
+
if (!paywallParams) {
|
|
53
|
+
console.error(
|
|
54
|
+
'[MonetaiSDK] usePaywall: Cannot open paywall - paywallParams is null (data not ready)'
|
|
55
|
+
);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
setPaywallVisible(true);
|
|
59
|
+
}, [paywallParams]);
|
|
60
|
+
|
|
61
|
+
// 페이월 닫기
|
|
62
|
+
const closePaywall = useCallback(() => {
|
|
63
|
+
setPaywallVisible(false);
|
|
64
|
+
onClose?.();
|
|
65
|
+
}, [onClose]);
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
paywallVisible,
|
|
69
|
+
paywallParams,
|
|
70
|
+
openPaywall,
|
|
71
|
+
closePaywall,
|
|
72
|
+
};
|
|
73
|
+
};
|
package/src/index.tsx
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
import MonetaiSDK from './MonetaiSDK';
|
|
2
2
|
import MonetaiProvider from './MonetaiProvider';
|
|
3
|
+
import Banner from './components/Banner';
|
|
4
|
+
import Paywall from './components/Paywall';
|
|
5
|
+
import {
|
|
6
|
+
CompactPaywallWrapper,
|
|
7
|
+
HighlightBenefitsPaywallWrapper,
|
|
8
|
+
KeyFeatureSummaryPaywallWrapper,
|
|
9
|
+
TextFocusedPaywallWrapper,
|
|
10
|
+
} from './components/paywall-wrappers';
|
|
11
|
+
import { useBanner } from './hooks/useBanner';
|
|
12
|
+
import { usePaywall } from './hooks/usePaywall';
|
|
3
13
|
|
|
4
14
|
export default MonetaiSDK;
|
|
5
|
-
export {
|
|
15
|
+
export {
|
|
16
|
+
MonetaiProvider,
|
|
17
|
+
Banner,
|
|
18
|
+
Paywall,
|
|
19
|
+
useBanner,
|
|
20
|
+
usePaywall,
|
|
21
|
+
CompactPaywallWrapper,
|
|
22
|
+
HighlightBenefitsPaywallWrapper,
|
|
23
|
+
KeyFeatureSummaryPaywallWrapper,
|
|
24
|
+
TextFocusedPaywallWrapper,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Export types
|
|
28
|
+
export type {
|
|
29
|
+
ABTestGroup,
|
|
30
|
+
PredictResult,
|
|
31
|
+
EventParams,
|
|
32
|
+
LogEventOptions,
|
|
33
|
+
DiscountInfo,
|
|
34
|
+
AppUserDiscountResponse,
|
|
35
|
+
PaywallConfig,
|
|
36
|
+
Feature,
|
|
37
|
+
BannerParams,
|
|
38
|
+
PaywallParams,
|
|
39
|
+
PaywallStyle,
|
|
40
|
+
} from './types';
|