@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
@@ -140,7 +140,8 @@ const NomineeDetail = ({ onGoBack, onSave, initialData }) => {
140
140
  const month = parseInt(dateParts[1], 10);
141
141
  const year = parseInt(dateParts[2], 10);
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
  }
@@ -262,7 +263,8 @@ const NomineeDetail = ({ onGoBack, onSave, initialData }) => {
262
263
  const year = parseInt(dateParts[2], 10);
263
264
  // Validate the parsed values
264
265
  if (day >= 1 && day <= 31 && month >= 1 && month <= 12 && year >= 1900 && year <= new Date().getFullYear()) {
265
- const parsedDate = new Date(year, month - 1, day);
266
+ // Use noon to avoid timezone issues
267
+ const parsedDate = new Date(year, month - 1, day, 12, 0, 0);
266
268
  if (!isNaN(parsedDate.getTime()) && parsedDate.getDate() === day && parsedDate.getMonth() === month - 1 && parsedDate.getFullYear() === year) {
267
269
  setSelectedDate(parsedDate);
268
270
  }
@@ -297,11 +299,13 @@ const NomineeDetail = ({ onGoBack, onSave, initialData }) => {
297
299
  if (react_native_1.Platform.OS === 'android') {
298
300
  // Android fires with types: 'set' and 'dismissed'
299
301
  if ((event === null || event === void 0 ? void 0 : event.type) === 'set' && selectedDate) {
300
- setSelectedDate(selectedDate);
302
+ // Normalize to noon to avoid timezone issues
303
+ const normalizedDate = new Date(selectedDate.getFullYear(), selectedDate.getMonth(), selectedDate.getDate(), 12, 0, 0);
304
+ setSelectedDate(normalizedDate);
301
305
  const today = new Date();
302
- let age = today.getFullYear() - selectedDate.getFullYear();
303
- const m = today.getMonth() - selectedDate.getMonth();
304
- if (m < 0 || (m === 0 && today.getDate() < selectedDate.getDate())) {
306
+ let age = today.getFullYear() - normalizedDate.getFullYear();
307
+ const m = today.getMonth() - normalizedDate.getMonth();
308
+ if (m < 0 || (m === 0 && today.getDate() < normalizedDate.getDate())) {
305
309
  age--;
306
310
  }
307
311
  if (age < 18) {
@@ -310,9 +314,9 @@ const NomineeDetail = ({ onGoBack, onSave, initialData }) => {
310
314
  closeDatePicker();
311
315
  return;
312
316
  }
313
- const day = selectedDate.getDate().toString().padStart(2, '0');
314
- const month = (selectedDate.getMonth() + 1).toString().padStart(2, '0');
315
- const year = selectedDate.getFullYear().toString();
317
+ const day = normalizedDate.getDate().toString().padStart(2, '0');
318
+ const month = (normalizedDate.getMonth() + 1).toString().padStart(2, '0');
319
+ const year = normalizedDate.getFullYear().toString();
316
320
  const formattedDate = `${day}/${month}/${year}`;
317
321
  updateField('dateOfBirth', formattedDate);
318
322
  closeDatePicker();
@@ -326,10 +330,12 @@ const NomineeDetail = ({ onGoBack, onSave, initialData }) => {
326
330
  }
327
331
  // iOS inline/spinner handling
328
332
  if (selectedDate) {
329
- setSelectedDate(selectedDate);
330
- const day = selectedDate.getDate().toString().padStart(2, '0');
331
- const month = (selectedDate.getMonth() + 1).toString().padStart(2, '0');
332
- const year = selectedDate.getFullYear().toString();
333
+ // Normalize to noon to avoid timezone issues
334
+ const normalizedDate = new Date(selectedDate.getFullYear(), selectedDate.getMonth(), selectedDate.getDate(), 12, 0, 0);
335
+ setSelectedDate(normalizedDate);
336
+ const day = normalizedDate.getDate().toString().padStart(2, '0');
337
+ const month = (normalizedDate.getMonth() + 1).toString().padStart(2, '0');
338
+ const year = normalizedDate.getFullYear().toString();
333
339
  const formattedDate = `${day}/${month}/${year}`;
334
340
  updateField('dateOfBirth', formattedDate);
335
341
  }
@@ -392,7 +398,7 @@ const NomineeDetail = ({ onGoBack, onSave, initialData }) => {
392
398
  return;
393
399
  closeAllMenus();
394
400
  openDatePicker();
395
- }, editable: false, keepWhiteBackground: true, inputStyle: { color: 'white' } }));
401
+ }, editable: false, keepWhiteBackground: true }));
396
402
  // Validation function to check if all required fields are filled (pure function - no setState)
