@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.
- package/assets/fonts/Lexend-Black.ttf +0 -0
- package/assets/fonts/Lexend-Bold.ttf +0 -0
- package/assets/fonts/Lexend-ExtraBold.ttf +0 -0
- package/assets/fonts/Lexend-ExtraLight.ttf +0 -0
- package/assets/fonts/Lexend-Light.ttf +0 -0
- package/assets/fonts/Lexend-Medium.ttf +0 -0
- package/assets/fonts/Lexend-Regular.ttf +0 -0
- package/assets/fonts/Lexend-SemiBold.ttf +0 -0
- package/assets/fonts/Lexend-Thin.ttf +0 -0
- package/assets/images/HomeGoods.png +0 -0
- package/assets/images/accessories.png +0 -0
- package/assets/images/amazon.png +0 -0
- package/assets/images/apnaClubLogo.png +0 -0
- package/assets/images/apparel.png +0 -0
- package/assets/images/backgroundblue.png +0 -0
- package/assets/images/bannerCard.png +0 -0
- package/assets/images/bottomWave.png +0 -0
- package/assets/images/coin_1_1.png +0 -0
- package/assets/images/flipkart.png +0 -0
- package/assets/images/frame_1171278967.png +0 -0
- package/assets/images/gift.png +0 -0
- package/assets/images/herobanner.png +0 -0
- package/assets/images/hinduPencilsLogo.png +0 -0
- package/assets/images/icons/addwhiteicon.png +0 -0
- package/assets/images/icons/arrow-back.png +0 -0
- package/assets/images/icons/arrowgreen.png +0 -0
- package/assets/images/icons/arrowred.png +0 -0
- package/assets/images/icons/arrowright.png +0 -0
- package/assets/images/icons/arrowup.png +0 -0
- package/assets/images/icons/blackarrowdown.png +0 -0
- package/assets/images/icons/blackarrowup.png +0 -0
- package/assets/images/icons/blackcross.png +0 -0
- package/assets/images/icons/call.png +0 -0
- package/assets/images/icons/camera.png +0 -0
- package/assets/images/icons/cart.png +0 -0
- package/assets/images/icons/chat.png +0 -0
- package/assets/images/icons/circleblack.png +0 -0
- package/assets/images/icons/copy.png +0 -0
- package/assets/images/icons/cross.png +0 -0
- package/assets/images/icons/delete.png +0 -0
- package/assets/images/icons/delivery.png +0 -0
- package/assets/images/icons/eVoucher.png +0 -0
- package/assets/images/icons/editIcon.png +0 -0
- package/assets/images/icons/email.png +0 -0
- package/assets/images/icons/eye.png +0 -0
- package/assets/images/icons/faq.png +0 -0
- package/assets/images/icons/filtericon.png +0 -0
- package/assets/images/icons/greyDownArrow.png +0 -0
- package/assets/images/icons/help.png +0 -0
- package/assets/images/icons/home.png +0 -0
- package/assets/images/icons/homeinactive.png +0 -0
- package/assets/images/icons/i_blackicon.png +0 -0
- package/assets/images/icons/i_icon.png +0 -0
- package/assets/images/icons/location.png +0 -0
- package/assets/images/icons/logout.png +0 -0
- package/assets/images/icons/minus.png +0 -0
- package/assets/images/icons/myOrders.png +0 -0
- package/assets/images/icons/orders.png +0 -0
- package/assets/images/icons/pencillogo.png +0 -0
- package/assets/images/icons/pending.png +0 -0
- package/assets/images/icons/plus.png +0 -0
- package/assets/images/icons/redCross.png +0 -0
- package/assets/images/icons/redCrossicon.png +0 -0
- package/assets/images/icons/redWarningicon.png +0 -0
- package/assets/images/icons/redeem.png +0 -0
- package/assets/images/icons/redeemactive.png +0 -0
- package/assets/images/icons/redemptionHistory.png +0 -0
- package/assets/images/icons/redhelpicon.png +0 -0
- package/assets/images/icons/search.png +0 -0
- package/assets/images/icons/stopwatch.png +0 -0
- package/assets/images/icons/successTick.gif +0 -0
- package/assets/images/icons/tick.png +0 -0
- package/assets/images/icons/tnc.png +0 -0
- package/assets/images/icons/user.png +0 -0
- package/assets/images/icons/userredicon.png +0 -0
- package/assets/images/icons/wavecorner.png +0 -0
- package/assets/images/logo.png +0 -0
- package/assets/images/myntra_copy.png +0 -0
- package/assets/images/nike.png +0 -0
- package/assets/images/oq3p0u0_1.png +0 -0
- package/assets/images/profileicon.png +0 -0
- package/assets/images/topWave.png +0 -0
- package/assets/images/zagglePropelLogo.png +0 -0
- package/index.ts +23 -0
- package/lib/index.ts +25 -0
- package/package.json +70 -0
- package/src/PropelSDKScreen.tsx +98 -0
- package/src/SDKApp.tsx +540 -0
- package/src/bridge/SDKBridge.ts +104 -0
- package/src/bridge/index.ts +3 -0
- package/src/components/SDKBackHandler.tsx +187 -0
- package/src/components/SDKProfile.tsx +87 -0
- package/src/components/SDKProfileWrapper.tsx +51 -0
- package/src/constants.ts +10 -0
- package/src/contexts/SDKContext.tsx +72 -0
- package/src/hooks/useSDKBackNavigation.ts +61 -0
- package/src/navigation/SDKMainTabNavigator.tsx +315 -0
- package/src/navigation/SDKNavigator.tsx +41 -0
- package/src/redux/store.ts +32 -0
- package/src/screens/Dashboard/index.tsx +531 -0
- package/src/screens/Dashboard/styles.ts +512 -0
- package/src/screens/DeliveryAddress/index.tsx +221 -0
- package/src/screens/DeliveryAddress/styles.ts +122 -0
- package/src/screens/E-Vouchers/index.tsx +157 -0
- package/src/screens/E-Vouchers/styles.ts +106 -0
- package/src/screens/Faq/Faq.constants.ts +164 -0
- package/src/screens/Faq/index.tsx +114 -0
- package/src/screens/Faq/styles.ts +131 -0
- package/src/screens/Help/index.tsx +128 -0
- package/src/screens/Help/styles.ts +121 -0
- package/src/screens/Login/index.tsx +215 -0
- package/src/screens/Login/styles.ts +134 -0
- package/src/screens/MyCart/MyCart.constants.ts +13 -0
- package/src/screens/MyCart/index.tsx +318 -0
- package/src/screens/MyCart/styles.ts +249 -0
- package/src/screens/MyOrders/index.tsx +87 -0
- package/src/screens/MyOrders/styles.ts +281 -0
- package/src/screens/MyProfile/index.tsx +72 -0
- package/src/screens/MyProfile/styles.ts +47 -0
- package/src/screens/NewDeliveryAddress/index.tsx +360 -0
- package/src/screens/NewDeliveryAddress/styles.ts +68 -0
- package/src/screens/Onboarding/index.tsx +57 -0
- package/src/screens/Onboarding/styles.ts +60 -0
- package/src/screens/OrdersDetails/index.tsx +333 -0
- package/src/screens/OrdersDetails/styles.ts +262 -0
- package/src/screens/OtpVerification/index.tsx +283 -0
- package/src/screens/OtpVerification/styles.ts +197 -0
- package/src/screens/PaymentMethod/index.tsx +389 -0
- package/src/screens/PaymentMethod/styles.ts +246 -0
- package/src/screens/PointsLog/index.tsx +286 -0
- package/src/screens/PointsLog/styles.ts +156 -0
- package/src/screens/ProductDetails/index.tsx +682 -0
- package/src/screens/ProductDetails/styles.ts +372 -0
- package/src/screens/Profile/index.tsx +368 -0
- package/src/screens/Profile/styles.ts +158 -0
- package/src/screens/RedemptionHistory/RedemptionHistory.constants.ts +4 -0
- package/src/screens/RedemptionHistory/index.tsx +304 -0
- package/src/screens/RedemptionHistory/styles.ts +84 -0
- package/src/screens/Reedem/index.tsx +345 -0
- package/src/screens/Reedem/styles.ts +269 -0
- package/src/screens/TnC/TnC.constants.ts +169 -0
- package/src/screens/TnC/index.tsx +83 -0
- package/src/screens/TnC/styles.ts +88 -0
- package/src/screens/TransactionSuccessful/index.tsx +77 -0
- package/src/screens/TransactionSuccessful/styles.ts +77 -0
- package/src/screens/Verification/index.tsx +58 -0
- package/src/screens/Verification/styles.ts +74 -0
- package/src/screens/index.ts +23 -0
- package/src/types/index.ts +46 -0
- package/src-app/components/AmountBreakDownModal/index.tsx +86 -0
- package/src-app/components/AmountBreakDownModal/styles.ts +110 -0
- package/src-app/components/BottomNavIcons.tsx +125 -0
- package/src-app/components/Button.tsx +120 -0
- package/src-app/components/Card.tsx +47 -0
- package/src-app/components/ConfirmPopup/ConfirmPopup.constants.ts +25 -0
- package/src-app/components/ConfirmPopup/index.tsx +48 -0
- package/src-app/components/ConfirmPopup/styles.ts +167 -0
- package/src-app/components/CustomButton/index.tsx +67 -0
- package/src-app/components/CustomButton/styles.ts +44 -0
- package/src-app/components/CustomCard/index.tsx +221 -0
- package/src-app/components/CustomCard/styles.ts +184 -0
- package/src-app/components/CustomError/index.tsx +54 -0
- package/src-app/components/CustomError/styles.ts +41 -0
- package/src-app/components/CustomImage/index.tsx +37 -0
- package/src-app/components/CustomImage/styles.ts +5 -0
- package/src-app/components/CustomLoader/index.tsx +45 -0
- package/src-app/components/CustomLoader/styles.ts +35 -0
- package/src-app/components/CustomMessagePopUp/index.tsx +51 -0
- package/src-app/components/CustomMessagePopUp/styles.ts +74 -0
- package/src-app/components/CustomProductCard/index.tsx +13 -0
- package/src-app/components/CustomProductCard/styles.ts +5 -0
- package/src-app/components/FilterModal.tsx +372 -0
- package/src-app/components/Footer/index.tsx +23 -0
- package/src-app/components/Footer/styles.ts +37 -0
- package/src-app/components/Icon.tsx +80 -0
- package/src-app/components/Logout/index.tsx +82 -0
- package/src-app/components/Logout/styles.ts +116 -0
- package/src-app/components/MobileHeader.tsx +141 -0
- package/src-app/components/NoDataFound/index.tsx +18 -0
- package/src-app/components/NoDataFound/styles.ts +26 -0
- package/src-app/components/OTPModal.tsx +747 -0
- package/src-app/components/ProfileField.tsx +47 -0
- package/src-app/components/QuantityModal/index.tsx +113 -0
- package/src-app/components/QuantityModal/styles.ts +84 -0
- package/src-app/components/TabBarIcons.tsx +110 -0
- package/src-app/components/TextInput.tsx +79 -0
- package/src-app/components/ToastConfig.tsx +60 -0
- package/src-app/components/index.ts +18 -0
- package/src-app/config/env.ts +22 -0
- package/src-app/constants/Fonts.ts +12 -0
- package/src-app/constants/Formatter.ts +39 -0
- package/src-app/constants/HtmlSanitization.ts +46 -0
- package/src-app/constants/Images.ts +81 -0
- package/src-app/constants/Labels.ts +8 -0
- package/src-app/constants/Messages.ts +108 -0
- package/src-app/constants/Routes.ts +17 -0
- package/src-app/constants/Scaling.ts +5 -0
- package/src-app/constants/Text.ts +8 -0
- package/src-app/constants/offSets.ts +18 -0
- package/src-app/hooks/useAppDispatch.ts +4 -0
- package/src-app/hooks/useAppSelector.ts +4 -0
- package/src-app/hooks/useBackHandler.ts +47 -0
- package/src-app/hooks/useScreenBackHandler.ts +91 -0
- package/src-app/navigation/AppNavigator.tsx +34 -0
- package/src-app/navigation/MainTabNavigator.tsx +294 -0
- package/src-app/redux/authSaga.ts +605 -0
- package/src-app/redux/authSlice.ts +754 -0
- package/src-app/redux/rootSaga.ts +6 -0
- package/src-app/redux/store.ts +25 -0
- package/src-app/services/api.ts +14 -0
- package/src-app/services/endpoints.ts +33 -0
- package/src-app/services/index.ts +574 -0
- package/src-app/services/sdkCredentials.ts +44 -0
- package/src-app/styles/colors.ts +85 -0
- package/src-app/styles/shared.ts +112 -0
- package/src-app/types/authTypes.ts +155 -0
- package/src-app/types/navigation.ts +99 -0
- package/src-app/utils/Validation.ts +48 -0
- package/src-app/utils/filterPins.ts +29 -0
- package/src-app/utils/navigationUtils.ts +43 -0
- 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
|
+
|