@finspringinnovations/fdsdk 0.0.1 → 0.0.3

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 (53) hide show
  1. package/lib/components/CompanyHeader.js +40 -36
  2. package/lib/components/InterestRateCard.js +3 -2
  3. package/lib/components/PaymentDetailsCard.js +8 -7
  4. package/lib/components/PendingFDBottomSheet.js +2 -2
  5. package/lib/components/TextFieldWithLabel.js +1 -1
  6. package/lib/components/TrustBox.js +2 -2
  7. package/lib/config/appDataConfig.d.ts +53 -2
  8. package/lib/config/appDataConfig.js +39 -6
  9. package/lib/hooks/usePaymentSSE.d.ts +24 -0
  10. package/lib/hooks/usePaymentSSE.js +135 -0
  11. package/lib/hooks/usePaymentStatusTimer.d.ts +25 -0
  12. package/lib/hooks/usePaymentStatusTimer.js +122 -0
  13. package/lib/index.d.ts +2 -2
  14. package/lib/index.js +5 -2
  15. package/lib/navigation/RootNavigator.js +6 -1
  16. package/lib/navigation/index.d.ts +2 -0
  17. package/lib/navigation/index.js +13 -11
  18. package/lib/screens/FDCalculator.d.ts +1 -0
  19. package/lib/screens/FDCalculator.js +64 -48
  20. package/lib/screens/FDList.js +10 -12
  21. package/lib/screens/NomineeDetail.js +21 -15
  22. package/lib/screens/PayNow.js +6 -6
  23. package/lib/screens/Payment.d.ts +1 -0
  24. package/lib/screens/Payment.js +34 -13
  25. package/lib/screens/PaymentStatus.js +33 -42
  26. package/lib/theme/ThemeContext.d.ts +2 -0
  27. package/lib/theme/ThemeContext.js +4 -2
  28. package/lib/theme/index.d.ts +6 -1
  29. package/lib/theme/index.js +24 -8
  30. package/lib/utils/sseParser.d.ts +7 -0
  31. package/lib/utils/sseParser.js +27 -0
  32. package/package.json +2 -2
  33. package/src/components/CompanyHeader.tsx +50 -44
  34. package/src/components/InterestRateCard.tsx +3 -2
  35. package/src/components/PaymentDetailsCard.tsx +45 -40
  36. package/src/components/PendingFDBottomSheet.tsx +2 -2
  37. package/src/components/TextFieldWithLabel.tsx +1 -1
  38. package/src/components/TrustBox.tsx +2 -2
  39. package/src/config/appDataConfig.ts +70 -5
  40. package/src/hooks/usePaymentSSE.ts +155 -0
  41. package/src/hooks/usePaymentStatusTimer.ts +169 -0
  42. package/src/index.tsx +4 -1
  43. package/src/navigation/RootNavigator.tsx +7 -0
  44. package/src/navigation/index.tsx +16 -17
  45. package/src/screens/FDCalculator.tsx +64 -40
  46. package/src/screens/FDList.tsx +11 -11
  47. package/src/screens/NomineeDetail.tsx +20 -14
  48. package/src/screens/PayNow.tsx +7 -7
  49. package/src/screens/Payment.tsx +45 -14
  50. package/src/screens/PaymentStatus.tsx +44 -57
  51. package/src/theme/ThemeContext.tsx +6 -1
  52. package/src/theme/index.ts +30 -8
  53. package/src/utils/sseParser.ts +40 -0
@@ -9,12 +9,14 @@ import { ApiProvider } from '../providers/ApiProvider';
9
9
  import { MasterDataProvider } from '../providers/MasterDataProvider';
10
10
  import { navigationRef } from './helpers';
11
11
  import type { ThemeName, Theme } from '../theme';
12
+ import { getSDKColors, type CustomColors } from '../config/appDataConfig';
12
13
 
13
14
  interface SDKNavigationContainerProps {
14
15
  config?: SDKNavigationConfig;
15
16
  onExit?: (fdDetails?: any) => void;
16
17
  children?: React.ReactNode;
17
18
  theme?: ThemeName | Theme;
19
+ colors?: CustomColors; // Custom color overrides (prop-level, highest priority)
18
20
  useReactNavigation?: boolean; // Flag to use React Navigation vs Simple Navigator
19
21
  }
20
22
 