397
403
  const validateForm = () => {
398
404
  // If makeNomination is "No", no other fields are required
@@ -523,7 +529,7 @@ const NomineeDetail = ({ onGoBack, onSave, initialData }) => {
523
529
  renderDropdown(nominee_1.NOMINEE_STRINGS.RELATIONSHIP_LABEL, form.relationship, 'relationship', nomineeRelationOptions, 'relationship'),
524
530
  renderDateField(nominee_1.NOMINEE_STRINGS.DATE_OF_BIRTH_LABEL, form.dateOfBirth, 'dateOfBirth'),
525
531
  showDatePicker && (react_1.default.createElement(react_native_1.View, { style: styles.datePickerContainer },
526
- react_1.default.createElement(datetimepicker_1.default, { value: selectedDate, mode: "date", display: react_native_1.Platform.OS === 'ios' ? 'spinner' : 'default', onChange: onDateChange, maximumDate: new Date(new Date().getFullYear() - 18, new Date().getMonth(), new Date().getDate()), themeVariant: "light" }))),
532
+ react_1.default.createElement(datetimepicker_1.default, { value: selectedDate, mode: "date", display: react_native_1.Platform.OS === 'ios' ? 'spinner' : 'default', onChange: onDateChange, minimumDate: new Date(1900, 0, 1), maximumDate: new Date(new Date().getFullYear() - 18, new Date().getMonth(), new Date().getDate()), themeVariant: "light" }))),
527
533
  react_1.default.createElement(react_native_1.View, { style: styles.checkboxContainer },
528
534
  react_1.default.createElement(react_native_1.TouchableOpacity, { style: styles.checkbox, onPress: () => updateField('printNameOnCertificate', !form.printNameOnCertificate) },
529
535
  react_1.default.createElement(react_native_1.View, { style: [styles.checkboxBox, form.printNameOnCertificate && styles.checkboxChecked] }, form.printNameOnCertificate ? (react_1.default.createElement(react_native_1.Image, { source: { uri: ((0, ThemeContext_1.useTheme)().themeName === 'dark') ? base64Images_1.base64Images.checkBoxDark : base64Images_1.base64Images.filledCheckBox }, style: { width: 22, height: 22 }, resizeMode: "contain" })) : (((0, ThemeContext_1.useTheme)().themeName === 'dark') ? (react_1.default.createElement(react_native_1.Image, { source: { uri: base64Images_1.base64Images.unCheckBoxDark }, style: { width: 22, height: 22 }, resizeMode: "contain" })) : null))),
@@ -159,7 +159,7 @@ const PayNow = ({ onGoBack, onConfirm, fdData }) => {
159
159
  const backHandler = react_native_1.BackHandler.addEventListener('hardwareBackPress', onHardwareBackPress);
160
160
  return () => backHandler.remove();
161
161
  }, [defaultProviderId, workflowInstanceId, applicationId, entityId]);
