@propel-nsl/propel-react-native-sdk 1.0.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 (221) hide show
  1. package/assets/fonts/Lexend-Black.ttf +0 -0
  2. package/assets/fonts/Lexend-Bold.ttf +0 -0
  3. package/assets/fonts/Lexend-ExtraBold.ttf +0 -0
  4. package/assets/fonts/Lexend-ExtraLight.ttf +0 -0
  5. package/assets/fonts/Lexend-Light.ttf +0 -0
  6. package/assets/fonts/Lexend-Medium.ttf +0 -0
  7. package/assets/fonts/Lexend-Regular.ttf +0 -0
  8. package/assets/fonts/Lexend-SemiBold.ttf +0 -0
  9. package/assets/fonts/Lexend-Thin.ttf +0 -0
  10. package/assets/images/HomeGoods.png +0 -0
  11. package/assets/images/accessories.png +0 -0
  12. package/assets/images/amazon.png +0 -0
  13. package/assets/images/apnaClubLogo.png +0 -0
  14. package/assets/images/apparel.png +0 -0
  15. package/assets/images/backgroundblue.png +0 -0
  16. package/assets/images/bannerCard.png +0 -0
  17. package/assets/images/bottomWave.png +0 -0
  18. package/assets/images/coin_1_1.png +0 -0
  19. package/assets/images/flipkart.png +0 -0
  20. package/assets/images/frame_1171278967.png +0 -0
  21. package/assets/images/gift.png +0 -0
  22. package/assets/images/herobanner.png +0 -0
  23. package/assets/images/hinduPencilsLogo.png +0 -0
  24. package/assets/images/icons/addwhiteicon.png +0 -0
  25. package/assets/images/icons/arrow-back.png +0 -0
  26. package/assets/images/icons/arrowgreen.png +0 -0
  27. package/assets/images/icons/arrowred.png +0 -0
  28. package/assets/images/icons/arrowright.png +0 -0
  29. package/assets/images/icons/arrowup.png +0 -0
  30. package/assets/images/icons/blackarrowdown.png +0 -0
  31. package/assets/images/icons/blackarrowup.png +0 -0
  32. package/assets/images/icons/blackcross.png +0 -0
  33. package/assets/images/icons/call.png +0 -0
  34. package/assets/images/icons/camera.png +0 -0
  35. package/assets/images/icons/cart.png +0 -0
  36. package/assets/images/icons/chat.png +0 -0
  37. package/assets/images/icons/circleblack.png +0 -0
  38. package/assets/images/icons/copy.png +0 -0
  39. package/assets/images/icons/cross.png +0 -0
  40. package/assets/images/icons/delete.png +0 -0
  41. package/assets/images/icons/delivery.png +0 -0
  42. package/assets/images/icons/eVoucher.png +0 -0
  43. package/assets/images/icons/editIcon.png +0 -0
  44. package/assets/images/icons/email.png +0 -0
  45. package/assets/images/icons/eye.png +0 -0
  46. package/assets/images/icons/faq.png +0 -0
  47. package/assets/images/icons/filtericon.png +0 -0
  48. package/assets/images/icons/greyDownArrow.png +0 -0
  49. package/assets/images/icons/help.png +0 -0
  50. package/assets/images/icons/home.png +0 -0
  51. package/assets/images/icons/homeinactive.png +0 -0
  52. package/assets/images/icons/i_blackicon.png +0 -0
  53. package/assets/images/icons/i_icon.png +0 -0
  54. package/assets/images/icons/location.png +0 -0
  55. package/assets/images/icons/logout.png +0 -0
  56. package/assets/images/icons/minus.png +0 -0
  57. package/assets/images/icons/myOrders.png +0 -0
  58. package/assets/images/icons/orders.png +0 -0
  59. package/assets/images/icons/pencillogo.png +0 -0
  60. package/assets/images/icons/pending.png +0 -0
  61. package/assets/images/icons/plus.png +0 -0
  62. package/assets/images/icons/redCross.png +0 -0
  63. package/assets/images/icons/redCrossicon.png +0 -0
  64. package/assets/images/icons/redWarningicon.png +0 -0
  65. package/assets/images/icons/redeem.png +0 -0
  66. package/assets/images/icons/redeemactive.png +0 -0
  67. package/assets/images/icons/redemptionHistory.png +0 -0
  68. package/assets/images/icons/redhelpicon.png +0 -0
  69. package/assets/images/icons/search.png +0 -0
  70. package/assets/images/icons/stopwatch.png +0 -0
  71. package/assets/images/icons/successTick.gif +0 -0
  72. package/assets/images/icons/tick.png +0 -0
  73. package/assets/images/icons/tnc.png +0 -0
  74. package/assets/images/icons/user.png +0 -0
  75. package/assets/images/icons/userredicon.png +0 -0
  76. package/assets/images/icons/wavecorner.png +0 -0
  77. package/assets/images/logo.png +0 -0
  78. package/assets/images/myntra_copy.png +0 -0
  79. package/assets/images/nike.png +0 -0
  80. package/assets/images/oq3p0u0_1.png +0 -0
  81. package/assets/images/profileicon.png +0 -0
  82. package/assets/images/topWave.png +0 -0
  83. package/assets/images/zagglePropelLogo.png +0 -0
  84. package/index.ts +23 -0
  85. package/lib/index.ts +25 -0
  86. package/package.json +70 -0
  87. package/src/PropelSDKScreen.tsx +98 -0
  88. package/src/SDKApp.tsx +540 -0
  89. package/src/bridge/SDKBridge.ts +104 -0
  90. package/src/bridge/index.ts +3 -0
  91. package/src/components/SDKBackHandler.tsx +187 -0
  92. package/src/components/SDKProfile.tsx +87 -0
  93. package/src/components/SDKProfileWrapper.tsx +51 -0
  94. package/src/constants.ts +10 -0
  95. package/src/contexts/SDKContext.tsx +72 -0
  96. package/src/hooks/useSDKBackNavigation.ts +61 -0
  97. package/src/navigation/SDKMainTabNavigator.tsx +315 -0
  98. package/src/navigation/SDKNavigator.tsx +41 -0
  99. package/src/redux/store.ts +32 -0
  100. package/src/screens/Dashboard/index.tsx +531 -0
  101. package/src/screens/Dashboard/styles.ts +512 -0
  102. package/src/screens/DeliveryAddress/index.tsx +221 -0
  103. package/src/screens/DeliveryAddress/styles.ts +122 -0
  104. package/src/screens/E-Vouchers/index.tsx +157 -0
  105. package/src/screens/E-Vouchers/styles.ts +106 -0
  106. package/src/screens/Faq/Faq.constants.ts +164 -0
  107. package/src/screens/Faq/index.tsx +114 -0
  108. package/src/screens/Faq/styles.ts +131 -0
  109. package/src/screens/Help/index.tsx +128 -0
  110. package/src/screens/Help/styles.ts +121 -0
  111. package/src/screens/Login/index.tsx +215 -0
  112. package/src/screens/Login/styles.ts +134 -0
  113. package/src/screens/MyCart/MyCart.constants.ts +13 -0
  114. package/src/screens/MyCart/index.tsx +318 -0
  115. package/src/screens/MyCart/styles.ts +249 -0
  116. package/src/screens/MyOrders/index.tsx +87 -0
  117. package/src/screens/MyOrders/styles.ts +281 -0
  118. package/src/screens/MyProfile/index.tsx +72 -0
  119. package/src/screens/MyProfile/styles.ts +47 -0
  120. package/src/screens/NewDeliveryAddress/index.tsx +360 -0
  121. package/src/screens/NewDeliveryAddress/styles.ts +68 -0
  122. package/src/screens/Onboarding/index.tsx +57 -0
  123. package/src/screens/Onboarding/styles.ts +60 -0
  124. package/src/screens/OrdersDetails/index.tsx +333 -0
  125. package/src/screens/OrdersDetails/styles.ts +262 -0
  126. package/src/screens/OtpVerification/index.tsx +283 -0
  127. package/src/screens/OtpVerification/styles.ts +197 -0
  128. package/src/screens/PaymentMethod/index.tsx +389 -0
  129. package/src/screens/PaymentMethod/styles.ts +246 -0
  130. package/src/screens/PointsLog/index.tsx +286 -0
  131. package/src/screens/PointsLog/styles.ts +156 -0
  132. package/src/screens/ProductDetails/index.tsx +682 -0
  133. package/src/screens/ProductDetails/styles.ts +372 -0
  134. package/src/screens/Profile/index.tsx +368 -0
  135. package/src/screens/Profile/styles.ts +158 -0
  136. package/src/screens/RedemptionHistory/RedemptionHistory.constants.ts +4 -0
  137. package/src/screens/RedemptionHistory/index.tsx +304 -0
  138. package/src/screens/RedemptionHistory/styles.ts +84 -0
  139. package/src/screens/Reedem/index.tsx +345 -0
  140. package/src/screens/Reedem/styles.ts +269 -0
  141. package/src/screens/TnC/TnC.constants.ts +169 -0
  142. package/src/screens/TnC/index.tsx +83 -0
  143. package/src/screens/TnC/styles.ts +88 -0
  144. package/src/screens/TransactionSuccessful/index.tsx +77 -0
  145. package/src/screens/TransactionSuccessful/styles.ts +77 -0
  146. package/src/screens/Verification/index.tsx +58 -0
  147. package/src/screens/Verification/styles.ts +74 -0
  148. package/src/screens/index.ts +23 -0
  149. package/src/types/index.ts +46 -0
  150. package/src-app/components/AmountBreakDownModal/index.tsx +86 -0
  151. package/src-app/components/AmountBreakDownModal/styles.ts +110 -0
  152. package/src-app/components/BottomNavIcons.tsx +125 -0
  153. package/src-app/components/Button.tsx +120 -0
  154. package/src-app/components/Card.tsx +47 -0
  155. package/src-app/components/ConfirmPopup/ConfirmPopup.constants.ts +25 -0
  156. package/src-app/components/ConfirmPopup/index.tsx +48 -0
  157. package/src-app/components/ConfirmPopup/styles.ts +167 -0
  158. package/src-app/components/CustomButton/index.tsx +67 -0
  159. package/src-app/components/CustomButton/styles.ts +44 -0
  160. package/src-app/components/CustomCard/index.tsx +221 -0
  161. package/src-app/components/CustomCard/styles.ts +184 -0
  162. package/src-app/components/CustomError/index.tsx +54 -0
  163. package/src-app/components/CustomError/styles.ts +41 -0
  164. package/src-app/components/CustomImage/index.tsx +37 -0
  165. package/src-app/components/CustomImage/styles.ts +5 -0
  166. package/src-app/components/CustomLoader/index.tsx +45 -0
  167. package/src-app/components/CustomLoader/styles.ts +35 -0
  168. package/src-app/components/CustomMessagePopUp/index.tsx +51 -0
  169. package/src-app/components/CustomMessagePopUp/styles.ts +74 -0
  170. package/src-app/components/CustomProductCard/index.tsx +13 -0
  171. package/src-app/components/CustomProductCard/styles.ts +5 -0
  172. package/src-app/components/FilterModal.tsx +372 -0
  173. package/src-app/components/Footer/index.tsx +23 -0
  174. package/src-app/components/Footer/styles.ts +37 -0
  175. package/src-app/components/Icon.tsx +80 -0
  176. package/src-app/components/Logout/index.tsx +82 -0
  177. package/src-app/components/Logout/styles.ts +116 -0
  178. package/src-app/components/MobileHeader.tsx +141 -0
  179. package/src-app/components/NoDataFound/index.tsx +18 -0
  180. package/src-app/components/NoDataFound/styles.ts +26 -0
  181. package/src-app/components/OTPModal.tsx +747 -0
  182. package/src-app/components/ProfileField.tsx +47 -0
  183. package/src-app/components/QuantityModal/index.tsx +113 -0
  184. package/src-app/components/QuantityModal/styles.ts +84 -0
  185. package/src-app/components/TabBarIcons.tsx +110 -0
  186. package/src-app/components/TextInput.tsx +79 -0
  187. package/src-app/components/ToastConfig.tsx +60 -0
  188. package/src-app/components/index.ts +18 -0
  189. package/src-app/config/env.ts +22 -0
  190. package/src-app/constants/Fonts.ts +12 -0
  191. package/src-app/constants/Formatter.ts +39 -0
  192. package/src-app/constants/HtmlSanitization.ts +46 -0
  193. package/src-app/constants/Images.ts +81 -0
  194. package/src-app/constants/Labels.ts +8 -0
  195. package/src-app/constants/Messages.ts +108 -0
  196. package/src-app/constants/Routes.ts +17 -0
  197. package/src-app/constants/Scaling.ts +5 -0
  198. package/src-app/constants/Text.ts +8 -0
  199. package/src-app/constants/offSets.ts +18 -0
  200. package/src-app/hooks/useAppDispatch.ts +4 -0
  201. package/src-app/hooks/useAppSelector.ts +4 -0
  202. package/src-app/hooks/useBackHandler.ts +47 -0
  203. package/src-app/hooks/useScreenBackHandler.ts +91 -0
  204. package/src-app/navigation/AppNavigator.tsx +34 -0
  205. package/src-app/navigation/MainTabNavigator.tsx +294 -0
  206. package/src-app/redux/authSaga.ts +605 -0
  207. package/src-app/redux/authSlice.ts +754 -0
  208. package/src-app/redux/rootSaga.ts +6 -0
  209. package/src-app/redux/store.ts +25 -0
  210. package/src-app/services/api.ts +14 -0
  211. package/src-app/services/endpoints.ts +33 -0
  212. package/src-app/services/index.ts +574 -0
  213. package/src-app/services/sdkCredentials.ts +44 -0
  214. package/src-app/styles/colors.ts +85 -0
  215. package/src-app/styles/shared.ts +112 -0
  216. package/src-app/types/authTypes.ts +155 -0
  217. package/src-app/types/navigation.ts +99 -0
  218. package/src-app/utils/Validation.ts +48 -0
  219. package/src-app/utils/filterPins.ts +29 -0
  220. package/src-app/utils/navigationUtils.ts +43 -0
  221. package/src-app/utils/useHardwareBack.ts +21 -0
