@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.
Files changed (134) hide show
  1. package/lib/commonjs/MonetaiProvider.js +45 -23
  2. package/lib/commonjs/MonetaiProvider.js.map +1 -1
  3. package/lib/commonjs/PaywallManager.js +63 -0
  4. package/lib/commonjs/PaywallManager.js.map +1 -0
  5. package/lib/commonjs/components/Banner.js +141 -0
  6. package/lib/commonjs/components/Banner.js.map +1 -0
  7. package/lib/commonjs/components/Paywall.js +236 -0
  8. package/lib/commonjs/components/Paywall.js.map +1 -0
  9. package/lib/commonjs/components/paywall-wrappers/CompactPaywallWrapper.js +61 -0
  10. package/lib/commonjs/components/paywall-wrappers/CompactPaywallWrapper.js.map +1 -0
  11. package/lib/commonjs/components/paywall-wrappers/HighlightBenefitsPaywallWrapper.js +33 -0
  12. package/lib/commonjs/components/paywall-wrappers/HighlightBenefitsPaywallWrapper.js.map +1 -0
  13. package/lib/commonjs/components/paywall-wrappers/KeyFeatureSummaryPaywallWrapper.js +33 -0
  14. package/lib/commonjs/components/paywall-wrappers/KeyFeatureSummaryPaywallWrapper.js.map +1 -0
  15. package/lib/commonjs/components/paywall-wrappers/TextFocusedPaywallWrapper.js +33 -0
  16. package/lib/commonjs/components/paywall-wrappers/TextFocusedPaywallWrapper.js.map +1 -0
  17. package/lib/commonjs/components/paywall-wrappers/index.js +41 -0
  18. package/lib/commonjs/components/paywall-wrappers/index.js.map +1 -0
  19. package/lib/commonjs/constants/paywall.js +11 -0
  20. package/lib/commonjs/constants/paywall.js.map +1 -0
  21. package/lib/commonjs/hooks/useBanner.js +84 -0
  22. package/lib/commonjs/hooks/useBanner.js.map +1 -0
  23. package/lib/commonjs/hooks/useInterval.js +34 -0
  24. package/lib/commonjs/hooks/useInterval.js.map +1 -0
  25. package/lib/commonjs/hooks/usePaywall.js +60 -0
  26. package/lib/commonjs/hooks/usePaywall.js.map +1 -0
  27. package/lib/commonjs/index.js +54 -1
  28. package/lib/commonjs/index.js.map +1 -1
  29. package/lib/commonjs/lib.js +2 -2
  30. package/lib/module/MonetaiProvider.js +48 -25
  31. package/lib/module/MonetaiProvider.js.map +1 -1
  32. package/lib/module/PaywallManager.js +58 -0
  33. package/lib/module/PaywallManager.js.map +1 -0
  34. package/lib/module/components/Banner.js +136 -0
  35. package/lib/module/components/Banner.js.map +1 -0
  36. package/lib/module/components/Paywall.js +230 -0
  37. package/lib/module/components/Paywall.js.map +1 -0
  38. package/lib/module/components/paywall-wrappers/CompactPaywallWrapper.js +55 -0
  39. package/lib/module/components/paywall-wrappers/CompactPaywallWrapper.js.map +1 -0
  40. package/lib/module/components/paywall-wrappers/HighlightBenefitsPaywallWrapper.js +27 -0
  41. package/lib/module/components/paywall-wrappers/HighlightBenefitsPaywallWrapper.js.map +1 -0
  42. package/lib/module/components/paywall-wrappers/KeyFeatureSummaryPaywallWrapper.js +27 -0
  43. package/lib/module/components/paywall-wrappers/KeyFeatureSummaryPaywallWrapper.js.map +1 -0
  44. package/lib/module/components/paywall-wrappers/TextFocusedPaywallWrapper.js +27 -0
  45. package/lib/module/components/paywall-wrappers/TextFocusedPaywallWrapper.js.map +1 -0
  46. package/lib/module/components/paywall-wrappers/index.js +11 -0
  47. package/lib/module/components/paywall-wrappers/index.js.map +1 -0
  48. package/lib/module/constants/paywall.js +7 -0
  49. package/lib/module/constants/paywall.js.map +1 -0
  50. package/lib/module/hooks/useBanner.js +79 -0
  51. package/lib/module/hooks/useBanner.js.map +1 -0
  52. package/lib/module/hooks/useInterval.js +30 -0
  53. package/lib/module/hooks/useInterval.js.map +1 -0
  54. package/lib/module/hooks/usePaywall.js +55 -0
  55. package/lib/module/hooks/usePaywall.js.map +1 -0
  56. package/lib/module/index.js +8 -1
  57. package/lib/module/index.js.map +1 -1
  58. package/lib/module/lib.js +2 -2
  59. package/lib/typescript/commonjs/src/MonetaiProvider.d.ts +3 -7
  60. package/lib/typescript/commonjs/src/MonetaiProvider.d.ts.map +1 -1
  61. package/lib/typescript/commonjs/src/PaywallManager.d.ts +7 -0
  62. package/lib/typescript/commonjs/src/PaywallManager.d.ts.map +1 -0
  63. package/lib/typescript/commonjs/src/components/Banner.d.ts +10 -0
  64. package/lib/typescript/commonjs/src/components/Banner.d.ts.map +1 -0
  65. package/lib/typescript/commonjs/src/components/Paywall.d.ts +14 -0
  66. package/lib/typescript/commonjs/src/components/Paywall.d.ts.map +1 -0
  67. package/lib/typescript/commonjs/src/components/paywall-wrappers/CompactPaywallWrapper.d.ts +8 -0
  68. package/lib/typescript/commonjs/src/components/paywall-wrappers/CompactPaywallWrapper.d.ts.map +1 -0
  69. package/lib/typescript/commonjs/src/components/paywall-wrappers/HighlightBenefitsPaywallWrapper.d.ts +8 -0
  70. package/lib/typescript/commonjs/src/components/paywall-wrappers/HighlightBenefitsPaywallWrapper.d.ts.map +1 -0
  71. package/lib/typescript/commonjs/src/components/paywall-wrappers/KeyFeatureSummaryPaywallWrapper.d.ts +8 -0
  72. package/lib/typescript/commonjs/src/components/paywall-wrappers/KeyFeatureSummaryPaywallWrapper.d.ts.map +1 -0
  73. package/lib/typescript/commonjs/src/components/paywall-wrappers/TextFocusedPaywallWrapper.d.ts +8 -0
  74. package/lib/typescript/commonjs/src/components/paywall-wrappers/TextFocusedPaywallWrapper.d.ts.map +1 -0
  75. package/lib/typescript/commonjs/src/components/paywall-wrappers/index.d.ts +6 -0
  76. package/lib/typescript/commonjs/src/components/paywall-wrappers/index.d.ts.map +1 -0
  77. package/lib/typescript/commonjs/src/constants/paywall.d.ts +3 -0
  78. package/lib/typescript/commonjs/src/constants/paywall.d.ts.map +1 -0
  79. package/lib/typescript/commonjs/src/hooks/useBanner.d.ts +14 -0
  80. package/lib/typescript/commonjs/src/hooks/useBanner.d.ts.map +1 -0
  81. package/lib/typescript/commonjs/src/hooks/useInterval.d.ts +7 -0
  82. package/lib/typescript/commonjs/src/hooks/useInterval.d.ts.map +1 -0
  83. package/lib/typescript/commonjs/src/hooks/usePaywall.d.ts +15 -0
  84. package/lib/typescript/commonjs/src/hooks/usePaywall.d.ts.map +1 -0
  85. package/lib/typescript/commonjs/src/index.d.ts +7 -1
  86. package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
  87. package/lib/typescript/commonjs/src/types.d.ts +48 -2
  88. package/lib/typescript/commonjs/src/types.d.ts.map +1 -1
  89. package/lib/typescript/module/src/MonetaiProvider.d.ts +3 -7
  90. package/lib/typescript/module/src/MonetaiProvider.d.ts.map +1 -1
  91. package/lib/typescript/module/src/PaywallManager.d.ts +7 -0
  92. package/lib/typescript/module/src/PaywallManager.d.ts.map +1 -0
  93. package/lib/typescript/module/src/components/Banner.d.ts +10 -0
  94. package/lib/typescript/module/src/components/Banner.d.ts.map +1 -0
  95. package/lib/typescript/module/src/components/Paywall.d.ts +14 -0
  96. package/lib/typescript/module/src/components/Paywall.d.ts.map +1 -0
  97. package/lib/typescript/module/src/components/paywall-wrappers/CompactPaywallWrapper.d.ts +8 -0
  98. package/lib/typescript/module/src/components/paywall-wrappers/CompactPaywallWrapper.d.ts.map +1 -0
  99. package/lib/typescript/module/src/components/paywall-wrappers/HighlightBenefitsPaywallWrapper.d.ts +8 -0
  100. package/lib/typescript/module/src/components/paywall-wrappers/HighlightBenefitsPaywallWrapper.d.ts.map +1 -0
  101. package/lib/typescript/module/src/components/paywall-wrappers/KeyFeatureSummaryPaywallWrapper.d.ts +8 -0
  102. package/lib/typescript/module/src/components/paywall-wrappers/KeyFeatureSummaryPaywallWrapper.d.ts.map +1 -0
  103. package/lib/typescript/module/src/components/paywall-wrappers/TextFocusedPaywallWrapper.d.ts +8 -0
  104. package/lib/typescript/module/src/components/paywall-wrappers/TextFocusedPaywallWrapper.d.ts.map +1 -0
  105. package/lib/typescript/module/src/components/paywall-wrappers/index.d.ts +6 -0
  106. package/lib/typescript/module/src/components/paywall-wrappers/index.d.ts.map +1 -0
  107. package/lib/typescript/module/src/constants/paywall.d.ts +3 -0
  108. package/lib/typescript/module/src/constants/paywall.d.ts.map +1 -0
  109. package/lib/typescript/module/src/hooks/useBanner.d.ts +14 -0
  110. package/lib/typescript/module/src/hooks/useBanner.d.ts.map +1 -0
  111. package/lib/typescript/module/src/hooks/useInterval.d.ts +7 -0
  112. package/lib/typescript/module/src/hooks/useInterval.d.ts.map +1 -0
  113. package/lib/typescript/module/src/hooks/usePaywall.d.ts +15 -0
  114. package/lib/typescript/module/src/hooks/usePaywall.d.ts.map +1 -0
  115. package/lib/typescript/module/src/index.d.ts +7 -1
  116. package/lib/typescript/module/src/index.d.ts.map +1 -1
  117. package/lib/typescript/module/src/types.d.ts +48 -2
  118. package/lib/typescript/module/src/types.d.ts.map +1 -1
  119. package/package.json +4 -2
  120. package/src/MonetaiProvider.tsx +72 -29
  121. package/src/PaywallManager.tsx +61 -0
  122. package/src/components/Banner.tsx +152 -0
  123. package/src/components/Paywall.tsx +254 -0
  124. package/src/components/paywall-wrappers/CompactPaywallWrapper.tsx +58 -0
  125. package/src/components/paywall-wrappers/HighlightBenefitsPaywallWrapper.tsx +30 -0
  126. package/src/components/paywall-wrappers/KeyFeatureSummaryPaywallWrapper.tsx +30 -0
  127. package/src/components/paywall-wrappers/TextFocusedPaywallWrapper.tsx +30 -0
  128. package/src/components/paywall-wrappers/index.ts +8 -0
  129. package/src/constants/paywall.ts +4 -0
  130. package/src/hooks/useBanner.ts +112 -0
  131. package/src/hooks/useInterval.ts +29 -0
  132. package/src/hooks/usePaywall.ts +73 -0
  133. package/src/index.tsx +36 -1
  134. 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,4 @@
1
+ // Base URLs for webview components
2
+ export const PAYWELL_BASE_URL = 'https://dashboard.monetai.io/webview';
3
+ // WebView user agent
4
+ export const WEBVIEW_USER_AGENT = 'MonetaiSDK';
@@ -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 { MonetaiProvider };
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';