162
- return (react_1.default.createElement(SafeAreaWrapper_1.default, { includeTop: false, bottomPadding: 25, statusBarColor: "#000000", statusBarStyle: "light-content" },
162
+ return (react_1.default.createElement(SafeAreaWrapper_1.default, { includeTop: false, bottomPadding: 25, statusBarColor: colors.background, statusBarStyle: themeName === 'dark' ? 'light-content' : 'dark-content' },
163
163
  react_1.default.createElement(react_native_1.View, { style: styles.container },
164
164
  react_1.default.createElement(react_native_1.ScrollView, { showsVerticalScrollIndicator: false, contentContainerStyle: styles.scrollContent },
165
165
  react_1.default.createElement(react_native_1.Text, { style: styles.title }, bank_1.BANK_STRINGS.PAYMENT_DETAILS_TITLE),
@@ -196,13 +196,13 @@ const createStyles = (colors, typography, themeName) => react_native_1.StyleShee
196
196
  },
197
197
  backButton: {
198
198
  flex: 1,
199
- backgroundColor: themeName === 'dark' ? colors.cancelButtonBg : 'transparent',
200
- borderWidth: themeName === 'dark' ? 1 : 0,
201
- borderColor: themeName === 'dark' ? '#ffffff' : 'transparent',
202
- borderRadius: themeName === 'dark' ? 10 : 0,
199
+ backgroundColor: colors.cancelButtonBg || 'transparent',
200
+ borderWidth: 0,
201
+ borderColor: 'transparent',
202
+ borderRadius: 10,
203
203
  },
204
204
  backButtonText: {
205
- color: themeName === 'dark' ? '#ffffff' : colors.primary,
205
+ color: colors.primary,
206
206
  },
207
207
  confirmButton: {
208
208
  flex: 1,
@@ -3,6 +3,7 @@ export interface PaymentProps {
3
3
  onGoBack?: () => void;
4
4
  onPaymentSuccess?: (data?: any) => void;
5
5
  onPaymentFailure?: (error?: any) => void;
6
+ onPaymentPending?: (data?: any) => void;
6
7
  paymentUrl: string;
7
8
  successUrl?: string;
8
9
  failureUrl?: string;
@@ -43,27 +43,43 @@ const SafeAreaWrapper_1 = __importDefault(require("../components/SafeAreaWrapper
43
43
  const ThemeContext_1 = require("../theme/ThemeContext");
44
44
  const encryption_1 = require("../utils/encryption");
45
45
  const encryptionConfig_1 = require("../config/encryptionConfig");
46
- const Payment = ({ onGoBack, onPaymentSuccess, onPaymentFailure, paymentUrl, successUrl = 'payment/success', failureUrl = 'payment/failure', }) => {
46
+ const native_1 = require("@react-navigation/native");
47
+ const paymentSession_1 = require("../state/paymentSession");
48
+ const usePaymentStatusTimer_1 = require("../hooks/usePaymentStatusTimer");
49
+ const Payment = ({ onGoBack, onPaymentSuccess, onPaymentFailure, onPaymentPending, paymentUrl, successUrl = 'payment/success', failureUrl = 'payment/failure', }) => {
47
50
  const colors = (0, ThemeContext_1.useColors)();
48
51
  const styles = createStyles(colors);
49
52
  const webViewRef = (0, react_1.useRef)(null);
50
53
  const [loading, setLoading] = (0, react_1.useState)(true);
54
+ const currentStatusRef = (0, react_1.useRef)('pending');
55
+ const { transactionId } = (0, paymentSession_1.getPaymentSession)();
56
+ const { startTimer, stopTimer } = (0, usePaymentStatusTimer_1.usePaymentStatusTimer)({
57
+ transactionId,
58
+ onStatusUpdate: (status, response) => {
59
+ currentStatusRef.current = status;
60
+ if (status === 'success') {
61
+ onPaymentSuccess === null || onPaymentSuccess === void 0 ? void 0 : onPaymentSuccess(response);
62
+ }
63
+ else if (status === 'failed') {
64
+ onPaymentFailure === null || onPaymentFailure === void 0 ? void 0 : onPaymentFailure(response);
65
+ }
66
+ else if (status === 'pending') {
67
+ onPaymentPending === null || onPaymentPending === void 0 ? void 0 : onPaymentPending(response);
68
+ }
69
+ }
70
+ });
71
+ (0, native_1.useFocusEffect)(react_1.default.useCallback(() => {
72
+ startTimer();
73
+ }, [startTimer, stopTimer]));
51
74
  const handleNavigationStateChange = async (navState) => {
52
75
  const { url } = navState;
53
76
  if (url.includes('payment/status')) {
54
77
  await handlePaymentStatusPage();
55
78
  return;
56
79
  }
57
- if (url.includes(successUrl)) {
58
- setLoading(false);
59
- onPaymentSuccess === null || onPaymentSuccess === void 0 ? void 0 : onPaymentSuccess(navState);
60
- return;
61
- }
62
- if (url.includes(failureUrl)) {
63
- setLoading(false);
64
- onPaymentFailure === null || onPaymentFailure === void 0 ? void 0 : onPaymentFailure(navState);
65
- return;
66
- }
80
+ // else{
81
+ // onPaymentPending?.(url);
82
+ // }
67
83
  };
68
84
  const handleWebViewError = (syntheticEvent) => {
69
85
  const { nativeEvent } = syntheticEvent;
@@ -114,17 +130,22 @@ const Payment = ({ onGoBack, onPaymentSuccess, onPaymentFailure, paymentUrl, suc
114
130
  // Check payment status in data
115
131
  const paymentStatus = (_c = (_b = response.data) === null || _b === void 0 ? void 0 : _b.paymentStatus) === null || _c === void 0 ? void 0 : _c.toLowerCase();
116
132
  if (paymentStatus === 'success') {
133
+ currentStatusRef.current = "success";
134
+ stopTimer();
117
135
  onPaymentSuccess === null || onPaymentSuccess === void 0 ? void 0 : onPaymentSuccess(response);
118
136
  }
119
137
  else if (paymentStatus === 'failed') {
138
+ currentStatusRef.current = "failed";
139
+ stopTimer();
120
140
  onPaymentFailure === null || onPaymentFailure === void 0 ? void 0 : onPaymentFailure(response);
121
141
  }
122
142
  else {
123
- onPaymentFailure === null || onPaymentFailure === void 0 ? void 0 : onPaymentFailure(response);
143
+ onPaymentPending === null || onPaymentPending === void 0 ? void 0 : onPaymentPending(response);
124
144
  }
125
145
  }
126
146
  else {
127
- onPaymentFailure === null || onPaymentFailure === void 0 ? void 0 : onPaymentFailure(response);
147
+ currentStatusRef.current = "pending";
148
+ onPaymentPending === null || onPaymentPending === void 0 ? void 0 : onPaymentPending(response);
128
149
  }
129
150
  setLoading(false);
130
151
  }
@@ -54,6 +54,7 @@ const native_1 = require("@react-navigation/native");
54
54
  const bank_1 = require("../constants/strings/bank");
55
55
  const common_1 = require("../constants/strings/common");
56
56
  const apiConfig_1 = require("../config/apiConfig");
57
+ const usePaymentStatusTimer_1 = require("../hooks/usePaymentStatusTimer");
57
58
  const PaymentStatus = ({ onRetry, onContinue, status, transactionId, fdData }) => {
58
59
  var _a;
59
60
  const colors = (0, ThemeContext_1.useColors)();
@@ -61,7 +62,7 @@ const PaymentStatus = ({ onRetry, onContinue, status, transactionId, fdData }) =
61
62
  const { themeName } = (0, ThemeContext_1.useTheme)();
62
63
  // Local state to track current status (can be updated from API responses)
63
64
  const [currentStatus, setCurrentStatus] = (0, react_1.useState)(status);
64
- const styles = createStyles(colors, typography, currentStatus);
65
+ const styles = createStyles(colors, typography, currentStatus, themeName);
65
66
  const route = (0, native_1.useRoute)();
66
67
  // Get transaction ID from navigation props (route params)
67
68
  const navigationTransactionId = (_a = route.params) === null || _a === void 0 ? void 0 : _a.transactionId;
@@ -74,8 +75,18 @@ const PaymentStatus = ({ onRetry, onContinue, status, transactionId, fdData }) =
74
75
  const applicationId = (0, store_1.useAppSelector)((state) => { var _a; return (_a = state === null || state === void 0 ? void 0 : state.onboarding) === null || _a === void 0 ? void 0 : _a.applicationId; });
75
76
  const entityId = (0, store_1.useAppSelector)((state) => { var _a; return (_a = state === null || state === void 0 ? void 0 : state.onboarding) === null || _a === void 0 ? void 0 : _a.entityid; });
76
77
  const providerId = (0, store_1.useAppSelector)((state) => { var _a; return (_a = state === null || state === void 0 ? void 0 : state.onboarding) === null || _a === void 0 ? void 0 : _a.providerId; });
77
- // Payment Reverse Feed API
78
- const [paymentReverseFeed, { data: paymentReverseFeedResponse, error: paymentReverseFeedError, isLoading: isLoadingPaymentReverseFeed, }] = (0, fdApi_1.usePaymentReverseFeedMutation)();
78
+ const { startTimer, stopTimer, triggerStatusCheck, isCheckingStatus, } = (0, usePaymentStatusTimer_1.usePaymentStatusTimer)({
79
+ transactionId: finalTransactionId,
80
+ overrides: {
81
+ providerId,
82
+ workflowInstanceId,
83
+ applicationid: applicationId,
84
+ entityid: entityId,
85
+ },
86
+ onStatusUpdate: (nextStatus) => {
87
+ setCurrentStatus(nextStatus);
88
+ },
89
+ });
79
90
  // Payment Retry API
80
91
  const [paymentRetry, { data: paymentRetryResponse, error: paymentRetryError, isLoading: isLoadingPaymentRetry, }] = (0, fdApi_1.usePaymentRetryMutation)();
81
92
  // Get Customer Applications API
@@ -99,37 +110,19 @@ const PaymentStatus = ({ onRetry, onContinue, status, transactionId, fdData }) =
99
110
  const formatAmount = (amount) => {
100
111
  return amount.toLocaleString('en-IN');
101
112
  };
102
- // Handle payment reverse feed API call for pending status
103
- const handlePaymentReverseFeed = async () => {
104
- var _a, _b, _c, _d;
105
- try {
106
- // Get user info from app data
107
- const userInfo = (0, appDataConfig_1.getUserInfoForAPI)();
108
- // Prepare the API request
109
- const paymentReverseFeedRequest = {
110
- // Headers
111
- providerId: providerId,
112
- workflowInstanceId: workflowInstanceId,
113
- userreferenceid: userInfo.id,
114
- applicationid: applicationId,
115
- entityid: entityId,
116
- // Body
117
- transactionId: finalTransactionId,
118
- };
119
- const response = await paymentReverseFeed(paymentReverseFeedRequest).unwrap();
120
- // Handle the response based on payment status
121
- if (((_b = (_a = response === null || response === void 0 ? void 0 : response.data) === null || _a === void 0 ? void 0 : _a.paymentStatus) === null || _b === void 0 ? void 0 : _b.toLowerCase()) === 'success') {
122
- setCurrentStatus('success');
123
- }
124
- else if (((_d = (_c = response === null || response === void 0 ? void 0 : response.data) === null || _c === void 0 ? void 0 : _c.paymentStatus) === null || _d === void 0 ? void 0 : _d.toLowerCase()) === 'failed') {
125
- setCurrentStatus('failed');
126
- }
113
+ (0, react_1.useEffect)(() => {
114
+ if (currentStatus === 'pending') {
115
+ startTimer();
127
116
  }
128
- catch (error) {
129
- // Alert.alert(COMMON_STRINGS.ERROR, BANK_STRINGS.PAYMENT_GATEWAY_ERROR, [
130
- // { text: COMMON_STRINGS.OK }
131
- // ]);
117
+ else {
118
+ stopTimer();
132
119
  }
120
+ return () => {
121
+ stopTimer();
122
+ };
123
+ }, [currentStatus, startTimer, stopTimer]);
124
+ const handlePaymentReverseFeed = async () => {
125
+ await triggerStatusCheck();
133
126
  };
134
127
  // Handle payment retry when status is failed
135
128
  const handlePaymentRetry = async () => {
@@ -278,8 +271,8 @@ const PaymentStatus = ({ onRetry, onContinue, status, transactionId, fdData }) =
278
271
  const backHandler = react_native_1.BackHandler.addEventListener('hardwareBackPress', onBackPress);
279
272
  return () => backHandler.remove();
280
273
  }, []);
281
- return (react_1.default.createElement(SafeAreaWrapper_1.default, { includeTop: true, bottomPadding: 0, statusBarColor: "#000000", statusBarStyle: "light-content" },
282
- react_native_1.Platform.OS === 'ios' && react_1.default.createElement(react_native_1.StatusBar, { barStyle: "light-content" }),
274
+ return (react_1.default.createElement(SafeAreaWrapper_1.default, { includeTop: true, bottomPadding: 0, statusBarColor: colors.background, statusBarStyle: themeName === 'dark' ? 'light-content' : 'dark-content' },
275
+ react_native_1.Platform.OS === 'ios' && react_1.default.createElement(react_native_1.StatusBar, { barStyle: themeName === 'dark' ? 'light-content' : 'dark-content' }),
283
276
  react_1.default.createElement(react_native_1.View, { style: styles.container },
284
277
  react_1.default.createElement(react_native_1.ScrollView, { showsVerticalScrollIndicator: false, contentContainerStyle: styles.scrollContent },
285
278
  react_1.default.createElement(react_native_1.View, { style: styles.iconContainer },
@@ -301,12 +294,10 @@ const PaymentStatus = ({ onRetry, onContinue, status, transactionId, fdData }) =
301
294
  react_1.default.createElement(react_native_1.Text, { style: styles.disclaimerText }, bank_1.BANK_STRINGS.PAYMENT_SUCCESS_DISCLAIMER)))),
302
295
  react_1.default.createElement(react_native_1.View, { style: styles.footer },
303
296
  currentStatus === 'success' && (react_1.default.createElement(ActionButton_1.default, { title: isLoadingCustomerApplications ? common_1.COMMON_STRINGS.LOADING : common_1.COMMON_STRINGS.EXIT, onPress: handleExit, disabled: isLoadingCustomerApplications })),
304
- currentStatus === 'failed' && (react_1.default.createElement(ActionButton_1.default, { title: isLoadingPaymentRetry ? common_1.COMMON_STRINGS.RETRYING : bank_1.BANK_STRINGS.RETRY_PAYMENT_BUTTON, onPress: handlePaymentRetry, disabled: isLoadingPaymentRetry })),
305
- currentStatus === 'pending' && (react_1.default.createElement(ActionButton_1.default, { title: isLoadingPaymentReverseFeed ? common_1.COMMON_STRINGS.CHECKING : bank_1.BANK_STRINGS.REFRESH_STATUS_BUTTON, onPress: handlePaymentReverseFeed, disabled: isLoadingPaymentReverseFeed }))))));
297
+ currentStatus === 'failed' && (react_1.default.createElement(ActionButton_1.default, { title: isLoadingPaymentRetry ? common_1.COMMON_STRINGS.RETRYING : bank_1.BANK_STRINGS.RETRY_PAYMENT_BUTTON, onPress: handlePaymentRetry, disabled: isLoadingPaymentRetry }))))));
306
298
  };
307
- const createStyles = (colors, typography, status) => {
308
- const statusColor = status === 'success' ? '#4CAF50' : status === 'failed' ? '#F44336' : '#FF9800';
309
- const cardBgColor = status === 'success' ? '#E8F5F0' : status === 'failed' ? '#FFF3F3' : '#FFF8E1';
299
+ const createStyles = (colors, typography, status, themeName) => {
300
+ const isDark = themeName === 'dark';
310
301
  return react_native_1.StyleSheet.create({
311
302
  container: {
312
303
  flex: 1,
@@ -342,7 +333,7 @@ const createStyles = (colors, typography, status) => {
342
333
  width: 60,
343
334
  height: 60,
344
335
  borderRadius: 30,
345
- backgroundColor: status === 'success' ? '#ffffff' : status === 'failed' ? 'rgba(244, 67, 54, 0.0)' : 'rgba(255, 152, 0, 0.2)',
336
+ backgroundColor: status === 'success' ? (isDark ? colors.surface : '#ffffff') : status === 'failed' ? 'rgba(244, 67, 54, 0.0)' : 'rgba(255, 152, 0, 0.2)',
346
337
  alignItems: 'center',
347
338
  justifyContent: 'center',
348
339
  },
@@ -359,7 +350,7 @@ const createStyles = (colors, typography, status) => {
359
350
  width: 56,
360
351
  height: 56,
361
352
  borderRadius: 28,
362
- backgroundColor: '#5F9E8F',
353
+ backgroundColor: colors.primary,
363
354
  alignItems: 'center',
364
355
  justifyContent: 'center',
365
356
  borderWidth: 4,
@@ -368,7 +359,7 @@ const createStyles = (colors, typography, status) => {
368
359
  avatarText: {
369
360
  fontSize: 24,
370
361
  fontWeight: '600',
371
- color: 'white',
362
+ color: colors.headerText,
372
363
  },
373
364
  footer: {
374
365
  position: 'absolute',
@@ -1,5 +1,6 @@
1
1
  import React, { ReactNode } from 'react';
2
2
  import { type Theme, type ThemeName } from './index';
3
+ import type { CustomColors } from '../config/appDataConfig';
3
4
  interface ThemeContextType {
4
5
  theme: Theme;
5
6
  themeName: ThemeName;
@@ -9,6 +10,7 @@ interface ThemeProviderProps {
9
10
  children: ReactNode;
10
11
  initialTheme?: ThemeName;
11
12
  theme?: Theme;
13
+ colorOverrides?: CustomColors | null;
12
14
  }
13
15
  export declare const ThemeProvider: React.FC<ThemeProviderProps>;
14
16
  export declare const useTheme: () => ThemeContextType;
@@ -37,9 +37,11 @@ exports.useSpacing = exports.useShadows = exports.useTypography = exports.useCol
37
37
  const react_1 = __importStar(require("react"));
38
38
  const index_1 = require("./index");
39
39
  const ThemeContext = (0, react_1.createContext)(undefined);
40
- const ThemeProvider = ({ children, initialTheme = 'primary', theme: customTheme, }) => {
40
+ const ThemeProvider = ({ children, initialTheme = 'primary', theme: customTheme, colorOverrides, }) => {
41
41
  const [currentThemeName, setCurrentThemeName] = react_1.default.useState(initialTheme);
42
- const theme = customTheme || (0, index_1.createTheme)(currentThemeName);
42
+ // If a full custom theme is provided, use it directly.
43
+ // Otherwise, create theme from name and merge any color overrides on top.
44
+ const theme = customTheme || (0, index_1.createTheme)(currentThemeName, colorOverrides);
43
45
  const setTheme = react_1.default.useCallback((themeName) => {
44
46
  setCurrentThemeName(themeName);
45
47
  }, []);
@@ -1,6 +1,7 @@
1
1
  import { colors, type ThemeName, type ColorScheme } from './colors';
2
2
  import { typography } from './typography';
3
3
  import { shadows } from './shadows';
4
+ import type { CustomColors } from '../config/appDataConfig';
4
5
  export declare const spacing: {
5
6
  readonly xs: 4;
6
7
  readonly sm: 8;
@@ -25,7 +26,11 @@ export interface Theme {
25
26
  spacing: typeof spacing;
26
27
  borderRadius: typeof borderRadius;
27
28
  }
28
- export declare const createTheme: (themeName?: ThemeName) => Theme;
29
+ /**
30
+ * Theme factory function.
31
+ * If customColors are provided, they override the corresponding colors from the base theme.
32
+ */
33
+ export declare const createTheme: (themeName?: ThemeName, customColors?: CustomColors | null) => Theme;
29
34
  export declare const getBorderColor: (theme: Theme, opacity?: number) => string;
30
35
  export declare const getShadowColor: (opacity?: number) => string;
31
36
  export { colors, typography, shadows, type ThemeName, type ColorScheme };
@@ -40,15 +40,31 @@ exports.borderRadius = {
40
40
  xl: 16,
41
41
  full: 9999,
42
42
  };
43
- // Theme factory function
44
- const createTheme = (themeName = 'primary') => ({
45
- colors: colors_1.colors[themeName],
46
- typography: typography_1.typography,
47
- shadows: shadows_1.shadows,
48
- spacing: exports.spacing,
49
- borderRadius: exports.borderRadius,
50
- });
43
+ /**
44
+ * Theme factory function.
45
+ * If customColors are provided, they override the corresponding colors from the base theme.
46
+ */
47
+ const createTheme = (themeName = 'primary', customColors) => {
48
+ const baseColors = colors_1.colors[themeName];
49
+ // Merge custom color overrides on top of the base theme colors
50
+ const mergedColors = customColors
51
+ ? Object.assign(Object.assign({}, baseColors), filterDefinedValues(customColors))
52
+ : baseColors;
53
+ return {
54
+ colors: mergedColors,
55
+ typography: typography_1.typography,
56
+ shadows: shadows_1.shadows,
57
+ spacing: exports.spacing,
58
+ borderRadius: exports.borderRadius,
59
+ };
60
+ };
51
61
  exports.createTheme = createTheme;
62
+ /**
63
+ * Utility to filter out undefined/null values so only explicitly set colors override the theme.
64
+ */
65
+ const filterDefinedValues = (obj) => {
66
+ return Object.fromEntries(Object.entries(obj).filter(([_, value]) => value !== undefined && value !== null));
67
+ };
52
68
  // Helper function to get border with opacity
53
69
  const getBorderColor = (theme, opacity = 0.36) => {
54
70
  const hex = theme.colors.border.replace('#', '');
@@ -0,0 +1,7 @@
1
+ export interface SSEEvent {
2
+ eventType: string;
3
+ data: string;
4
+ }
5
+ export declare function parseSSEBuffer(buffer: {
6
+ value: string;
7
+ }, chunk: string): SSEEvent[];
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseSSEBuffer = parseSSEBuffer;
4
+ function parseSSEBuffer(buffer, chunk) {
5
+ const results = [];
6
+ buffer.value += chunk;
7
+ let idx;
8
+ while ((idx = buffer.value.indexOf('\n\n')) >= 0) {
9
+ const block = buffer.value.slice(0, idx);
10
+ buffer.value = buffer.value.slice(idx + 2);
11
+ let eventType = 'message';
12
+ const dataparts = [];
13
+ for (const line of block.split(/\r?\n/)) {
14
+ if (line.startsWith('event:')) {
15
+ eventType = line.slice(6).trim();
16
+ }
17
+ else if (line.startsWith('data:')) {
18
+ dataparts.push(line.slice(5).trim());
19
+ }
20
+ }
21
+ const data = dataparts.join('\n').trim();
22
+ if (data) {
23
+ results.push({ eventType, data });
24
+ }
25
+ }
26
+ return results;
27
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finspringinnovations/fdsdk",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "FD SDK for React Native applications",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -73,4 +73,4 @@
73
73
  "url": "https://github.com/shriram-finance/shriramsdk/issues"
74
74
  },
75
75
  "license": "MIT"
76
- }
76
+ }
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { View, Text, StyleSheet, Image } from 'react-native';
3
- import { useColors, useTypography } from '../theme/ThemeContext';
3
+ import { useColors, useTypography, useTheme } from '../theme/ThemeContext';
4
4
  import { base64Images } from '../constants/strings/base64Images';
5
5
 
6
6
  interface CompanyHeaderProps {
@@ -11,7 +11,8 @@ interface CompanyHeaderProps {
11
11
  const CompanyHeader: React.FC<CompanyHeaderProps> = ({ companyName, rating }) => {
12
12
  const colors = useColors();
13
13
  const typography = useTypography();
14
- const styles = createStyles(colors, typography);
14
+ const { themeName } = useTheme();
15
+ const styles = createStyles(colors, typography, themeName);
15
16
 
16
17
  return (
17
18
  <View style={styles.wrapper}>
@@ -32,47 +33,52 @@ const CompanyHeader: React.FC<CompanyHeaderProps> = ({ companyName, rating }) =>
32
33
  );
33
34
  };
34
35
 
35
- const createStyles = (colors: any, typography: any) => StyleSheet.create({
36
- wrapper: {
37
- paddingHorizontal: 2,
38
- paddingTop: 12,
39
- paddingBottom: 8,
40
- },
41
- card: {
42
- backgroundColor: '#0B2940',
43
- borderRadius: 24,
44
- padding: 4,
45
- borderWidth: 1,
46
- borderColor: '#124061',
47
- shadowColor: '#000',
48
- shadowOpacity: 0.2,
49
- shadowOffset: { width: 0, height: 4 },
50
- shadowRadius: 12,
51
- elevation: 4,
52
- },
53
- innerBorder: {
54
- borderRadius: 20,
55
- padding: 16,
56
- flexDirection: 'row',
57
- alignItems: 'center',
58
- },
59
- logo: {
60
- width: 45,
61
- height: 45,
62
- marginRight: 12,
63
- },
64
- info: {
65
- flex: 1,
66
- },
67
- name: {
68
- ...typography.styles.h2,
69
- color: '#FFFFFF',
70
- marginBottom: 6,
71
- },
72
- rating: {
73
- ...typography.styles.text14Medium,
74
- color: '#BFD5F6',
75
- },
76
- });
36
+ const createStyles = (colors: any, typography: any, themeName: string) => {
37
+ const isDark = themeName === 'dark';
38
+
39
+ return StyleSheet.create({
40
+ wrapper: {
41
+ paddingHorizontal: 2,
42
+ paddingTop: 12,
43
+ paddingBottom: 8,
44
+ },
45
+ card: {
46
+ backgroundColor: isDark ? colors.headerBg : '#FFFFFF',
47
+ borderRadius: 24,
48
+ padding: 4,
49
+ borderWidth: 1,
50
+ borderColor: isDark ? colors.headerBg + 'AA' : colors.border + '40',
51
+ shadowColor: colors.shadow || '#000',
52
+ shadowOpacity: isDark ? 0.2 : 0.1,
53
+ shadowOffset: { width: 0, height: 4 },
54
+ shadowRadius: 12,
55
+ elevation: 4,
56
+ },
57
+ innerBorder: {
58
+ borderRadius: 20,
59
+ padding: 16,
60
+ flexDirection: 'row',
61
+ alignItems: 'center',
62
+ },
63
+ logo: {
64
+ width: 45,
65
+ height: 45,
66
+ marginRight: 12,
67
+ },
68
+ info: {
69
+ flex: 1,
70
+ },
71
+ name: {
72
+ ...typography.styles.h2,
73
+ color: isDark ? colors.headerText : colors.text,
74
+ marginBottom: 6,
75
+ },
76
+ rating: {
77
+ ...typography.styles.text14Medium,
78
+ color: isDark ? colors.headerText : colors.primary,
79
+ opacity: isDark ? 0.8 : 1,
80
+ },
81
+ });
82
+ };
77
83
 
78
84
  export default CompanyHeader;
@@ -42,9 +42,10 @@ const InterestRateCard: React.FC<InterestRateCardProps> = ({
42
42
  const createStyles = (colors: any, typography: any, themeName: string) => StyleSheet.create({
43
43
  card: {
44
44
  backgroundColor: themeName === 'dark' ? colors.inputBackground : 'rgba(0,235,180,0.1)',
45
- padding: 16,
45
+ paddingVertical: 16,
46
46
  borderRadius: 4,
47
47
  marginTop: 25,
48
+ paddingHorizontal: 12,
48
49
  },
49
50
  row: {
50
51
  flexDirection: 'row',
@@ -59,7 +60,7 @@ const createStyles = (colors: any, typography: any, themeName: string) => StyleS
59
60
  },
60
61
  label: {
61
62
  ...typography.styles.bodySmall,
62
- color: themeName === 'dark' ? colors.labelColor : colors.textLight,
63
+ color: colors.textLight,
63
64
  marginBottom: 4,
64
65
  },
65
66
  value: {