package/src/SDKApp.tsx ADDED
@@ -0,0 +1,540 @@
1
+ /**
2
+ * SDK App Component
3
+ *
4
+ * Main React Native component that wraps the SDK functionality.
5
+ * This is the root component registered with AppRegistry.
6
+ */
7
+
8
+ import React, { useEffect, useState } from 'react';
9
+ import { CommonActions, NavigationContainer, useNavigationContainerRef } from '@react-navigation/native';
10
+ import { Provider } from 'react-redux';
11
+ import { SafeAreaProvider } from 'react-native-safe-area-context';
12
+ import { GestureHandlerRootView } from 'react-native-gesture-handler';
13
+ import { Linking, Platform } from 'react-native';
14
+ import Toast from 'react-native-toast-message';
15
+ import AsyncStorage from '@react-native-async-storage/async-storage';
16
+
17
+ import { store } from './redux/store';
18
+ import SDKNavigator from './navigation/SDKNavigator';
19
+ import { sdkBridge } from './bridge';
20
+ import { SDKParams } from './types';
21
+ import { toastConfig } from 'src-app/components/ToastConfig';
22
+ import ENV, { setApiUrl } from 'src-app/config/env';
23
+ import { SDKProvider } from './contexts/SDKContext';
24
+ import { clearAllAuthState, sdkAuthRequest, sdkAuthSuccess, sdkAuthFailure } from 'src-app/redux/authSlice';
25
+ import { sdkAuthApi } from 'src-app/services';
26
+ import { setSDKCredentials, clearSDKCredentials } from 'src-app/services/sdkCredentials';
27
+
28
+ // Helper to detect if deep link is for RedemptionHistory
29
+ const isDeepLinkToRedemptionHistory = (params: any): boolean => {
30
+ if (!params) return false;
31
+
32
+ const checkValue = (val: any): boolean => {
33
+ if (!val || typeof val !== 'string') return false;
34
+ const normalized = val.trim().toLowerCase();
35
+ return (
36
+ normalized === 'transaction-history' ||
37
+ normalized === 'transaction_history' ||
38
+ normalized === 'transactionhistory' ||
39
+ normalized === 'redemption-history' ||
40
+ normalized === 'redemption_history' ||
41
+ normalized === 'redemptionhistory' ||
42
+ normalized === 'redeem-history' ||
43
+ normalized === 'redeemhistory'
44
+ );
45
+ };
46
+
47
+ return (
48
+ checkValue(params.screen) ||
49
+ checkValue(params.route) ||
50
+ checkValue(params.initialRoute) ||
51
+ checkValue(params.path) ||
52
+ checkValue(params.url) ||
53
+ checkValue(params.customParams?.screen) ||
54
+ checkValue(params.customParams?.route)
55
+ );
56
+ };
57
+
58
+ // Helper to detect if deep link is for Redeem screen
59
+ const isDeepLinkToRedeem = (params: any): boolean => {
60
+ if (!params) return false;
61
+
62
+ const checkValue = (val: any): boolean => {
63
+ if (!val || typeof val !== 'string') return false;
64
+ const normalized = val.trim().toLowerCase();
65
+ return (
66
+ normalized === 'redeem' ||
67
+ normalized === 'redeem-screen' ||
68
+ normalized === 'redeem_screen' ||
69
+ normalized === 'redeemscreen'
70
+ );
71
+ };
72
+
73
+ return (
74
+ checkValue(params.screen) ||
75
+ checkValue(params.route) ||
76
+ checkValue(params.initialRoute) ||
77
+ checkValue(params.path) ||
78
+ checkValue(params.url) ||
79
+ checkValue(params.customParams?.screen) ||
80
+ checkValue(params.customParams?.route)
81
+ );
82
+ };
83
+
84
+ const SDKApp: React.FC<any> = (props) => {
85
+ const [initialParams, setInitialParams] = useState<SDKParams | null>(null);
86
+ const [isReady, setIsReady] = useState(false);
87
+ const initialPropsRef = React.useRef<any>(props);
88
+ const navigationRef = useNavigationContainerRef();
89
+ const pendingNavigationRef = React.useRef<(() => void) | null>(null);
90
+
91
+ // Unique key to force NavigationContainer remount and reset navigation state
92
+ // This ensures fresh navigation state each time SDK is opened
93
+ const [navigationKey] = useState(() => `sdk-nav-${Date.now()}`);
94
+
95
+ // Determine initial route based on deep link in initialParams (from bridge)
96
+ // IMPORTANT: Use initialParams (loaded from bridge) NOT props (which may be stale)
97
+ const deepLinkToRedemptionHistory = React.useMemo(
98
+ () => initialParams ? isDeepLinkToRedemptionHistory(initialParams) : false,
99
+ [initialParams] // Re-evaluate when initialParams changes
100
+ );
101
+
102
+ const deepLinkToRedeem = React.useMemo(
103
+ () => initialParams ? isDeepLinkToRedeem(initialParams) : false,
104
+ [initialParams] // Re-evaluate when initialParams changes
105
+ );
106
+
107
+
108
+ // State to track if authentication is complete for deep link navigation
109
+ const [isAuthenticated, setIsAuthenticated] = useState(false);
110
+ const pendingDeepLinkRef = React.useRef<{ target: string; from?: string } | null>(null);
111
+ // Track if deep link has already been handled to prevent duplicate calls
112
+ const deepLinkHandledRef = React.useRef(false);
113
+
114
+ // Authenticate SDK and store token - returns true if successful
115
+ const authenticateSDK = React.useCallback(async (): Promise<boolean> => {
116
+ console.log('🔐 authenticateSDK: Starting SDK authentication...');
117
+ try {
118
+ store.dispatch(sdkAuthRequest({}));
119
+ const response = await sdkAuthApi();
120
+ console.log('🔐 authenticateSDK: Response received:', response.data);
121
+
122
+ if (response.data?.status === true && response.data?.session?.token) {
123
+ const token = response.data.session.token;
124
+ await AsyncStorage.setItem('accessToken', token);
125
+ console.log('🔐 authenticateSDK: Token stored successfully');
126
+ store.dispatch(sdkAuthSuccess(response.data));
127
+ setIsAuthenticated(true);
128
+ return true;
129
+ } else {
130
+ const errorMsg = response.data?.message || 'SDK authentication failed';
131
+ console.log('🔐 authenticateSDK: Auth failed -', errorMsg);
132
+ store.dispatch(sdkAuthFailure(errorMsg));
133
+ return false;
134
+ }
135
+ } catch (error: any) {
136
+ console.log('🔐 authenticateSDK: Error -', error?.response?.data || error?.message);
137
+ store.dispatch(
138
+ sdkAuthFailure(
139
+ error?.response?.data?.message || error?.message || 'SDK authentication failed'
140
+ )
141
+ );
142
+ return false;
143
+ }
144
+ }, []);
145
+
146
+ const navigateToTransactionHistory = React.useCallback(
147
+ (from?: string) => {
148
+ console.log('🚀 navigateToTransactionHistory called, from=', from);
149
+
150
+ const doNavigate = () => {
151
+ console.log('🚀 doNavigate executing - navigating to RedemptionHistory');
152
+ // Navigate to RedemptionHistory hidden tab
153
+ navigationRef.dispatch(
154
+ CommonActions.navigate({
155
+ name: 'MainTabs',
156
+ params: {
157
+ screen: 'RedemptionHistory',
158
+ params: {
159
+ from: from || 'DeepLink',
160
+ exitToHost: from === 'HostApp' ? true : undefined,
161
+ },
162
+ },
163
+ })
164
+ );
165
+ console.log('🚀 Navigation to RedemptionHistory completed');
166
+ };
167
+
168
+ const isNavReady = navigationRef.isReady();
169
+ console.log('🚀 navigationRef.isReady()=', isNavReady);
170
+
171
+ if (!isNavReady) {
172
+ console.log('🚀 Navigation not ready, storing in pendingNavigationRef');
173
+ pendingNavigationRef.current = doNavigate;
174
+ return;
175
+ }
176
+
177
+ doNavigate();
178
+ },
179
+ [navigationRef]
180
+ );
181
+
182
+ const navigateToRedemptionHistory = React.useCallback(
183
+ (from?: string) => {
184
+ const doNavigate = () => {
185
+ // Navigate to RedemptionHistory hidden tab
186
+ navigationRef.dispatch(
187
+ CommonActions.navigate({
188
+ name: 'MainTabs',
189
+ params: {
190
+ screen: 'RedemptionHistory',
191
+ params: { from: from || 'DeepLink' },
192
+ },
193
+ })
194
+ );
195
+ };
196
+
197
+ if (!navigationRef.isReady()) {
198
+ pendingNavigationRef.current = doNavigate;
199
+ return;
200
+ }
201
+
202
+ doNavigate();
203
+ },
204
+ [navigationRef]
205
+ );
206
+
207
+ const navigateToRedeem = React.useCallback(
208
+ (from?: string) => {
209
+ console.log('🚀 navigateToRedeem called, from=', from);
210
+
211
+ const doNavigate = () => {
212
+ console.log('🚀 doNavigate executing - navigating to Redeem tab');
213
+ // Navigate to Redeem tab
214
+ navigationRef.dispatch(
215
+ CommonActions.navigate({
216
+ name: 'MainTabs',
217
+ params: { screen: 'Redeem' },
218
+ })
219
+ );
220
+ console.log('🚀 Navigation to Redeem completed');
221
+ };
222
+
223
+ const isNavReady = navigationRef.isReady();
224
+ console.log('🚀 navigationRef.isReady()=', isNavReady);
225
+
226
+ if (!isNavReady) {
227
+ console.log('🚀 Navigation not ready, storing in pendingNavigationRef');
228
+ pendingNavigationRef.current = doNavigate;
229
+ return;
230
+ }
231
+
232
+ doNavigate();
233
+ },
234
+ [navigationRef]
235
+ );
236
+
237
+ // Authenticate first, then navigate to deep link target
238
+ const authenticateAndNavigate = React.useCallback(
239
+ async (target: string, from?: string) => {
240
+ console.log('🔐 authenticateAndNavigate: target=', target, 'from=', from);
241
+
242
+ const authSuccess = await authenticateSDK();
243
+
244
+ if (!authSuccess) {
245
+ console.log('🔐 authenticateAndNavigate: Auth failed, cannot navigate');
246
+ return;
247
+ }
248
+
249
+ console.log('🔐 authenticateAndNavigate: Auth successful, proceeding to navigate');
250
+
251
+ // Navigate based on target
252
+ if (target === 'RedemptionHistory') {
253
+ navigateToTransactionHistory(from);
254
+ } else if (target === 'Redeem') {
255
+ navigateToRedeem(from);
256
+ }
257
+ },
258
+ [authenticateSDK, navigateToTransactionHistory, navigateToRedeem]
259
+ );
260
+
261
+ const maybeHandleRedeemHistoryIntent = React.useCallback(
262
+ (intent?: any) => {
263
+ if (!intent) return;
264
+
265
+ const raw =
266
+ typeof intent === 'string'
267
+ ? intent
268
+ : intent?.url || intent?.path || intent?.route || intent?.screen || intent?.initialRoute;
269
+
270
+
271
+ if (!raw || typeof raw !== 'string') return;
272
+
273
+ const normalized = raw.trim().toLowerCase();
274
+ // Accept both full URLs (propelsdk://redeem-history) and plain route tokens.
275
+ const path = normalized
276
+ .replace(/^[a-z][a-z0-9+.-]*:\/\//, '')
277
+ .split('?')[0]
278
+ .split('#')[0];
279
+
280
+ const lastSegment = path.split('/').filter(Boolean).pop() || '';
281
+
282
+ const isRedemptionHistory =
283
+ normalized === 'redeem history' ||
284
+ normalized === 'redemption history' ||
285
+ normalized === 'redemptionhistory' ||
286
+ normalized === 'redemption_history' ||
287
+ lastSegment === 'redemptionhistory' ||
288
+ lastSegment === 'redemption_history' ||
289
+ lastSegment === 'redemption-history' ||
290
+ lastSegment === 'redeem-history' ||
291
+ lastSegment === 'redeemhistory';
292
+
293
+ const isTransactionHistory =
294
+ normalized === 'transaction history' ||
295
+ normalized === 'transactionhistory' ||
296
+ normalized === 'transaction_history' ||
297
+ normalized === 'transaction-history' ||
298
+ lastSegment === 'transactionhistory' ||
299
+ lastSegment === 'transaction_history' ||
300
+ lastSegment === 'transaction-history';
301
+
302
+ const isRedeem =
303
+ normalized === 'redeem' ||
304
+ normalized === 'redeem-screen' ||
305
+ normalized === 'redeem_screen' ||
306
+ normalized === 'redeemscreen' ||
307
+ lastSegment === 'redeem' ||
308
+ lastSegment === 'redeem-screen' ||
309
+ lastSegment === 'redeem_screen' ||
310
+ lastSegment === 'redeemscreen';
311
+
312
+ if (isRedemptionHistory || isTransactionHistory) {
313
+ // Prevent duplicate handling
314
+ if (deepLinkHandledRef.current) {
315
+ console.log('🚀 Deep link already handled, skipping duplicate call');
316
+ return;
317
+ }
318
+ deepLinkHandledRef.current = true;
319
+ console.log('🚀 Intent matched! Will authenticate then navigate to TransactionHistory...');
320
+ // Store pending deep link and authenticate first
321
+ pendingDeepLinkRef.current = { target: 'RedemptionHistory', from: 'HostApp' };
322
+ authenticateAndNavigate('RedemptionHistory', 'HostApp');
323
+ } else if (isRedeem) {
324
+ // Prevent duplicate handling
325
+ if (deepLinkHandledRef.current) {
326
+ console.log('🚀 Deep link already handled, skipping duplicate call');
327
+ return;
328
+ }
329
+ deepLinkHandledRef.current = true;
330
+ console.log('🚀 Intent matched! Will authenticate then navigate to Redeem...');
331
+ pendingDeepLinkRef.current = { target: 'Redeem', from: 'HostApp' };
332
+ authenticateAndNavigate('Redeem', 'HostApp');
333
+ }
334
+ },
335
+ [authenticateAndNavigate]
336
+ );
337
+
338
+ useEffect(() => {
339
+ // Clear auth state on SDK mount to ensure fresh authentication each time
340
+ // This prevents stale data from previous SDK sessions
341
+ const clearPreviousSession = async () => {
342
+ store.dispatch(clearAllAuthState());
343
+ clearSDKCredentials();
344
+ // Also clear the stored access token to force re-authentication
345
+ await AsyncStorage.removeItem('accessToken');
346
+ };
347
+ clearPreviousSession();
348
+
349
+ // Reset deep link handled flag and pending deep link on each SDK mount
350
+ // This ensures fresh deep link handling for each SDK session
351
+ deepLinkHandledRef.current = false;
352
+ pendingDeepLinkRef.current = null;
353
+ }, []);
354
+
355
+ useEffect(() => {
356
+ // Get initial parameters from native host app OR from RN host props
357
+ const loadParams = async () => {
358
+ try {
359
+ const baseParams = initialPropsRef.current || {};
360
+ const isRNHostMode = !!baseParams.__rnHostMode;
361
+
362
+ // In RN host mode, skip native bridge entirely — use props as the source
363
+ const bridgeParams = isRNHostMode ? {} : (await sdkBridge.getInitialParams()) || {};
364
+
365
+ // IMPORTANT: Bridge params (from native) take precedence for deep link params
366
+ // Only use initialPropsRef for base config like apiUrl, userToken
367
+ // This prevents stale customParams from previous sessions
368
+ const mergedParams = {
369
+ ...baseParams,
370
+ ...bridgeParams,
371
+ // Explicitly handle deep link related params - bridge takes full precedence
372
+ // In RN host mode, baseParams IS the source of truth
373
+ screen: isRNHostMode ? (baseParams.screen || baseParams.customParams?.screen) : bridgeParams.screen,
374
+ route: isRNHostMode ? baseParams.route : bridgeParams.route,
375
+ initialRoute: isRNHostMode ? baseParams.initialRoute : bridgeParams.initialRoute,
376
+ customParams: isRNHostMode ? baseParams.customParams : bridgeParams.customParams,
377
+ } as SDKParams;
378
+
379
+ console.log('🔍 SDKApp loadParams: bridgeParams.customParams=', bridgeParams.customParams);
380
+ console.log('🔍 SDKApp loadParams: mergedParams.customParams=', mergedParams.customParams);
381
+
382
+ if (mergedParams?.userToken && mergedParams.userToken !== 'null') {
383
+ await AsyncStorage.setItem('accessToken', mergedParams.userToken);
384
+ }
385
+
386
+ // Override API URL if provided by host app
387
+ if (mergedParams?.apiUrl) {
388
+ setApiUrl(mergedParams.apiUrl);
389
+ }
390
+
391
+ // Set SDK credentials for authentication
392
+ if (mergedParams?.authToken && mergedParams?.authId) {
393
+ setSDKCredentials({
394
+ authToken: mergedParams.authToken,
395
+ authId: mergedParams.authId,
396
+ userId: mergedParams.userId,
397
+ userName: mergedParams.userName,
398
+ userMobile: mergedParams.userMobile,
399
+ userEmail: mergedParams.userEmail,
400
+ deviceId: mergedParams.deviceId,
401
+ clientName: mergedParams.clientName,
402
+ clientVersion: mergedParams.clientVersion,
403
+ environment: mergedParams.environment,
404
+ });
405
+ }
406
+
407
+ setInitialParams(mergedParams);
408
+ setIsReady(true);
409
+ } catch (error) {
410
+ console.error('Failed to load SDK params:', error);
411
+ // Use default params if bridge fails
412
+ const mergedParams = {
413
+ apiUrl: ENV.apiUrl,
414
+ environment: 'production',
415
+ // Don't include deep link params from cached initialPropsRef on error
416
+ } as SDKParams;
417
+
418
+ if (mergedParams?.apiUrl) {
419
+ setApiUrl(mergedParams.apiUrl);
420
+ }
421
+
422
+ setInitialParams(mergedParams);
423
+ setIsReady(true);
424
+ }
425
+ };
426
+
427
+ loadParams();
428
+ }, []);
429
+
430
+ useEffect(() => {
431
+ // Handle deep link intents passed via initial params/props
432
+ if (!initialParams) return;
433
+
434
+ // Log for debugging
435
+ console.log('🔍 SDKApp: Checking for deep link intent in initialParams:', {
436
+ screen: initialParams?.screen,
437
+ route: initialParams?.route,
438
+ initialRoute: initialParams?.initialRoute,
439
+ customParams: initialParams?.customParams,
440
+ });
441
+
442
+ // Only process deep links if current initialParams actually has deep link data
443
+ // These values come from the bridge (native) and are NOT cached from previous sessions
444
+ const hasDeepLinkInCurrentParams =
445
+ initialParams?.screen ||
446
+ initialParams?.route ||
447
+ initialParams?.initialRoute ||
448
+ (initialParams?.customParams && Object.keys(initialParams.customParams).length > 0);
449
+
450
+ if (!hasDeepLinkInCurrentParams) {
451
+ console.log('🔍 SDKApp: No deep link params in current session, skipping deep link handling');
452
+ return;
453
+ }
454
+
455
+ // Only use initialParams (which has bridge values) - don't use initialPropsRef which may be stale
456
+ maybeHandleRedeemHistoryIntent(initialParams);
457
+ maybeHandleRedeemHistoryIntent(initialParams?.customParams);
458
+ }, [initialParams, maybeHandleRedeemHistoryIntent]);
459
+
460
+ useEffect(() => {
461
+ const handleUrl = (url: string) => {
462
+ maybeHandleRedeemHistoryIntent(url);
463
+ };
464
+
465
+ Linking.getInitialURL()
466
+ .then((url) => {
467
+ if (url) handleUrl(url);
468
+ })
469
+ .catch(() => {
470
+ // ignore
471
+ });
472
+
473
+ const subscription = Linking.addEventListener('url', ({ url }) => {
474
+ if (url) handleUrl(url);
475
+ });
476
+
477
+ return () => {
478
+ subscription.remove();
479
+ };
480
+ }, [maybeHandleRedeemHistoryIntent]);
481
+
482
+ if (!isReady || !initialParams) {
483
+ // Return loading state or null while initializing
484
+ return null;
485
+ }
486
+
487
+ // Determine if app was opened via deep link
488
+ const isDeepLinkEntryValue = deepLinkToRedemptionHistory || deepLinkToRedeem;
489
+ const deepLinkTargetValue = deepLinkToRedemptionHistory ? 'RedemptionHistory' : (deepLinkToRedeem ? 'Redeem' : undefined);
490
+ const isRNHostMode = !!(initialParams as any)?.__rnHostMode;
491
+
492
+ console.log('🔍 SDKApp render: deepLinkToRedemptionHistory=', deepLinkToRedemptionHistory, 'deepLinkToRedeem=', deepLinkToRedeem, 'deepLinkTargetValue=', deepLinkTargetValue);
493
+
494
+ return (
495
+ <GestureHandlerRootView style={{ flex: 1 }}>
496
+ <Provider store={store}>
497
+ <SDKProvider
498
+ isSDK={true}
499
+ initialDeepLinkEntry={isDeepLinkEntryValue}
500
+ initialDeepLinkTarget={deepLinkTargetValue}
501
+ >
502
+ <SafeAreaProvider>
503
+ <NavigationContainer
504
+ key={navigationKey}
505
+ ref={navigationRef}
506
+ independent={isRNHostMode}
507
+ onReady={() => {
508
+ console.log('🚀 NavigationContainer onReady fired');
509
+ console.log('🚀 Navigation state after ready:', JSON.stringify(navigationRef.current?.getRootState(), null, 2));
510
+
511
+ // Execute any pending navigation
512
+ if (pendingNavigationRef.current) {
513
+ console.log('🚀 Executing pending navigation');
514
+ const pendingNav = pendingNavigationRef.current;
515
+ pendingNavigationRef.current = null;
516
+ // Use setTimeout to ensure navigation state is fully ready
517
+ setTimeout(() => {
518
+ pendingNav();
519
+ }, 100);
520
+ }
521
+ }}
522
+ >
523
+ <SDKNavigator
524
+ initialParams={initialParams}
525
+ initialRouteOverride={deepLinkTargetValue}
526
+ />
527
+ </NavigationContainer>
528
+ <Toast
529
+ config={toastConfig}
530
+ topOffset={Platform.OS === 'ios' ? 60 : 40}
531
+ />
532
+ </SafeAreaProvider>
533
+ </SDKProvider>
534
+ </Provider>
535
+ </GestureHandlerRootView>
536
+ );
537
+ };
538
+
539
+ export default SDKApp;
540
+
@@ -0,0 +1,104 @@
1
+ /**
2
+ * SDK Bridge Module
3
+ *
4
+ * Handles communication between native host app and React Native SDK
5
+ */
6
+
7
+ import { NativeModules, NativeEventEmitter, Platform } from 'react-native';
8
+
9
+ export interface SDKBridgeParams {
10
+ apiUrl: string;
11
+ apiKey?: string;
12
+ userId?: string;
13
+ userToken?: string;
14
+ theme?: any;
15
+ environment?: string;
16
+ [key: string]: any;
17
+ }
18
+
19
+ export interface SDKBridgeResult {
20
+ success: boolean;
21
+ data?: any;
22
+ message?: string;
23
+ }
24
+
25
+ class SDKBridge {
26
+ private bridgeModule: any;
27
+ private eventEmitter: NativeEventEmitter | null = null;
28
+
29
+ constructor() {
30
+ this.bridgeModule = NativeModules.PropelSDKBridge || NativeModules.PropelSDKModule;
31
+
32
+ if (this.bridgeModule) {
33
+ this.eventEmitter = new NativeEventEmitter(this.bridgeModule);
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Get initial parameters from native host app
39
+ */
40
+ getInitialParams(): Promise<SDKBridgeParams> {
41
+ if (this.bridgeModule && this.bridgeModule.getInitialParams) {
42
+ return this.bridgeModule.getInitialParams();
43
+ }
44
+ return Promise.resolve({} as SDKBridgeParams);
45
+ }
46
+
47
+ /**
48
+ * Send result/response back to native host app
49
+ */
50
+ sendResult(result: SDKBridgeResult): void {
51
+ if (this.bridgeModule && this.bridgeModule.sendResult) {
52
+ this.bridgeModule.sendResult(result);
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Send event to native host app
58
+ */
59
+ sendEvent(eventType: string, payload?: any): void {
60
+ if (this.bridgeModule && this.bridgeModule.sendEvent) {
61
+ this.bridgeModule.sendEvent(eventType, payload);
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Listen to events from native host app
67
+ */
68
+ addEventListener(eventType: string, callback: (data: any) => void): any {
69
+ if (this.eventEmitter) {
70
+ return this.eventEmitter.addListener(eventType, callback);
71
+ }
72
+ return null;
73
+ }
74
+
75
+ /**
76
+ * Remove event listener
77
+ */
78
+ removeEventListener(listener: any): void {
79
+ if (listener) {
80
+ listener.remove();
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Dismiss SDK (close the SDK view)
86
+ */
87
+ dismiss(): void {
88
+ if (this.bridgeModule && this.bridgeModule.dismiss) {
89
+ this.bridgeModule.dismiss();
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Check if bridge is available
95
+ */
96
+ isAvailable(): boolean {
97
+ return !!this.bridgeModule;
98
+ }
99
+ }
100
+
101
+ // Export singleton instance
102
+ export const sdkBridge = new SDKBridge();
103
+ export default sdkBridge;
104
+
@@ -0,0 +1,3 @@
1
+ export { default as sdkBridge, SDKBridge } from './SDKBridge';
2
+ export type { SDKBridgeParams, SDKBridgeResult } from './SDKBridge';
3
+