@@ -24,8 +26,15 @@ export const SDKNavigationContainer: React.FC<SDKNavigationContainerProps> = ({
24
26
  onExit,
25
27
  children,
26
28
  theme,
29
+ colors: propColors,
27
30
  useReactNavigation = true, // Default to React Navigation
28
31
  }) => {
32
+ // Merge color overrides: prop-level colors take priority over global SDK colors
33
+ const globalColors = getSDKColors();
34
+ const colorOverrides = (propColors || globalColors)
35
+ ? { ...(globalColors || {}), ...(propColors || {}) }
36
+ : null;
37
+
29
38
  // Choose navigator based on flag
30
39
  const navigator = useReactNavigation
31
40
  ? <RootNavigator config={config} onExit={onExit} />
@@ -41,27 +50,17 @@ export const SDKNavigationContainer: React.FC<SDKNavigationContainerProps> = ({
41
50
  )
42
51
  );
43
52
 
44
- if (theme) {
45
- const themeProps = typeof theme === 'string'
46
- ? { initialTheme: theme }
47
- : { theme };
48
-
49
- return (
50
- <ApiProvider>
51
- <MasterDataProvider>
52
- <ThemeProvider {...themeProps}>
53
- {content}
54
- </ThemeProvider>
55
- </MasterDataProvider>
56
- </ApiProvider>
57
- );
58
- }
53
+ // Build ThemeProvider props
54
+ const themeProps = typeof theme === 'string'
55
+ ? { initialTheme: theme, colorOverrides }
56
+ : theme
57
+ ? { theme, colorOverrides }
58
+ : { colorOverrides };
59
59
 
60
- // Default theme provider with API provider
61
60
  return (
62
61
  <ApiProvider>
63
62
  <MasterDataProvider>
64
- <ThemeProvider>
63
+ <ThemeProvider {...themeProps}>
65
64
  {content}
66
65
  </ThemeProvider>
67
66
  </MasterDataProvider>
@@ -11,6 +11,7 @@ import {
11
11
  Platform,
12
12
  BackHandler,
13
13
  Keyboard,
14
+ Alert,
14
15
  } from 'react-native';
15
16
  import Icon from 'react-native-vector-icons/Ionicons';
16
17
  import SafeAreaWrapper from '../components/SafeAreaWrapper';
@@ -40,6 +41,7 @@ import { base64Images } from '../constants/strings/base64Images';
40
41
 
41
42
  export interface FDCalculatorProps {
42
43
  onGoBack?: () => void;
44
+ onExitSDK?: () => void;
43
45
  onNavigateToReviewKYC?: () => void;
44
46
  fdData?: {
45
47
  id: string;
@@ -54,7 +56,7 @@ export interface FDCalculatorProps {
54
56
  };
55
57
  }
56
58
 
57
- const FDCalculator: React.FC<FDCalculatorProps> = ({ onGoBack, onNavigateToReviewKYC, fdData }) => {
59
+ const FDCalculator: React.FC<FDCalculatorProps> = ({ onGoBack, onExitSDK, onNavigateToReviewKYC, fdData }) => {
58
60
  const typography = useTypography();
59
61
  const colors = useColors();
60
62
  const { themeName } = useTheme();
@@ -200,16 +202,6 @@ const FDCalculator: React.FC<FDCalculatorProps> = ({ onGoBack, onNavigateToRevie
200
202
  setPayoutValue(defaultOption);
201
203
  }
202
204
  }, [payoutOptions]);
203
-
204
- React.useEffect(() => {
205
- if (amount && payoutValue) {
206
- // Only call if amount is valid
207
- const numericValue = parseInt(amount.replace(/,/g, ''), 10);
208
- if (numericValue >= 5000 && numericValue <= 50000000 && numericValue % 1000 === 0) {
209
- handleCalculateFD(amount, payoutValue);
210
- }
211
- }
212
- }, [payoutValue]);
213
205
  // Master data processing complete
214
206
 
215
207
  // FD Calculator API
@@ -232,6 +224,17 @@ const FDCalculator: React.FC<FDCalculatorProps> = ({ onGoBack, onNavigateToRevie
232
224
  }, [interestRates]);
233
225
 
234
226
  const [tenureValue, setTenureValue] = useState<string>('');
227
+
228
+ // Effect to recalculate when payout or tenure changes
229
+ React.useEffect(() => {
230
+ if (amount && payoutValue && tenureValue) {
231
+ // Only call if amount is valid
232
+ const numericValue = parseInt(amount.replace(/,/g, ''), 10);
233
+ if (numericValue >= 5000 && numericValue <= 50000000 && numericValue % 1000 === 0) {
234
+ handleCalculateFD(amount, payoutValue, tenureValue);
235
+ }
236
+ }
237
+ }, [payoutValue, tenureValue]);
235
238
  React.useEffect(() => {
236
239
  if (tenureOptions.length > 0) {
237
240
  // If effectiveFdData has tenure, use it; otherwise use first option
@@ -253,15 +256,16 @@ const FDCalculator: React.FC<FDCalculatorProps> = ({ onGoBack, onNavigateToRevie
253
256
  };
254
257
 
255
258
  // Function to call FD calculator API
256
- const handleCalculateFD = React.useCallback(async (currentAmount?: string, selectedPayout?: string) => {
259
+ const handleCalculateFD = React.useCallback(async (currentAmount?: string, selectedPayout?: string, selectedTenure?: string) => {
257
260
  const payoutToUse = selectedPayout || payoutValue;
258
261
  const amountToUse = currentAmount || amount; // ✅ use latest if provided
262
+ const tenureToUse = selectedTenure || tenureValue; // ✅ use latest if provided
259
263
 
260
- if (!amountToUse || !payoutToUse) return;
264
+ if (!amountToUse || !payoutToUse || !tenureToUse) return;
261
265
 
262
266
  try {
263
267
  const amountValue = parseFloat(amountToUse.replace(/,/g, ''));
264
- const tenureMonths = parseInt(tenureValue.replace(' Months', ''));
268
+ const tenureMonths = parseInt(tenureToUse.replace(' Months', ''));
265
269
 
266
270
  if (isNaN(amountValue) || isNaN(tenureMonths) || amountValue <= 0 || tenureMonths <= 0) return;
267
271
 
@@ -272,7 +276,7 @@ const FDCalculator: React.FC<FDCalculatorProps> = ({ onGoBack, onNavigateToRevie
272
276
 
273
277
  const appData = getAppData();
274
278
  const userDob = appData?.dob || '1990-01-01';
275
- const isWomenDepositor = appData?.gender === 'F';
279
+ const isWomenDepositor = appData?.gender === 'Female';
276
280
 
277
281
  const requestData = {
278
282
  principalAmount: amountValue,
@@ -286,6 +290,7 @@ const FDCalculator: React.FC<FDCalculatorProps> = ({ onGoBack, onNavigateToRevie
286
290
 
287
291
  const result = await calculateFD(requestData).unwrap();
288
292
  setCalculationResult(result);
293
+ console.log(';following is the calculation result', result, 'requestData', requestData, 'appData', appData)
289
294
  } catch (error) {
290
295
  // FD calculation failed
291
296
  } finally {
@@ -326,10 +331,10 @@ const FDCalculator: React.FC<FDCalculatorProps> = ({ onGoBack, onNavigateToRevie
326
331
 
327
332
  // Step 7: Debounced API call
328
333
  const isValidLocal = numericValue >= 5000 && numericValue <= 50000000 && numericValue % 1000 === 0;
329
- if (cleanedText && isValidLocal && payoutValue) {
334
+ if (cleanedText && isValidLocal && payoutValue && tenureValue) {
330
335
  const newTimer = setTimeout(() => {
331
- // Pass latest cleanedText directly to avoid stale state
332
- handleCalculateFD(cleanedText);
336
+ // Pass latest values directly to avoid stale state
337
+ handleCalculateFD(cleanedText, payoutValue, tenureValue);
333
338
  }, 1000);
334
339
  setDebounceTimer(newTimer);
335
340
  }
@@ -358,6 +363,30 @@ const FDCalculator: React.FC<FDCalculatorProps> = ({ onGoBack, onNavigateToRevie
358
363
  return;
359
364
  }
360
365
 
366
+ const appData = getAppData();
367
+
368
+ const hasEssentialData = appData?.panNumber?.trim();
369
+
370
+ if(!hasEssentialData) {
371
+ const alertMessage = appData?.startFDAlertMessage || 'Please complete your KYC details to start an FD, Update your profile in the app and try again.';
372
+ Alert.alert(
373
+ 'Action Required',
374
+ alertMessage,
375
+ [
376
+ {
377
+ text: 'OK',
378
+ onPress: () => {
379
+ if (onExitSDK) {
380
+ onExitSDK();
381
+ }
382
+ },
383
+ },
384
+ ],
385
+ { cancelable: false }
386
+ );
387
+ return;
388
+ }
389
+
361
390
  // Get providerId from interest rates data
362
391
  const sdr = interestRates?.data?.sdrScheme || [];
363
392
  const fdr = interestRates?.data?.fdrScheme || [];
@@ -365,7 +394,6 @@ const FDCalculator: React.FC<FDCalculatorProps> = ({ onGoBack, onNavigateToRevie
365
394
  const providerId = firstRate?.providerId;
366
395
 
367
396
  // Get user data for all required fields
368
- const appData = getAppData();
369
397
  const userDob = appData?.dob || '1990-01-01';
370
398
  const isWomenDepositor = appData?.gender === 'F';
371
399
 
@@ -591,19 +619,18 @@ const FDCalculator: React.FC<FDCalculatorProps> = ({ onGoBack, onNavigateToRevie
591
619
  <SafeAreaWrapper
592
620
  includeTop={false}
593
621
  bottomPadding={25}
594
- statusBarColor="#000000"
595
- statusBarStyle="light-content"
622
+ statusBarColor={colors.background}
623
+ statusBarStyle={themeName === 'dark' ? 'light-content' : 'dark-content'}
596
624
  >
597
625
  {/* Header */}
598
626
  <View style={styles.header}>
599
627
  <TouchableOpacity onPress={() => navigate('FDList')} style={styles.backButton}>
600
628
  <Image
601
629
  source={{ uri: base64Images.backArrow }}
602
- style={[styles.backIcon, { tintColor: themeName === 'dark' ? colors.headerText : undefined }]}
630
+ style={[styles.backIcon, { tintColor: colors.text }]}
603
631
  resizeMode="contain"
604
632
  width={24}
605
633
  height={24}
606
-
607
634
  />
608
635
  </TouchableOpacity>
609
636
  </View>
@@ -656,8 +683,8 @@ const FDCalculator: React.FC<FDCalculatorProps> = ({ onGoBack, onNavigateToRevie
656
683
  onPress={() => {
657
684
  setTenureValue(option);
658
685
  setShowTenureMenu(false);
659
- // Call API after dropdown value changes
660
- setTimeout(() => handleCalculateFD(), 100);
686
+ // Call API with the new tenure value directly to avoid closure issues
687
+ setTimeout(() => handleCalculateFD(amount, payoutValue, option), 100);
661
688
  }}
662
689
  >
663
690
  <Text style={styles.modalOptionText}>{option}</Text>
@@ -688,9 +715,8 @@ const FDCalculator: React.FC<FDCalculatorProps> = ({ onGoBack, onNavigateToRevie
688
715
  onPress={() => {
689
716
  setPayoutValue(option);
690
717
  setShowPayoutModal(false);
691
- handleCalculateFD(amount, option);
692
- // Pass the selected option directly to avoid stale state
693
- // setTimeout(() => handleCalculateFD(option), 100);
718
+ // Pass all values directly to avoid stale state
719
+ handleCalculateFD(amount, option, tenureValue);
694
720
  }}
695
721
  >
696
722
  <Text style={styles.modalOptionText}>{option}</Text>
@@ -893,34 +919,32 @@ const createStyles = (typography: any, colors: any, themeName: string) => StyleS
893
919
  position: 'relative',
894
920
  },
895
921
  startButton: {
896
- backgroundColor: themeName === 'dark' ? colors.buttonBackground : colors.headerBg,
897
- paddingVertical: themeName === 'dark' ? 1 : 20,
922
+ backgroundColor: colors.buttonBackground || colors.headerBg,
923
+ paddingVertical: 20,
898
924
  paddingHorizontal: 20,
899
- borderRadius: themeName === 'dark' ? 10 : 30,
925
+ borderRadius: 30,
900
926
  marginTop: 30,
901
927
  alignItems: 'center',
902
928
  justifyContent: 'center',
903
- height: themeName === 'dark' ? 50 : 56,
929
+ height: 56,
904
930
  width: '90%',
905
931
  alignSelf: 'center',
906
932
  },
907
933
  startButtonDisabled: {
908
- backgroundColor: '#909090',
909
- color: '#ffffff',
934
+ backgroundColor: colors.muted || '#909090',
910
935
  },
911
936
  startButtonText: {
912
937
  ...typography.styles.button,
913
- color: themeName === 'dark' ? colors.buttonTextColor : colors.background,
914
- lineHeight: themeName === 'dark' ? 24 : typography.styles.button.lineHeight,
938
+ color: colors.buttonTextColor || colors.background,
915
939
  fontSize: 16,
916
940
  },
917
941
  startButtonTextDisabled: {
918
- color: themeName === 'dark' ? "#ffffff" : colors.tabSelected,
942
+ color: colors.textSecondary,
919
943
  },
920
944
  inlineMenu: {
921
- backgroundColor: themeName === 'dark' ? colors.inputBackground : colors.background,
922
- borderWidth: themeName === 'dark' ? 1 : 0.5,
923
- borderColor: themeName === 'dark' ? '#ffffff' : 'rgba(0,0,0,0.2)',
945
+ backgroundColor: colors.inputBackground || colors.background,
946
+ borderWidth: 0.5,
947
+ borderColor: colors.inputBorder || colors.border,
924
948
  borderRadius: 8,
925
949
  marginTop: 6,
926
950
  paddingHorizontal: 12,
@@ -888,7 +888,7 @@ const FDList: React.FC<FDListProps> = ({
888
888
  <SafeAreaWrapper
889
889
  style={customStyles.container}
890
890
  includeTop={false}
891
- statusBarColor="#000000"
891
+ statusBarColor={colors.headerBg}
892
892
  statusBarStyle="light-content"
893
893
  >
894
894
  <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
@@ -903,7 +903,7 @@ const FDList: React.FC<FDListProps> = ({
903
903
  <SafeAreaWrapper
904
904
  style={customStyles.container}
905
905
  includeTop={false}
906
- statusBarColor="#0A1929"
906
+ statusBarColor={colors.headerBg}
907
907
  statusBarStyle="light-content"
908
908
  >
909
909
  {/* Custom FDList Header */}
@@ -923,12 +923,12 @@ const FDList: React.FC<FDListProps> = ({
923
923
  <View style={styles.headerContent}>
924
924
  <Text style={styles.headerTitle}>{FD_STRINGS.CORPORATE_FDS_TITLE}</Text>
925
925
  <View style={styles.poweredByContainer}>
926
- <Text style={styles.poweredByText}>Powered by</Text>
926
+ {/* <Text style={styles.poweredByText}>Powered by</Text>
927
927
  <Image
928
928
  source={{ uri: base64Images.simplfylogo }}
929
929
  style={styles.simplifyLogo}
930
930
  resizeMode="contain"
931
- />
931
+ /> */}
932
932
  </View>
933
933
  </View>
934
934
  </View>
@@ -1105,7 +1105,7 @@ const createStyles = (colors: ColorScheme, typography: any, spacing: any, themeN
1105
1105
  backgroundColor: colors.surface,
1106
1106
  },
1107
1107
  customHeader: {
1108
- backgroundColor: '#0A1929',
1108
+ backgroundColor: colors.headerBg,
1109
1109
  paddingTop: Platform.OS === 'ios' ? 50 : 20,
1110
1110
  paddingBottom: spacing.md,
1111
1111
  paddingHorizontal: spacing.lg,
@@ -1121,7 +1121,7 @@ const createStyles = (colors: ColorScheme, typography: any, spacing: any, themeN
1121
1121
  backIcon: {
1122
1122
  width: 24,
1123
1123
  height: 24,
1124
- tintColor: '#FFFFFF',
1124
+ tintColor: colors.headerText,
1125
1125
  },
1126
1126
  headerContent: {
1127
1127
  flex: 1,
@@ -1130,7 +1130,7 @@ const createStyles = (colors: ColorScheme, typography: any, spacing: any, themeN
1130
1130
  },
1131
1131
  headerTitle: {
1132
1132
  ...typography.styles.h3,
1133
- color: '#FFFFFF',
1133
+ color: colors.headerText,
1134
1134
  marginBottom: spacing.xs,
1135
1135
  },
1136
1136
  poweredByContainer: {
@@ -1139,7 +1139,7 @@ const createStyles = (colors: ColorScheme, typography: any, spacing: any, themeN
1139
1139
  },
1140
1140
  poweredByText: {
1141
1141
  ...typography.styles.text10Regular,
1142
- color: '#FFFFFF',
1142
+ color: colors.headerText,
1143
1143
  opacity: 0.7,
1144
1144
  marginRight: 0,
1145
1145
  },
@@ -1175,14 +1175,14 @@ const createStyles = (colors: ColorScheme, typography: any, spacing: any, themeN
1175
1175
  paddingVertical: spacing.md,
1176
1176
  marginHorizontal: 2,
1177
1177
  borderBottomWidth: 3,
1178
- borderBottomColor: 'white',
1178
+ borderBottomColor: colors.surface,
1179
1179
  borderBottomLeftRadius: 8,
1180
1180
  borderBottomRightRadius: 8,
1181
1181
  backgroundColor: 'transparent',
1182
1182
  },
1183
1183
  activeTab: {
1184
1184
  borderBottomColor: colors.tabSelected,
1185
- backgroundColor: themeName === 'dark' ? '#000000' : '#fff',
1185
+ backgroundColor: colors.background,
1186
1186
  borderBottomLeftRadius: 12,
1187
1187
  borderBottomRightRadius: 12,
1188
1188
  },
@@ -1276,7 +1276,7 @@ const createStyles = (colors: ColorScheme, typography: any, spacing: any, themeN
1276
1276
  },
1277
1277
  showAllButtonText: {
1278
1278
  ...typography.styles.text14Medium,
1279
- color: '#ffffff', // Use white color directly
1279
+ color: colors.headerText, // Use theme header text color
1280
1280
  textAlign: 'center',
1281
1281
  },
1282
1282
  });
@@ -140,7 +140,8 @@ const NomineeDetail: React.FC<NomineeDetailProps> = ({ onGoBack, onSave, initial
140
140
  const year = parseInt(dateParts[2], 10);
141
141
 
142
142
  if (day >= 1 && day <= 31 && month >= 1 && month <= 12 && year >= 1900 && year <= new Date().getFullYear()) {
143
- const parsedDate = new Date(year, month - 1, day);
143
+ // Use noon to avoid timezone issues
144
+ const parsedDate = new Date(year, month - 1, day, 12, 0, 0);
144
145
  if (!isNaN(parsedDate.getTime()) && parsedDate.getDate() === day && parsedDate.getMonth() === month - 1 && parsedDate.getFullYear() === year) {
145
146
  setSelectedDate(parsedDate);
146
147
  }
@@ -286,7 +287,8 @@ const NomineeDetail: React.FC<NomineeDetailProps> = ({ onGoBack, onSave, initial
286
287
 
287
288
  // Validate the parsed values
288
289
  if (day >= 1 && day <= 31 && month >= 1 && month <= 12 && year >= 1900 && year <= new Date().getFullYear()) {
289
- const parsedDate = new Date(year, month - 1, day);
290
+ // Use noon to avoid timezone issues
291
+ const parsedDate = new Date(year, month - 1, day, 12, 0, 0);
290
292
  if (!isNaN(parsedDate.getTime()) && parsedDate.getDate() === day && parsedDate.getMonth() === month - 1 && parsedDate.getFullYear() === year) {
291
293
  setSelectedDate(parsedDate);
292
294
  } else {
@@ -320,12 +322,14 @@ const NomineeDetail: React.FC<NomineeDetailProps> = ({ onGoBack, onSave, initial
320
322
  if (Platform.OS === 'android') {
321
323
  // Android fires with types: 'set' and 'dismissed'
322
324
  if (event?.type === 'set' && selectedDate) {
323
- setSelectedDate(selectedDate);
325
+ // Normalize to noon to avoid timezone issues
326
+ const normalizedDate = new Date(selectedDate.getFullYear(), selectedDate.getMonth(), selectedDate.getDate(), 12, 0, 0);
327
+ setSelectedDate(normalizedDate);
324
328
 
325
329
  const today = new Date();
326
- let age = today.getFullYear() - selectedDate.getFullYear();
327
- const m = today.getMonth() - selectedDate.getMonth();
328
- if (m < 0 || (m === 0 && today.getDate() < selectedDate.getDate())) {
330
+ let age = today.getFullYear() - normalizedDate.getFullYear();
331
+ const m = today.getMonth() - normalizedDate.getMonth();
332
+ if (m < 0 || (m === 0 && today.getDate() < normalizedDate.getDate())) {
329
333
  age--;
330
334
  }
331
335
 
@@ -336,9 +340,9 @@ const NomineeDetail: React.FC<NomineeDetailProps> = ({ onGoBack, onSave, initial
336
340
  return;
337
341
  }
338
342
 
339
- const day = selectedDate.getDate().toString().padStart(2, '0');
340
- const month = (selectedDate.getMonth() + 1).toString().padStart(2, '0');
341
- const year = selectedDate.getFullYear().toString();
343
+ const day = normalizedDate.getDate().toString().padStart(2, '0');
344
+ const month = (normalizedDate.getMonth() + 1).toString().padStart(2, '0');
345
+ const year = normalizedDate.getFullYear().toString();
342
346
  const formattedDate = `${day}/${month}/${year}`;
343
347
  updateField('dateOfBirth', formattedDate);
344
348
  closeDatePicker();
@@ -353,10 +357,12 @@ const NomineeDetail: React.FC<NomineeDetailProps> = ({ onGoBack, onSave, initial
353
357
 
354
358
  // iOS inline/spinner handling
355
359
  if (selectedDate) {
356
- setSelectedDate(selectedDate);
357
- const day = selectedDate.getDate().toString().padStart(2, '0');
358
- const month = (selectedDate.getMonth() + 1).toString().padStart(2, '0');
359
- const year = selectedDate.getFullYear().toString();
360
+ // Normalize to noon to avoid timezone issues
361
+ const normalizedDate = new Date(selectedDate.getFullYear(), selectedDate.getMonth(), selectedDate.getDate(), 12, 0, 0);
362
+ setSelectedDate(normalizedDate);
363
+ const day = normalizedDate.getDate().toString().padStart(2, '0');
364
+ const month = (normalizedDate.getMonth() + 1).toString().padStart(2, '0');
365
+ const year = normalizedDate.getFullYear().toString();
360
366
  const formattedDate = `${day}/${month}/${year}`;
361
367
  updateField('dateOfBirth', formattedDate);
362
368
  }
@@ -499,7 +505,6 @@ const NomineeDetail: React.FC<NomineeDetailProps> = ({ onGoBack, onSave, initial
499
505
  }}
500
506
  editable={false}
501
507
  keepWhiteBackground={true}
502
- inputStyle={{ color: 'white' }}
503
508
  />
504
509
  );
505
510
 
@@ -679,6 +684,7 @@ const NomineeDetail: React.FC<NomineeDetailProps> = ({ onGoBack, onSave, initial
679
684
  mode="date"
680
685
  display={Platform.OS === 'ios' ? 'spinner' : 'default'}
681
686
  onChange={onDateChange}
687
+ minimumDate={new Date(1900, 0, 1)} // January 1, 1900
682
688
  maximumDate={new Date(new Date().getFullYear() - 18, new Date().getMonth(), new Date().getDate())}
683
689
  themeVariant="light"
684
690
  />
@@ -166,8 +166,8 @@ const PayNow: React.FC<PayNowProps> = ({ onGoBack, onConfirm, fdData }) => {
166
166
  <SafeAreaWrapper
167
167
  includeTop={false}
168
168
  bottomPadding={25}
169
- statusBarColor="#000000"
170
- statusBarStyle="light-content"
169
+ statusBarColor={colors.background}
170
+ statusBarStyle={themeName === 'dark' ? 'light-content' : 'dark-content'}
171
171
  >
172
172
  <View style={styles.container}>
173
173
  <ScrollView showsVerticalScrollIndicator={false} contentContainerStyle={styles.scrollContent}>
@@ -243,13 +243,13 @@ const createStyles = (colors: any, typography: any, themeName: string) => StyleS
243
243
  },
244
244
  backButton: {
245
245
  flex: 1,
246
- backgroundColor: themeName === 'dark' ? colors.cancelButtonBg : 'transparent',
247
- borderWidth: themeName === 'dark' ? 1 : 0,
248
- borderColor: themeName === 'dark' ? '#ffffff' : 'transparent',
249
- borderRadius: themeName === 'dark' ? 10 : 0,
246
+ backgroundColor: colors.cancelButtonBg || 'transparent',
247
+ borderWidth: 0,
248
+ borderColor: 'transparent',
249
+ borderRadius: 10,
250
250
  },
251
251
  backButtonText: {
252
- color: themeName === 'dark' ? '#ffffff' : colors.primary,
252
+ color: colors.primary,
253
253
  },
254
254
  confirmButton: {
255
255
  flex: 1,
@@ -5,11 +5,15 @@ import SafeAreaWrapper from '../components/SafeAreaWrapper';
5
5
  import { useColors } from '../theme/ThemeContext';
6
6
  import { decryptResponse } from '../utils/encryption';
7
7
  import { getEncryptionConfig } from '../config/encryptionConfig';
8
+ import { useFocusEffect } from '@react-navigation/native';
9
+ import { getPaymentSession } from '../state/paymentSession';
10
+ import { usePaymentStatusTimer, PaymentStatus } from '../hooks/usePaymentStatusTimer';
8
11
 
9
12
  export interface PaymentProps {
10
13
  onGoBack?: () => void;
11
14
  onPaymentSuccess?: (data?: any) => void;
12
15
  onPaymentFailure?: (error?: any) => void;
16
+ onPaymentPending?: (data?: any) => void;
13
17
  paymentUrl: string;
14
18
  successUrl?: string;
15
19
  failureUrl?: string;
@@ -19,6 +23,7 @@ const Payment: React.FC<PaymentProps> = ({
19
23
  onGoBack,
20
24
  onPaymentSuccess,
21
25
  onPaymentFailure,
26
+ onPaymentPending,
22
27
  paymentUrl,
23
28
  successUrl = 'payment/success',
24
29
  failureUrl = 'payment/failure',
@@ -27,6 +32,36 @@ const Payment: React.FC<PaymentProps> = ({
27
32
  const styles = createStyles(colors);
28
33
  const webViewRef = useRef<WebView>(null);
29
34
  const [loading, setLoading] = useState(true);
35
+ const currentStatusRef = useRef<PaymentStatus>('pending');
36
+ const { transactionId } = getPaymentSession();
37
+
38
+ const { startTimer, stopTimer } = usePaymentStatusTimer({
39
+ transactionId,
40
+ onStatusUpdate: (status, response) => {
41
+ currentStatusRef.current = status;
42
+
43
+ if (status === 'success') {
44
+ onPaymentSuccess?.(response);
45
+ }
46
+ else if (status === 'failed') {
47
+ onPaymentFailure?.(response);
48
+ }
49
+ else if (status === 'pending') {
50
+ onPaymentPending?.(response);
51
+ }
52
+ }
53
+
54
+ });
55
+
56
+ useFocusEffect(
57
+ React.useCallback(() => {
58
+ startTimer();
59
+
60
+ }, [startTimer, stopTimer])
61
+ );
62
+
63
+
64
+
30
65
 
31
66
  const handleNavigationStateChange = async (navState: any) => {
32
67
  const { url } = navState;
@@ -35,18 +70,9 @@ const Payment: React.FC<PaymentProps> = ({
35
70
  await handlePaymentStatusPage();
36
71
  return;
37
72
  }
38
-
39
- if (url.includes(successUrl)) {
40
- setLoading(false);
41
- onPaymentSuccess?.(navState);
42
- return;
43
- }
44
-
45
- if (url.includes(failureUrl)) {
46
- setLoading(false);
47
- onPaymentFailure?.(navState);
48
- return;
49
- }
73
+ // else{
74
+ // onPaymentPending?.(url);
75
+ // }
50
76
  };
51
77
 
52
78
  const handleWebViewError = (syntheticEvent: any) => {
@@ -107,14 +133,19 @@ const Payment: React.FC<PaymentProps> = ({
107
133
  const paymentStatus = response.data?.paymentStatus?.toLowerCase();
108
134
 
109
135
  if (paymentStatus === 'success') {
136
+ currentStatusRef.current = "success";
137
+ stopTimer();
110
138
  onPaymentSuccess?.(response);
111
139
  } else if (paymentStatus === 'failed') {
140
+ currentStatusRef.current = "failed";
141
+ stopTimer();
112
142
  onPaymentFailure?.(response);
113
143
  } else {
114
- onPaymentFailure?.(response);
144
+ onPaymentPending?.(response);
115
145
  }
116
146
  } else {
117
- onPaymentFailure?.(response);
147
+ currentStatusRef.current = "pending";
148
+ onPaymentPending?.(response);
118
149
  }
119
150
 
120
151
  setLoading(false);