@finspringinnovations/fixeddepositsdk 1.0.3 → 1.0.4

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 (43) hide show
  1. package/lib/api/customerApi.js +1 -0
  2. package/lib/api/fdApi.d.ts +401 -1
  3. package/lib/api/fdApi.js +24 -2
  4. package/lib/config/workflowConstants.d.ts +40 -30
  5. package/lib/config/workflowConstants.js +35 -24
  6. package/lib/navigation/RootNavigator.js +62 -81
  7. package/lib/navigation/types.d.ts +4 -1
  8. package/lib/navigation/workflowNavigator.d.ts +1 -1
  9. package/lib/navigation/workflowNavigator.js +107 -26
  10. package/lib/screens/AadhaarVerification.js +1 -11
  11. package/lib/screens/AddBankAccount.js +1 -11
  12. package/lib/screens/BankDetail.js +1 -11
  13. package/lib/screens/Employee.js +1 -11
  14. package/lib/screens/FDCalculator.js +1 -11
  15. package/lib/screens/FDList.js +529 -416
  16. package/lib/screens/FindIFSC.js +1 -11
  17. package/lib/screens/NomineeDetail.js +12 -22
  18. package/lib/screens/PayNow.js +1 -11
  19. package/lib/screens/Payment.js +2 -11
  20. package/lib/screens/PaymentStatus.js +11 -21
  21. package/lib/screens/ReviewKYC.js +1 -11
  22. package/lib/types/workflowTypes.d.ts +1 -1
  23. package/package.json +1 -1
  24. package/src/api/customerApi.ts +1 -0
  25. package/src/api/fdApi.ts +28 -1
  26. package/src/config/workflowConstants.ts +51 -39
  27. package/src/constants/strings/bank.ts +80 -80
  28. package/src/navigation/RootNavigator.tsx +646 -663
  29. package/src/navigation/types.ts +4 -1
  30. package/src/navigation/workflowNavigator.ts +170 -107
  31. package/src/screens/AadhaarVerification.tsx +1 -15
  32. package/src/screens/AddBankAccount.tsx +2 -16
  33. package/src/screens/BankDetail.tsx +1 -15
  34. package/src/screens/Employee.tsx +1 -15
  35. package/src/screens/FDCalculator.tsx +1 -15
  36. package/src/screens/FDList.tsx +2311 -2171
  37. package/src/screens/FindIFSC.tsx +2 -16
  38. package/src/screens/NomineeDetail.tsx +762 -775
  39. package/src/screens/PayNow.tsx +2 -16
  40. package/src/screens/Payment.tsx +190 -203
  41. package/src/screens/PaymentStatus.tsx +574 -588
  42. package/src/screens/ReviewKYC.tsx +1 -15
  43. package/src/types/workflowTypes.ts +1 -10
@@ -1,2225 +1,2365 @@
1
- import React, { useState, useEffect, useCallback } from 'react';
2
1
  import {
3
- View,
4
- Text,
5
- StyleSheet,
6
- ScrollView,
7
- TouchableOpacity,
8
- ActivityIndicator,
9
- Alert,
10
- Platform,
11
- BackHandler,
12
- StatusBar,
13
- Image,
14
- } from 'react-native';
2
+ initializeEnvironment as initializeMahindraEnvironment, initializeSDK as initializeMahindraSDK, setGlobalData as setMahindraGlobalData, type AppData as MahindraAppData,
3
+ type EnvironmentData as MahindraEnvironmentData
4
+ } from '@finspringinnovations/mahindrafdsdk';
5
+ import {
6
+ initializeEnvironment as initializeShriramEnvironment,
7
+ initializeSDK as initializeShriramSDK,
8
+ type AppData as ShriramAppData,
9
+ type EnvironmentData as ShriramEnvironmentData,
10
+ } from '@finspringinnovations/shriramfdsdk';
15
11
  import { useFocusEffect } from '@react-navigation/native';
16
- import { Header, ActiveFDCard, FDCard, PendingFDBottomSheet } from '../components';
17
- import { useColors, useTypography, useSpacing, useTheme } from '../theme/ThemeContext';
12
+ import React, { useCallback, useEffect, useState } from 'react';
13
+ import {
14
+ ActivityIndicator,
15
+ Alert,
16
+ Image,
17
+ Platform,
18
+ ScrollView,
19
+ StyleSheet,
20
+ Text,
21
+ TouchableOpacity,
22
+ View
23
+ } from 'react-native';
24
+ import { useGetCustomerApplicationDetailsMutation, useGetCustomerApplicationsMutation } from '../api/customerApi';
25
+ import { useLazyGetPaymentUrlQuery, usePaymentReverseFeedMutation } from '../api/fdApi';
18
26
  import { useGetInterestRatesMutation } from '../api/interestRateApi';
19
- import { useGetCustomerApplicationsMutation, useGetCustomerApplicationDetailsMutation } from '../api/customerApi';
20
27
  import { useGetMasterDataQuery } from '../api/masterDataApi';
21
- import { useMasterData } from '../providers/MasterDataProvider';
22
28
  import { useTerminateWorkflowMutation } from '../api/workflowApi';
23
- import { usePaymentReverseFeedMutation } from '../api/fdApi';
24
- import type { ColorScheme, ThemeName } from '../theme';
29
+ import { ActiveFDCard, FDCard, PendingFDBottomSheet } from '../components';
25
30
  import SafeAreaWrapper from '../components/SafeAreaWrapper';
26
- import { getUserInfoForAPI, getAppData, getEnvironmentData } from '../config/appDataConfig';
31
+ import { getAppData, getEnvironmentData, getUserInfoForAPI } from '../config/appDataConfig';
32
+ import { MAHINDRA_WORKFLOW_STATES, SHRIRAM_WORKFLOW_STATES } from '../config/workflowConstants';
33
+ import { FD_STRINGS } from '../constants/strings';
34
+ import { base64Images } from '../constants/strings/base64Images';
27
35
  import { navigate } from '../navigation/helpers';
36
+ import { handleWorkflowNavigation } from '../navigation/workflowNavigator';
37
+ import { useMasterData } from '../providers/MasterDataProvider';
28
38
  import { useAppDispatch, useAppSelector } from '../store';
29
- import { setOnboardingIds } from '../store/onboardingSlice';
30
39
  import { setFDListSelected } from '../store/fdListSelectedSlice';
31
- import { handleWorkflowNavigation } from '../navigation/workflowNavigator';
32
- import { WORKFLOW_STATES, SHRIRAM_WORKFLOW_STATES, MAHINDRA_WORKFLOW_STATES } from '../config/workflowConstants';
33
- import { setGlobalData } from '../utils/globalData';
34
- import { setGlobalData as setMahindraGlobalData } from '@finspringinnovations/mahindrafdsdk';
35
- import { FD_STRINGS } from '../constants/strings';
36
- import { base64Images } from '../constants/strings/base64Images';
37
- import {
38
- initializeSDK as initializeShriramSDK,
39
- initializeEnvironment as initializeShriramEnvironment,
40
- type AppData as ShriramAppData,
41
- type EnvironmentData as ShriramEnvironmentData,
42
- } from '@finspringinnovations/shriramfdsdk';
43
- import {
44
- initializeSDK as initializeMahindraSDK,
45
- initializeEnvironment as initializeMahindraEnvironment,
46
- type AppData as MahindraAppData,
47
- type EnvironmentData as MahindraEnvironmentData,
48
- } from '@finspringinnovations/mahindrafdsdk';
40
+ import { setOnboardingIds } from '../store/onboardingSlice';
41
+ import type { ColorScheme, ThemeName } from '../theme';
42
+ import { useColors, useSpacing, useTheme, useTypography } from '../theme/ThemeContext';
43
+ import { getGlobalData, setGlobalData } from '../utils/globalData';
49
44
  var completeFDData: any = null;
50
45
 
51
46
  /** Resolve company logo type for FD list card by name or provider. */
52
47
  function getLogoTypeForFD(fd: { name?: string; providerId?: string }): 'shriram' | 'mahindra' | undefined {
53
- const name = (fd.name || '').toLowerCase();
54
- const providerId = (fd.providerId || '').toLowerCase();
55
- if (name.includes('shriram') || providerId.includes('shriram')) return 'shriram';
56
- if (name.includes('mahindra') || providerId.includes('mahindra') || providerId.includes('881d0b98')) return 'mahindra';
57
- return undefined;
48
+ const name = (fd.name || '').toLowerCase();
49
+ const providerId = (fd.providerId || '').toLowerCase();
50
+ if (name.includes('shriram') || providerId.includes('shriram')) return 'shriram';
51
+ if (name.includes('mahindra') || providerId.includes('mahindra') || providerId.includes('881d0b98')) return 'mahindra';
52
+ return undefined;
58
53
  }
59
54
 
60
55
  function getStaticCreditRatingForFD(fd: { name?: string; providerId?: string }): string {
61
- const logoType = getLogoTypeForFD(fd);
62
- if (logoType === 'mahindra') return 'AAA';
63
- if (logoType === 'shriram') return 'AAA';
64
- return 'AAA';
56
+ const logoType = getLogoTypeForFD(fd);
57
+ if (logoType === 'mahindra') return 'AAA';
58
+ if (logoType === 'shriram') return 'AAA';
59
+ return 'AAA';
65
60
  }
66
61
 
67
62
  /** Statuses that count as pending/active (in-progress) workflow. */
68
63
  const PENDING_ACTIVE_STATUSES = ['active', 'pending', 'in_progress', 'in-progress'];
69
64
 
70
65
  function getSdkTypeFromProviderId(
71
- providerId: string,
72
- mahindraProviderId: string,
73
- shriramProviderId: string
66
+ providerId: string,
67
+ mahindraProviderId: string,
68
+ shriramProviderId: string
74
69
  ): 'mahindra' | 'shriram' | null {
75
- const lower = (providerId || '').toLowerCase();
76
- if (lower === mahindraProviderId.toLowerCase()) return 'mahindra';
77
- if (lower === shriramProviderId.toLowerCase()) return 'shriram';
78
- return null;
70
+ const lower = (providerId || '').toLowerCase();
71
+ if (lower === mahindraProviderId.toLowerCase()) return 'mahindra';
72
+ if (lower === shriramProviderId.toLowerCase()) return 'shriram';
73
+ return null;
79
74
  }
80
75
 
81
76
  /** Get the pending/active application for a given provider, if any. */
82
77
  function getPendingOrActiveAppForProvider(
83
- applicationsData: any[],
84
- providerId: string
78
+ applicationsData: any[],
79
+ providerId: string
85
80
  ): any | null {
86
- const providerIdLower = (providerId || '').toLowerCase();
87
- return applicationsData.find((a: any) => {
88
- const appProviderId = (a.fd_provider_id || a.fdProviderId || a.provider_id || a.providerId || '').toLowerCase();
89
- if (appProviderId !== providerIdLower) return false;
90
- const s = (a.wf_status || a.status || '').toString().toLowerCase();
91
- return PENDING_ACTIVE_STATUSES.includes(s);
92
- }) || null;
81
+ const providerIdLower = (providerId || '').toLowerCase();
82
+ return applicationsData.find((a: any) => {
83
+ const appProviderId = (a.fd_provider_id || a.fdProviderId || a.provider_id || a.providerId || '').toLowerCase();
84
+ if (appProviderId !== providerIdLower) return false;
85
+ const s = (a.wf_status || a.status || '').toString().toLowerCase();
86
+ return PENDING_ACTIVE_STATUSES.includes(s);
87
+ }) || null;
93
88
  }
94
89
 
95
90
  /** Convert application to pending FD display data. */
96
91
  function appToPendingFDData(app: any): {
97
- name: string;
98
- invested: number;
99
- value: number;
100
- returns: number;
101
- maturityDate: string;
102
- interestPayout: string;
103
- logoType?: 'shriram' | 'mahindra';
92
+ name: string;
93
+ invested: number;
94
+ value: number;
95
+ returns: number;
96
+ maturityDate: string;
97
+ interestPayout: string;
98
+ tenure: string;
99
+ logoType?: 'shriram' | 'mahindra';
104
100
  } {
105
- const invested = Number(app.investment_amount ?? app.amount ?? app.investmentAmount ?? 0);
106
- const rate = Number(app.interest_rate ?? app.interestRate ?? 0);
107
- const value = Number(app.current_value ?? app.currentValue ?? app.maturity_amount ?? app.maturityAmount ?? 0);
108
- const maturityDateStr: string = app.maturity_date || app.maturityDate || '';
109
- let maturityDate = '--';
110
- if (maturityDateStr) {
111
- const d = new Date(maturityDateStr);
112
- if (!isNaN(d.getTime())) {
113
- const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
114
- maturityDate = `${months[d.getMonth()]} ${d.getFullYear()}`;
115
- }
116
- }
117
- const providerName = app.fd_provider_name || app.providerName || 'Mahindra Finance Ltd';
118
- const logoType = getLogoTypeForFD({ name: providerName, providerId: app.fd_provider_id || app.fdProviderId || '' });
119
- return {
120
- name: providerName,
121
- invested,
122
- value,
123
- returns: rate || 0,
124
- maturityDate,
125
- interestPayout: app.interest_payout_term || app.interestPayoutTerm || app.payout_term || 'Yearly',
126
- logoType: logoType ?? undefined,
127
- };
101
+ const invested = Number(app.investment_amount ?? app.amount ?? app.investmentAmount ?? 0);
102
+ const rate = Number(app.interest_rate ?? app.interestRate ?? 0);
103
+ const value = Number(app.current_value ?? app.currentValue ?? app.maturity_amount ?? app.maturityAmount ?? 0);
104
+ const maturityDateStr: string = app.maturity_date || app.maturityDate || '';
105
+ const tenure: string = Number(app.tenure_in_months) ? app.tenure_in_months + " Months" : '';
106
+ let maturityDate = '--';
107
+ if (maturityDateStr) {
108
+ const d = new Date(maturityDateStr);
109
+ if (!isNaN(d.getTime())) {
110
+ const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
111
+ maturityDate = `${months[d.getMonth()]} ${d.getFullYear()}`;
112
+ }
113
+ }
114
+ const providerName = app.fd_provider_name || app.providerName || 'Mahindra Finance Ltd';
115
+ const logoType = getLogoTypeForFD({ name: providerName, providerId: app.fd_provider_id || app.fdProviderId || '' });
116
+ return {
117
+ name: providerName,
118
+ invested,
119
+ value,
120
+ returns: rate || 0,
121
+ maturityDate,
122
+ interestPayout: app.interest_payout_term || app.interestPayoutTerm || app.payout_term || 'Yearly',
123
+ logoType: logoType ?? undefined,
124
+ tenure: tenure
125
+ };
128
126
  }
129
127
 
130
128
  /** Resolve providerId from app payload robustly; infer from provider name when id key is missing. */
131
129
  function resolveProviderIdFromApp(
132
- app: any,
133
- mahindraProviderId: string,
134
- shriramProviderId: string
130
+ app: any,
131
+ mahindraProviderId: string,
132
+ shriramProviderId: string
135
133
  ): string {
136
- const direct =
137
- app?.fd_provider_id ||
138
- app?.fdProviderId ||
139
- app?.provider_id ||
140
- app?.providerId ||
141
- app?.fd_provider ||
142
- app?.fdProvider ||
143
- app?.provider;
144
- if (direct) return String(direct);
145
-
146
- const providerName = String(
147
- app?.fd_provider_name ||
148
- app?.fdProviderName ||
149
- app?.provider_name ||
150
- app?.providerName ||
151
- ''
152
- ).toLowerCase();
153
-
154
- if (providerName.includes('mahindra')) return mahindraProviderId;
155
- if (providerName.includes('shriram')) return shriramProviderId;
156
- return '';
134
+ const direct =
135
+ app?.fd_provider_id ||
136
+ app?.fdProviderId ||
137
+ app?.provider_id ||
138
+ app?.providerId ||
139
+ app?.fd_provider ||
140
+ app?.fdProvider ||
141
+ app?.provider;
142
+ if (direct) return String(direct);
143
+
144
+ const providerName = String(
145
+ app?.fd_provider_name ||
146
+ app?.fdProviderName ||
147
+ app?.provider_name ||
148
+ app?.providerName ||
149
+ ''
150
+ ).toLowerCase();
151
+
152
+ if (providerName.includes('mahindra')) return mahindraProviderId;
153
+ if (providerName.includes('shriram')) return shriramProviderId;
154
+ return '';
157
155
  }
158
156
 
159
157
  export interface FDItem {
160
- id: string;
161
- providerId: string; // Added providerId field
162
- name: string;
163
- accountNumber: string;
164
- roi: string;
165
- tenure: string;
166
- amount: number;
167
- maturityDate: string;
168
- status: 'active' | 'matured' | 'pending';
169
- creditRating: string;
170
- interestPayout?: string; // Optional interest payout field
158
+ id: string;
159
+ providerId: string; // Added providerId field
160
+ name: string;
161
+ accountNumber: string;
162
+ roi: string;
163
+ tenure: string;
164
+ amount: number;
165
+ maturityDate: string;
166
+ status: 'active' | 'matured' | 'pending';
167
+ creditRating: string;
168
+ interestPayout?: string; // Optional interest payout field
171
169
  }
172
170
 
173
171
  export interface FDListProps {
174
- onGoBack?: () => void;
175
- onSelectFD?: (fdId: string) => void;
176
- onNavigateToFDCalculator?: (fdData?: any, masterData?: any) => void;
177
- onNavigateToExternalSDK?: (sdkType: any, fdData?: any, context?: any) => void;
178
- customStyles?: {
179
- container?: object;
180
- header?: object;
181
- title?: object;
182
- fdCard?: object;
183
- };
172
+ onGoBack?: () => void;
173
+ onSelectFD?: (fdId: string) => void;
174
+ onNavigateToFDCalculator?: (fdData?: any, masterData?: any) => void;
175
+ onNavigateToExternalSDK?: (sdkType: any, fdData?: any, context?: any) => void;
176
+ customStyles?: {
177
+ container?: object;
178
+ header?: object;
179
+ title?: object;
180
+ fdCard?: object;
181
+ };
184
182
  }
185
183
 
186
184
  const FDList: React.FC<FDListProps> = ({
187
- onGoBack,
188
- onSelectFD,
189
- onNavigateToFDCalculator,
190
- onNavigateToExternalSDK,
191
- customStyles = {},
185
+ onGoBack,
186
+ onSelectFD,
187
+ onNavigateToFDCalculator,
188
+ onNavigateToExternalSDK,
189
+ customStyles = {},
192
190
  }) => {
193
- const [activeTab, setActiveTab] = useState<string>(FD_STRINGS.ALL_FDS_TAB);
194
- const [showPendingFDBottomSheet, setShowPendingFDBottomSheet] = useState(false);
195
- const [selectedFD, setSelectedFD] = useState<FDItem | null>(null);
196
- /** Application for which the pending bottom sheet is shown (matches the FD list card provider they tapped). Used for terminate/continue. */
197
- const [pendingContextApp, setPendingContextApp] = useState<any>(null);
198
- const [isInitialized, setIsInitialized] = useState(false);
199
- /** Loading state for Mahindra customer/application/details prefetch (used for pending FD continue). */
200
- const [isLoadingMahindraCustomerDetails, setIsLoadingMahindraCustomerDetails] = useState(false);
201
- const colors = useColors();
202
- const typography = useTypography();
203
- const spacing = useSpacing();
204
- const { themeName } = useTheme();
205
- const dispatch = useAppDispatch();
206
- const hasFetchedRatesRef = React.useRef(false);
207
- const isCallingInterestRatesRef = React.useRef(false);
208
- const MahindraProviderId = '881d0b98-78d7-4668-931c-f4f47d753e9d';
209
- const ShriramProviderId = '92d94e74-c5cb-4141-91ad-ffa05df7fbff';
210
-
211
- // API calls to fetch
212
- const [getInterestRates, {
213
- data: interestRates,
214
- error: interestRatesError,
215
- isLoading: isLoadingRates,
216
- }] = useGetInterestRatesMutation();
217
-
218
- const [getCustomerApplications, {
219
- data: customerApplications,
220
- error: customerApplicationsError,
221
- isLoading: isLoadingApplications,
222
- }] = useGetCustomerApplicationsMutation();
223
- const [getCustomerApplicationDetails] = useGetCustomerApplicationDetailsMutation();
224
-
225
- const [terminateWorkflow, {
226
- data: terminateWorkflowData,
227
- error: terminateWorkflowError,
228
- isLoading: isTerminatingWorkflow,
229
- }] = useTerminateWorkflowMutation();
230
-
231
- // Payment Reverse Feed API
232
- const [paymentReverseFeed, {
233
- data: paymentReverseFeedResponse,
234
- error: paymentReverseFeedError,
235
- isLoading: isLoadingPaymentReverseFeed,
236
- }] = usePaymentReverseFeedMutation();
237
-
238
- const styles = createStyles(colors, typography, spacing, themeName);
239
- const { masterData: masterDataContext, setMasterData } = useMasterData();
240
-
241
- // Redux selectors for workflow IDs
242
- const workflowInstanceId = useAppSelector((state: any) => state?.onboarding?.workflowInstanceId);
243
- const applicationId = useAppSelector((state: any) => state?.onboarding?.applicationId);
244
- const entityId = useAppSelector((state: any) => state?.onboarding?.entityid);
245
- const providerId = useAppSelector((state: any) => state?.onboarding?.providerId);
246
- const onboardingIds = useAppSelector((state: any) => state?.onboarding);
247
-
248
- // Shared promise ref so FDList pending-flow and eager prefetch can wait
249
- // on the same Mahindra customer/application/details fetch.
250
- const mahindraDetailsPrefetchPromiseRef = React.useRef<Promise<void> | null>(null);
251
-
252
- const initializeExternalSDKFromFDList = useCallback((
253
- sdkType: 'shriram' | 'mahindra',
254
- options?: { silent?: boolean }
255
- ): boolean => {
256
- const silent = options?.silent === true;
257
- const currentAppData = getAppData();
258
- const storedEnvData = getEnvironmentData();
259
- const currentEnvData = storedEnvData as ShriramEnvironmentData & MahindraEnvironmentData | null;
260
-
261
- if (!currentAppData) {
262
- if (!silent) {
263
- Alert.alert('Error', 'SDK app data not available. Please restart FD flow.');
264
- }
265
- return false;
266
- }
267
- if (!currentEnvData) {
268
- if (!silent) {
269
- Alert.alert('Error', 'SDK environment data not available. Please initialize environment from host app.');
270
- }
271
- return false;
272
- }
273
-
274
- try {
275
- if (sdkType === 'shriram') {
276
- initializeShriramEnvironment(currentEnvData as ShriramEnvironmentData);
277
- initializeShriramSDK(currentAppData as ShriramAppData);
278
- } else {
279
- initializeMahindraEnvironment(currentEnvData as MahindraEnvironmentData);
280
- initializeMahindraSDK(currentAppData as MahindraAppData);
281
- }
282
- return true;
283
- } catch (error: any) {
284
- if (!silent) {
285
- Alert.alert(
286
- 'Error',
287
- `Failed to initialize ${sdkType === 'shriram' ? 'Shriram' : 'Mahindra'} SDK: ${error?.message || 'Unknown error'}`
288
- );
289
- }
290
- return false;
291
- }
292
- }, []);
293
-
294
- // As soon as FDList mounts, warm-initialize embedded SDKs with the same FDSDK data/env.
295
- useEffect(() => {
296
- initializeExternalSDKFromFDList('shriram', { silent: true });
297
- initializeExternalSDKFromFDList('mahindra', { silent: true });
298
- }, [initializeExternalSDKFromFDList]);
299
-
300
- // Helper function to check if customer applications is empty
301
- const isCustomerApplicationsEmpty = () => {
302
- if (!customerApplications) {
303
- return true;
304
- }
305
-
306
- // Handle direct array response
307
- if (Array.isArray(customerApplications)) {
308
- const isEmpty = customerApplications.length === 0;
309
- return isEmpty;
310
- }
311
-
312
- // Handle response with data property
313
- if (customerApplications.data && Array.isArray(customerApplications.data)) {
314
- const isEmpty = customerApplications.data.length === 0;
315
- return isEmpty;
316
- }
317
-
318
- // Handle response with applications property
319
- if (customerApplications.applications && Array.isArray(customerApplications.applications)) {
320
- const isEmpty = customerApplications.applications.length === 0;
321
- return isEmpty;
322
- }
323
-
324
- // If customerApplications exists but doesn't match expected structure, consider it not empty
325
- return false;
326
- };
327
-
328
- // Helper function to get provider ID from interest rates response (robust to varying shapes)
329
- const getProviderIdFromInterestRates = () => {
330
- try {
331
- const data = interestRates?.data;
332
- if (!data) return undefined;
333
-
334
- const extractId = (rate: any) => {
335
- return (
336
- rate?.providerId ||
337
- rate?.provider_id ||
338
- rate?.fd_provider_id ||
339
- rate?.fdProviderId ||
340
- rate?.provider?.id ||
341
- undefined
342
- );
343
- };
344
-
345
- // Prefer SDR
346
- const sdr = Array.isArray(data?.sdrScheme) ? data.sdrScheme : [];
347
- for (const r of sdr) {
348
- const id = extractId(r);
349
- if (id) {
350
- return id;
351
- }
352
- }
353
-
354
- // Fallback to FDR
355
- const fdr = Array.isArray(data?.fdrScheme) ? data.fdrScheme : [];
356
- for (const r of fdr) {
357
- const id = extractId(r);
358
- if (id) {
359
- return id;
360
- }
361
- }
362
-
363
- return undefined;
364
- } catch (e) {
365
- return undefined;
366
- }
367
- };
368
-
369
- /**
370
- * Prefetch Mahindra customer/application/details for the currently pending/active
371
- * Mahindra FD application. This is used to ensure that when we navigate into
372
- * Mahindra SDK screens (especially via "Continue" on a pending FD), all
373
- * customer details are already available, and we show a loader while fetching.
374
- */
375
- const prefetchMahindraCustomerApplicationDetails = useCallback(async (overrideMahindraApp?: any) => {
376
- // Avoid duplicate fetches reuse in-flight promise if any
377
- if (mahindraDetailsPrefetchPromiseRef.current) {
378
- return mahindraDetailsPrefetchPromiseRef.current;
379
- }
380
-
381
- const promise = (async () => {
382
- try {
383
- let mahindraApp: any = null;
384
- const overrideProviderId = (
385
- overrideMahindraApp?.fd_provider_id ||
386
- overrideMahindraApp?.fdProviderId ||
387
- overrideMahindraApp?.provider_id ||
388
- overrideMahindraApp?.providerId ||
389
- ''
390
- ).toString();
391
-
392
- if (overrideMahindraApp && overrideProviderId.toLowerCase() === MahindraProviderId.toLowerCase()) {
393
- mahindraApp = overrideMahindraApp;
394
- } else {
395
- if (!customerApplications || isCustomerApplicationsEmpty()) {
396
- return;
397
- }
398
-
399
- // Normalize list
400
- let applicationsData: any[] = [];
401
- if (Array.isArray(customerApplications)) {
402
- applicationsData = customerApplications;
403
- } else if (customerApplications.data && Array.isArray(customerApplications.data)) {
404
- applicationsData = customerApplications.data;
405
- } else if (customerApplications.applications && Array.isArray(customerApplications.applications)) {
406
- applicationsData = customerApplications.applications;
407
- }
408
-
409
- if (!applicationsData.length) return;
410
-
411
- // Find Mahindra pending/active app for MahindraProviderId
412
- mahindraApp = getPendingOrActiveAppForProvider(applicationsData, MahindraProviderId);
413
- }
414
- if (!mahindraApp) return;
415
-
416
- const ids = {
417
- workflowInstanceId:
418
- mahindraApp.workflow_instance_id ||
419
- mahindraApp.workflowInstanceId ||
420
- mahindraApp.workflowinstanceid ||
421
- mahindraApp.workflow_instance ||
422
- mahindraApp.workflowInstance ||
423
- undefined,
424
- applicationId:
425
- mahindraApp.application_id ||
426
- mahindraApp.applicationId ||
427
- mahindraApp.applicationid ||
428
- undefined,
429
- entityid:
430
- mahindraApp.entity_id ||
431
- mahindraApp.entityId ||
432
- mahindraApp.entityid ||
433
- undefined,
434
- customerId:
435
- mahindraApp.customer_id ||
436
- mahindraApp.customerId ||
437
- mahindraApp.customerid ||
438
- undefined,
439
- providerId:
440
- mahindraApp.fd_provider_id ||
441
- mahindraApp.fdProviderId ||
442
- mahindraApp.provider_id ||
443
- mahindraApp.providerId ||
444
- MahindraProviderId,
445
- };
446
-
447
- if (!ids.applicationId || !ids.customerId || !ids.providerId) {
448
- return;
449
- }
450
-
451
- setIsLoadingMahindraCustomerDetails(true);
452
-
453
- const userInfo = getUserInfoForAPI();
454
- const detailsResp = await getCustomerApplicationDetails({
455
- providerId: ids.providerId,
456
- applicationId: ids.applicationId,
457
- customerId: ids.customerId,
458
- workflowInstanceId: ids.workflowInstanceId,
459
- userreferenceid: userInfo.userReferenceId,
460
- applicationid: ids.applicationId,
461
- entityid: ids.entityid,
462
- }).unwrap();
463
-
464
- const detailsData = (detailsResp as any)?.data || detailsResp;
465
- if (detailsData) {
466
- setGlobalData({ mahindraCustomerDetails: detailsData });
467
- setMahindraGlobalData({ mahindraCustomerDetails: detailsData });
468
- }
469
- } catch (err) {
470
- } finally {
471
- setIsLoadingMahindraCustomerDetails(false);
472
- mahindraDetailsPrefetchPromiseRef.current = null;
473
- }
474
- })();
475
-
476
- mahindraDetailsPrefetchPromiseRef.current = promise;
477
- return promise;
478
- }, [customerApplications, getCustomerApplicationDetails]);
479
-
480
- // Helper function to filter FDs based on tenure
481
- const filterFDsByTenure = (fdList: FDItem[], filter: string): FDItem[] => {
482
- if (filter === FD_STRINGS.ALL_FDS_TAB) return fdList;
483
-
484
- return fdList.filter(fd => {
485
- // Extract tenure in months from the fd.tenure string (e.g., "12 Months" -> 12)
486
- const tenureMatch = fd.tenure.match(/(\d+)\s*Months?/i);
487
- if (!tenureMatch) return false;
488
-
489
- const tenureMonths = parseInt(tenureMatch[1], 10);
490
- const tenureYears = tenureMonths / 12;
491
-
492
- switch (filter) {
493
- case '<1Y':
494
- return tenureYears <= 1;
495
- case '<1-3Y':
496
- return tenureYears > 1 && tenureYears <= 3;
497
- case '<3-5Y':
498
- return tenureYears > 3 && tenureYears <= 5;
499
- case '>5Y':
500
- return tenureYears > 5;
501
- default:
502
- return true;
503
- }
504
- });
505
- };
506
-
507
-
508
-
509
- // Derive identifiers for workflow termination from applications API (for active FD/provider)
510
- const terminateIdentifiers = React.useMemo(() => {
511
- try {
512
- if (!customerApplications || isCustomerApplicationsEmpty()) {
513
- return {
514
- applicationId: undefined as string | undefined,
515
- workflowInstanceId: undefined as string | undefined,
516
- entityId: undefined as string | undefined,
517
- providerIdFromApp: undefined as string | undefined,
518
- };
519
- }
520
-
521
- // Normalize list
522
- let applicationsData: any[] = [];
523
- if (Array.isArray(customerApplications)) {
524
- applicationsData = customerApplications;
525
- } else if (customerApplications.data && Array.isArray(customerApplications.data)) {
526
- applicationsData = customerApplications.data;
527
- } else if (customerApplications.applications && Array.isArray(customerApplications.applications)) {
528
- applicationsData = customerApplications.applications;
529
- }
530
-
531
- if (applicationsData.length === 0) {
532
- return {
533
- applicationId: undefined,
534
- workflowInstanceId: undefined,
535
- entityId: undefined,
536
- providerIdFromApp: undefined,
537
- };
538
- }
539
-
540
- // Prefer a pending/in-progress one for termination, else first
541
- const appForTerminate = applicationsData.find((a: any) => {
542
- const s = (a.wf_status || a.status || '').toString().toLowerCase();
543
- return s === 'pending' || s === 'in_progress' || s === 'in-progress';
544
- }) || applicationsData[0];
545
-
546
- const applicationId: string | undefined =
547
- appForTerminate.application_id || appForTerminate.applicationId || appForTerminate.id;
548
- const workflowInstanceId: string | undefined =
549
- appForTerminate.workflow_instance_id || appForTerminate.workflowInstanceId || appForTerminate.workflow?.instanceId;
550
- const entityId: string | undefined =
551
- appForTerminate.entity_id || appForTerminate.entityId || appForTerminate.entity?.id;
552
- const providerIdFromApp: string | undefined =
553
- appForTerminate.provider_id || appForTerminate.providerId || appForTerminate.fd_provider_id || appForTerminate.fdProviderId;
554
-
555
- return { applicationId, workflowInstanceId, entityId, providerIdFromApp };
556
- } catch (e) {
557
- return {
558
- applicationId: undefined,
559
- workflowInstanceId: undefined,
560
- entityId: undefined,
561
- providerIdFromApp: undefined,
562
- };
563
- }
564
- }, [customerApplications]);
565
-
566
- const activeProviderId = terminateIdentifiers.providerIdFromApp;
567
-
568
- // Master data fetch - prefer tapped FD provider, then active app provider, then rates
569
- const defaultProviderId = ''; // Default provider ID for initial call
570
- const inferredProviderId = React.useMemo(() => {
571
- if (selectedFD?.providerId) return selectedFD.providerId;
572
- if (activeProviderId) return activeProviderId;
573
- if (interestRates?.data?.sdrScheme?.[0]?.providerId) return interestRates.data.sdrScheme[0].providerId;
574
- if (interestRates?.data?.fdrScheme?.[0]?.providerId) return interestRates.data.fdrScheme[0].providerId;
575
- return defaultProviderId;
576
- }, [selectedFD, activeProviderId, interestRates]);
577
-
578
- const { data: masterData, isLoading: isLoadingMaster } = useGetMasterDataQuery(
579
- { providerId: inferredProviderId },
580
- { skip: !inferredProviderId }
581
- );
582
- const { data: MahindraMasterData, refetch: refetchMahindraMasterData } = useGetMasterDataQuery(
583
- { providerId: MahindraProviderId },
584
- { skip: false }
585
- );
586
- const { data: ShriramMasterData, refetch: refetchShriramMasterData } = useGetMasterDataQuery(
587
- { providerId: ShriramProviderId },
588
- { skip: false }
589
- );
590
-
591
- // Only render once all three API calls have completed (success or error)
592
- // Show loading initially until all APIs have completed
593
- const isAllDataReady = React.useMemo(() => {
594
- // If not initialized yet, show loading
595
- if (!isInitialized) return false;
596
-
597
- // Step 1: Check if interest rates API has been called and completed
598
- // If currently loading, show loading
599
- if (isLoadingRates) return false;
600
- // If not loading but no data and no error, API hasn't been called yet - show loading
601
- if (!interestRates && !interestRatesError) return false;
602
-
603
- // Step 2: Once interest rates are done, customer applications should be called
604
- // If currently loading, show loading
605
- if (isLoadingApplications) return false;
606
- // If interest rates succeeded, we must wait for customer applications
607
- // If interest rates failed, we can still proceed (but applications might not be called)
608
- if (interestRates) {
609
- // Interest rates succeeded, so applications should be called
610
- // If not loading but no data and no error, applications haven't been called yet - show loading
611
- if (!customerApplications && !customerApplicationsError) return false;
612
- }
613
-
614
- // Step 3: Check master data - only required if providerId is available
615
- const masterShouldRun = !!inferredProviderId;
616
- if (masterShouldRun) {
617
- // Master data should be done (either loaded or error, but not loading)
618
- if (isLoadingMaster) return false;
619
- }
620
-
621
- return true;
622
- }, [isInitialized, interestRates, interestRatesError, isLoadingRates, customerApplications, customerApplicationsError, isLoadingApplications, inferredProviderId, isLoadingMaster]);
623
-
624
- useEffect(() => {
625
- if (masterData) {
626
- setMasterData(masterData);
627
- }
628
- }, [masterData, setMasterData]);
629
-
630
- // Function to refresh all data - Required sequence: Interest -> Applications -> Master
631
- const refreshData = useCallback(async () => {
632
- if (!isInitialized) {
633
- return;
634
- }
635
-
636
- // Prevent multiple simultaneous calls
637
- if (isCallingInterestRatesRef.current || isLoadingRates) {
638
- return;
639
- }
640
-
641
- try {
642
- isCallingInterestRatesRef.current = true;
643
- // Add 500ms delay before calling interest rate API
644
- await new Promise(resolve => setTimeout(resolve, 500));
645
- await getInterestRates({});
646
- } catch (error) {
647
- // Handle error silently
648
- } finally {
649
- isCallingInterestRatesRef.current = false;
650
- }
651
- }, [getInterestRates, isInitialized, isLoadingRates]);
652
-
653
- // Initialize component after mount
654
- useEffect(() => {
655
- const initializeComponent = async () => {
656
- // Validate required user data before proceeding
657
- const appData = getAppData();
658
-
659
- const requiredFields = [
660
- { field: 'id', label: 'User ID' },
661
- { field: 'name', label: 'User Name' },
662
- { field: 'dob', label: 'Date of Birth' },
663
- { field: 'gender', label: 'Gender' },
664
- { field: 'mobNo', label: 'Mobile Number' },
665
- { field: 'email', label: 'Email' },
666
- ];
667
-
668
- const missingFields: string[] = [];
669
-
670
- requiredFields.forEach(({ field, label }) => {
671
- const value = appData?.[field as keyof typeof appData];
672
- if (!value || (typeof value === 'string' && !value.trim())) {
673
- missingFields.push(label);
674
- }
675
- });
676
-
677
- // Show alert if required fields are missing
678
- if (missingFields.length > 0) {
679
- Alert.alert(
680
- 'Cannot Proceed with FD Booking',
681
- `The following required information is missing from the main app:\n\n${missingFields.join('\n')}\n\nPlease provide all required user information to proceed.`,
682
- [
683
- {
684
- text: 'Cancel FD Booking',
685
- onPress: () => {
686
- // Redirect back to main app
687
- if (onGoBack) {
688
- onGoBack();
689
- } else {
690
- // No-op when onGoBack is not provided
691
- }
692
- },
693
- style: 'cancel',
694
- },
695
- ],
696
- { cancelable: false }
697
- );
698
- return; // Don't initialize if validation fails
699
- }
700
-
701
- // Additional value validation (case-insensitive): gender, maritalStatus, typeOfAccount
702
- const errors: string[] = [];
703
-
704
- // Gender must be Male, Female, or Other (case-insensitive)
705
- const gender = String(appData?.gender || '').trim().toLowerCase();
706
- const allowedGenders = ['male', 'female', 'other'];
707
- if (gender && !allowedGenders.includes(gender)) {
708
- errors.push("Gender must be one of: Male, Female, Other");
709
- }
710
-
711
- // Marital status must be Married, Unmarried, or Other (case-insensitive)
712
- const maritalStatus = String(appData?.maritalStatus || '').trim().toLowerCase();
713
- const allowedMarital = ['married', 'unmarried', 'other'];
714
- if (maritalStatus && !allowedMarital.includes(maritalStatus)) {
715
- errors.push("Marital Status must be one of: Married, Unmarried, Other");
716
- }
717
-
718
- // Account type must be 'Saving A/c' or 'Current A/c' (support legacy codes like SB/CA)
719
- const typeOfAccountRaw = String(appData?.typeOfAccount || '').trim();
720
- const normalizeAccountType = (value: string) =>
721
- value.replace(/[^a-z]/gi, '').toLowerCase();
722
- const accountTypeMap: Record<string, 'Saving A/c' | 'Current A/c'> = {
723
- savingac: 'Saving A/c',
724
- savingaccount: 'Saving A/c',
725
- savingsaccount: 'Saving A/c',
726
- savings: 'Saving A/c',
727
- saving: 'Saving A/c',
728
- sb: 'Saving A/c',
729
- currentac: 'Current A/c',
730
- currentaccount: 'Current A/c',
731
- current: 'Current A/c',
732
- ca: 'Current A/c',
733
- };
734
- const normalizedAccountType = normalizeAccountType(typeOfAccountRaw);
735
- const canonicalAccountType = accountTypeMap[normalizedAccountType];
736
- if (typeOfAccountRaw && !canonicalAccountType) {
737
- errors.push("Type of Account must be one of: 'Saving A/c', 'Current A/c'");
738
- }
739
-
740
- if (errors.length > 0) {
741
- Alert.alert(
742
- 'Invalid User Data',
743
- `${errors.join('\n')}`,
744
- [
745
- {
746
- text: 'OK',
747
- onPress: () => {
748
- if (onGoBack) {
749
- onGoBack();
750
- }
751
- },
752
- style: 'cancel',
753
- },
754
- ],
755
- { cancelable: false }
756
- );
757
- return; // Stop initialization on invalid values
758
- }
759
-
760
- // Small delay to ensure component is fully mounted
761
- await new Promise(resolve => setTimeout(resolve, 100));
762
- setIsInitialized(true);
763
- };
764
-
765
- initializeComponent();
766
- }, [onGoBack]);
767
-
768
- // Handle Android hardware back button - use same navigation as header back button
769
- useEffect(() => {
770
- if (Platform.OS !== 'android') return;
771
-
772
- const onHardwareBackPress = () => {
773
- // Call onGoBack which triggers onExit in RootNavigator
774
- if (onGoBack) {
775
- onGoBack();
776
- }
777
- return true; // Prevent default behavior
778
- };
779
-
780
- const backHandler = BackHandler.addEventListener(
781
- 'hardwareBackPress',
782
- onHardwareBackPress
783
- );
784
-
785
- return () => backHandler.remove();
786
- }, [onGoBack]);
787
-
788
- // Removed init-time fetch to avoid duplicate calls; we'll fetch on first focus only
789
-
790
- // Refresh data when screen comes into focus (only if initialized)
791
- useFocusEffect(
792
- useCallback(() => {
793
- if (!isInitialized) return;
794
- if (hasFetchedRatesRef.current) return; // ensure called only once when screen first shows
795
- // Prevent multiple simultaneous calls
796
- if (isCallingInterestRatesRef.current || isLoadingRates) return;
797
- hasFetchedRatesRef.current = true;
798
- refreshData();
799
- }, [refreshData, isInitialized, isLoadingRates])
800
- );
801
-
802
- // As soon as FDList has customer applications, eagerly prefetch Mahindra
803
- // customer/application/details so that if user taps a Mahindra pending FD,
804
- // details are already cached (or at least being fetched with a loader).
805
- useEffect(() => {
806
- if (!isInitialized) return;
807
- if (!customerApplications || isCustomerApplicationsEmpty()) return;
808
- prefetchMahindraCustomerApplicationDetails();
809
- }, [isInitialized, customerApplications, prefetchMahindraCustomerApplicationDetails]);
810
-
811
- // After interest rates are received, call Customer Applications
812
- useEffect(() => {
813
- if (!interestRates) return;
814
- try {
815
- const userInfo = getUserInfoForAPI();
816
- getCustomerApplications({ userReferenceId: userInfo.id });
817
- } catch (e) {
818
- }
819
- // eslint-disable-next-line react-hooks/exhaustive-deps
820
- }, [!!interestRates]);
821
-
822
- // Handle API errors
823
- useEffect(() => {
824
- if (interestRatesError) {
825
- Alert.alert(
826
- 'API Error',
827
- 'Failed to fetch interest rates. Please try again.',
828
- [
829
- {
830
- text: 'Retry',
831
- onPress: async () => {
832
- // Prevent multiple simultaneous calls
833
- if (isCallingInterestRatesRef.current || isLoadingRates) {
834
- return;
835
- }
836
- try {
837
- isCallingInterestRatesRef.current = true;
838
- // Add 500ms delay before retrying interest rate API
839
- await new Promise(resolve => setTimeout(resolve, 500));
840
- await getInterestRates({});
841
- } catch (error) {
842
- // Handle error silently
843
- } finally {
844
- isCallingInterestRatesRef.current = false;
845
- }
846
- }
847
- },
848
- { text: 'Continue', style: 'cancel' }
849
- ]
850
- );
851
- }
852
- }, [interestRatesError, getInterestRates]);
853
-
854
- // Log successful data fetch
855
- useEffect(() => {
856
- if (interestRates && interestRates.data?.sdrScheme?.length > 0) {
857
- // Interest rates loaded successfully
858
- }
859
- }, [interestRates]);
860
-
861
- // Persist providerId to onboarding store as soon as we have rates
862
- // useEffect(() => {
863
- // const pid = getProviderIdFromInterestRates();
864
- // if (pid) {
865
- // try {
866
- // dispatch(setOnboardingIds({ providerId: pid }));
867
- // } catch (e) {
868
- // // Handle error silently
869
- // }
870
- // }
871
- // }, [interestRates, dispatch]);
872
-
873
- // useEffect(() => {
874
- // if (customerApplications) {
875
- // // Customer applications loaded successfully
876
-
877
- // // Persist onboarding identifiers globally for subsequent API calls (like FDCalculator)
878
- // try {
879
- // // Extract workflow parameters from response
880
- // const responseData = customerApplications?.data || customerApplications;
881
- // let applicationData = Array.isArray(responseData) ? responseData[0] : responseData;
882
-
883
- // // Prefer the application with wf_status === 'Active'
884
- // if (Array.isArray(responseData)) {
885
- // const activeOnly = responseData.find((app: any) => {
886
- // const status = (app.wf_status || app.status || '').toString();
887
- // return status === 'Active';
888
- // });
889
- // if (activeOnly) applicationData = activeOnly;
890
- // }
891
-
892
- // if (Array.isArray(responseData)) {
893
- // completeFDData = responseData.find((app: any) => {
894
- // const status = (app.wf_status || app.status || '').toString();
895
- // return status === 'Completed';
896
- // });
897
- // setGlobalData({ completeFDData: !!completeFDData });
898
- // }
899
- // if (applicationData) {
900
- // const ids = {
901
- // workflowInstanceId: applicationData.workflow_instance_id,
902
- // applicationId: applicationData.application_id,
903
- // entityid: applicationData.entity_id,
904
- // customerId: applicationData.customer_id,
905
- // fdId: applicationData.fd_id,
906
- // currentState: applicationData.current_state,
907
- // currentTask: applicationData.current_task,
908
- // // Persist providerId from interest-rate API (prefers SDR, fallback FDR)
909
- // providerId: getProviderIdFromInterestRates(),
910
- // wfStatus: applicationData.wf_status,
911
- // };
912
- // if (dispatch && setOnboardingIds) {
913
- // dispatch(setOnboardingIds(ids));
914
- // } else {
915
- // // Dispatch or setOnboardingIds unavailable
916
- // }
917
- // }
918
- // } catch (error) {
919
- // // Handle error silently
920
- // }
921
- // }
922
- // }, [customerApplications, dispatch]);
923
-
924
- // Unified handler: behave like bottom sheet Continue flow
925
- const handlePendingFDContinue = async (
926
- overrideApp?: any,
927
- forcedSdkType?: 'mahindra' | 'shriram'
928
- ) => {
929
- // Get current state from customer applications for workflow navigation
930
- let currentState: any = null;
931
- let transactionId: string | undefined = undefined;
932
- let appData: any = overrideApp || pendingContextApp;
933
-
934
- let fdDisplay: ReturnType<typeof appToPendingFDData> | null = null;
935
- let onboardingIdsFromApp: any = null;
936
-
937
- try {
938
- if (!appData && customerApplications && !isCustomerApplicationsEmpty()) {
939
- // Normalize list
940
- let applicationsData: any[] = [];
941
- if (Array.isArray(customerApplications)) {
942
- applicationsData = customerApplications;
943
- } else if (customerApplications.data && Array.isArray(customerApplications.data)) {
944
- applicationsData = customerApplications.data;
945
- } else if (customerApplications.applications && Array.isArray(customerApplications.applications)) {
946
- applicationsData = customerApplications.applications;
947
- }
948
-
949
- if (applicationsData.length > 0) {
950
- // Fallback: find first Active application when no override/pendingContext
951
- appData = applicationsData.find((a: any) => {
952
- const s = (a.wf_status || a.status || '').toString();
953
- return s === 'Active';
954
- }) || applicationsData.find((a: any) => {
955
- const s = (a.wf_status || a.status || '').toString().toLowerCase();
956
- return PENDING_ACTIVE_STATUSES.includes(s);
957
- }) || applicationsData[0];
958
- }
959
- }
960
- if (appData) {
961
- currentState = appData.current_state;
962
- transactionId = appData.transaction_id || appData.transactionId;
963
- fdDisplay = appToPendingFDData(appData);
964
- let providerIdFromApp = resolveProviderIdFromApp(appData, MahindraProviderId, ShriramProviderId);
965
- if (forcedSdkType === 'mahindra') {
966
- providerIdFromApp = MahindraProviderId;
967
- } else if (forcedSdkType === 'shriram') {
968
- providerIdFromApp = ShriramProviderId;
969
- }
970
- const providerIdLower = (providerIdFromApp || '').toLowerCase();
971
- const isMahindra = providerIdLower === MahindraProviderId.toLowerCase();
972
-
973
- // For Mahindra flows, ensure customer/application/details are fully loaded
974
- // before navigating into ExternalSDK so downstream screens see complete
975
- // nominee/bank/KYC/guardian data. Show a loader while we wait.
976
- if (isMahindra) {
977
- await prefetchMahindraCustomerApplicationDetails(appData);
978
- }
979
-
980
- try {
981
- const fdListSelectedData = {
982
- id: appData.fd_id || appData.fdId || 'active-fd',
983
- providerId: providerIdFromApp,
984
- name: fdDisplay.name,
985
- accountNumber: appData.account_number || appData.accountNumber || '',
986
- roi: `${fdDisplay.returns}% p.a.`,
987
- tenure: fdDisplay.maturityDate || '-',
988
- amount: Number(fdDisplay.invested) || 0,
989
- maturityDate: fdDisplay.maturityDate || '-',
990
- status: 'active' as const,
991
- creditRating: getStaticCreditRatingForFD({
992
- name: fdDisplay.name,
993
- providerId: providerIdFromApp,
994
- }),
995
- companyName: fdDisplay.name,
996
- fdRate: `${fdDisplay.returns}% p.a.`,
997
- interestPayout: fdDisplay.interestPayout || '',
998
- };
999
- dispatch(setFDListSelected(fdListSelectedData));
1000
- } catch (e) {
1001
- // Handle error silently
1002
- }
1003
-
1004
- try {
1005
- const ids = {
1006
- workflowInstanceId: appData.workflow_instance_id || appData.workflowInstanceId,
1007
- applicationId: appData.application_id || appData.applicationId,
1008
- entityid: appData.entity_id || appData.entityId,
1009
- customerId: appData.customer_id || appData.customerId,
1010
- fdId: appData.fd_id || appData.fdId,
1011
- currentState: appData.current_state || appData.currentState,
1012
- currentTask: appData.current_task || appData.currentTask,
1013
- providerId: providerIdFromApp || undefined,
1014
- wfStatus: appData.wf_status || appData.wfStatus,
1015
- };
1016
- onboardingIdsFromApp = ids;
1017
- dispatch(setOnboardingIds(ids));
1018
- } catch (e) {
1019
- }
1020
- }
1021
- } catch (error) {
1022
- // Handle error silently
1023
- }
1024
-
1025
- if (!fdDisplay && appData) fdDisplay = appToPendingFDData(appData);
1026
-
1027
- // If current state is payment and transactionId is available, call payment reverse feed API
1028
- // Check for both Mahindra and Shriram payment states
1029
- const isPaymentState = currentState === WORKFLOW_STATES.PAYMENT ||
1030
- currentState === MAHINDRA_WORKFLOW_STATES.PAYMENT ||
1031
- currentState === SHRIRAM_WORKFLOW_STATES.PAYMENT;
1032
-
1033
- if (isPaymentState && transactionId) {
1034
- try {
1035
- // Get user info and required IDs (prefer appData from continue context)
1036
- const userInfo = getUserInfoForAPI();
1037
- let providerIdFromApp = resolveProviderIdFromApp(appData, MahindraProviderId, ShriramProviderId);
1038
- if (forcedSdkType === 'mahindra') providerIdFromApp = MahindraProviderId;
1039
- if (forcedSdkType === 'shriram') providerIdFromApp = ShriramProviderId;
1040
- const paymentReverseFeedRequest = {
1041
- providerId: providerIdFromApp || providerId,
1042
- workflowInstanceId: appData?.workflow_instance_id || appData?.workflowInstanceId || workflowInstanceId,
1043
- userreferenceid: userInfo.id,
1044
- applicationid: appData?.application_id || appData?.applicationId || applicationId,
1045
- entityid: appData?.entity_id || appData?.entityId || entityId,
1046
- transactionId: transactionId,
1047
- };
1048
-
1049
- const response = await paymentReverseFeed(paymentReverseFeedRequest).unwrap();
1050
-
1051
- // Handle the response based on payment status
1052
- const paymentStatus = (response?.data?.paymentStatus || '').toLowerCase();
1053
- const statusParam = paymentStatus === 'success' ? 'success' : paymentStatus === 'failed' ? 'failed' : 'pending';
1054
-
1055
- const fdDataParam = fdDisplay ? {
1056
- companyName: fdDisplay.name,
1057
- amount: Number(fdDisplay.invested) || 0,
1058
- fdRate: `${fdDisplay.returns}% p.a.`,
1059
- tenure: fdDisplay.maturityDate ? `${fdDisplay.maturityDate}` : '-',
1060
- interestPayout: fdDisplay.interestPayout || 'Yearly',
1061
- } : activeFD ? {
1062
- companyName: activeFD.name,
1063
- amount: Number(activeFD.invested) || 0,
1064
- fdRate: `${activeFD.returns}% p.a.`,
1065
- tenure: activeFD.maturityDate ? `${activeFD.maturityDate}` : '-',
1066
- interestPayout: activeFD.interestPayout || 'Yearly',
1067
- } : undefined;
1068
-
1069
- // Shriram: always navigate to Shriram SDK PaymentStatus, never FDSDK PaymentStatus
1070
- let paymentProviderId = resolveProviderIdFromApp(appData, MahindraProviderId, ShriramProviderId).toLowerCase();
1071
- if (forcedSdkType === 'mahindra') paymentProviderId = MahindraProviderId.toLowerCase();
1072
- if (forcedSdkType === 'shriram') paymentProviderId = ShriramProviderId.toLowerCase();
1073
- const isShriramPayment = paymentProviderId === ShriramProviderId.toLowerCase();
1074
- if (isShriramPayment && appData) {
1075
- if (!initializeExternalSDKFromFDList('shriram')) return;
1076
- let ShriramSDKGlobalDataPayment: any = null;
1077
- if (customerApplications && !isCustomerApplicationsEmpty()) {
1078
- let applicationsData: any[] = [];
1079
- if (Array.isArray(customerApplications)) applicationsData = customerApplications;
1080
- else if (customerApplications?.data && Array.isArray(customerApplications.data)) applicationsData = customerApplications.data;
1081
- else if (customerApplications?.applications && Array.isArray(customerApplications.applications)) applicationsData = customerApplications.applications;
1082
- const completeApplications = applicationsData.filter((app: any) => {
1083
- const s = (app.wf_status || app.status || '').toString().toLowerCase();
1084
- return s === 'completed' || s === 'complete';
1085
- });
1086
- const shriramComplete = completeApplications.find((app: any) => (app.fd_provider_id || app.fdProviderId || app.provider_id || app.providerId || '').toLowerCase() === ShriramProviderId.toLowerCase());
1087
- if (shriramComplete) ShriramSDKGlobalDataPayment = shriramComplete;
1088
- }
1089
- const fdListSelectedDataPayment = fdDisplay ? {
1090
- id: appData.fd_id || appData.fdId || 'active-fd',
1091
- providerId: providerIdFromApp,
1092
- name: fdDisplay.name,
1093
- accountNumber: appData.account_number || appData.accountNumber || '',
1094
- roi: `${fdDisplay.returns}% p.a.`,
1095
- tenure: fdDisplay.maturityDate || '-',
1096
- amount: Number(fdDisplay.invested) || 0,
1097
- maturityDate: fdDisplay.maturityDate || '-',
1098
- status: 'active' as const,
1099
- creditRating: getStaticCreditRatingForFD({
1100
- name: fdDisplay.name,
1101
- providerId: providerIdFromApp,
1102
- }),
1103
- companyName: fdDisplay.name,
1104
- fdRate: `${fdDisplay.returns}% p.a.`,
1105
- interestPayout: fdDisplay.interestPayout || 'Yearly',
1106
- } : null;
1107
- const masterDataPayment = ShriramMasterData;
1108
-
1109
- navigate('ExternalSDK', {
1110
- sdkType: 'shriram',
1111
- providerId: providerIdFromApp,
1112
- fdListSelectedData: fdListSelectedDataPayment,
1113
- masterData: masterDataPayment,
1114
- onboardingIds: onboardingIdsFromApp || onboardingIds,
1115
- initialRouteName: 'PaymentStatus',
1116
- initialPaymentStatusParams: { status: statusParam, transactionId, fdData: fdDataParam },
1117
- shriramSDKGlobalData: ShriramSDKGlobalDataPayment,
1118
- } as any);
1119
- return;
1120
- }
1121
-
1122
- navigate('PaymentStatus', { status: statusParam, transactionId, fdData: fdDataParam } as any);
1123
- return;
1124
-
1125
- } catch (error) {
1126
- // Handle error silently
1127
- const fdDataParam = fdDisplay ? {
1128
- companyName: fdDisplay.name,
1129
- amount: Number(fdDisplay.invested) || 0,
1130
- fdRate: `${fdDisplay.returns}% p.a.`,
1131
- tenure: fdDisplay.maturityDate ? `${fdDisplay.maturityDate}` : '-',
1132
- interestPayout: fdDisplay.interestPayout || 'Yearly',
1133
- } : activeFD ? {
1134
- companyName: activeFD.name,
1135
- amount: Number(activeFD.invested) || 0,
1136
- fdRate: `${activeFD.returns}% p.a.`,
1137
- tenure: activeFD.maturityDate ? `${activeFD.maturityDate}` : '-',
1138
- interestPayout: activeFD.interestPayout || 'Yearly',
1139
- } : undefined;
1140
- let providerIdFromApp = resolveProviderIdFromApp(appData, MahindraProviderId, ShriramProviderId);
1141
- if (forcedSdkType === 'mahindra') providerIdFromApp = MahindraProviderId;
1142
- if (forcedSdkType === 'shriram') providerIdFromApp = ShriramProviderId;
1143
- const paymentProviderIdCatch = providerIdFromApp.toLowerCase();
1144
- const isShriramPaymentCatch = paymentProviderIdCatch === ShriramProviderId.toLowerCase();
1145
- if (isShriramPaymentCatch && appData) {
1146
- if (!initializeExternalSDKFromFDList('shriram')) return;
1147
- let ShriramSDKGlobalDataPayment: any = null;
1148
- if (customerApplications && !isCustomerApplicationsEmpty()) {
1149
- let applicationsData: any[] = [];
1150
- if (Array.isArray(customerApplications)) applicationsData = customerApplications;
1151
- else if (customerApplications?.data && Array.isArray(customerApplications.data)) applicationsData = customerApplications.data;
1152
- else if (customerApplications?.applications && Array.isArray(customerApplications.applications)) applicationsData = customerApplications.applications;
1153
- const completeApplications = applicationsData.filter((app: any) => {
1154
- const s = (app.wf_status || app.status || '').toString().toLowerCase();
1155
- return s === 'completed' || s === 'complete';
1156
- });
1157
- const shriramComplete = completeApplications.find((app: any) => (app.fd_provider_id || app.fdProviderId || app.provider_id || app.providerId || '').toLowerCase() === ShriramProviderId.toLowerCase());
1158
- if (shriramComplete) ShriramSDKGlobalDataPayment = shriramComplete;
1159
- }
1160
- const fdListSelectedDataPayment = fdDisplay ? {
1161
- id: appData.fd_id || appData.fdId || 'active-fd',
1162
- providerId: providerIdFromApp,
1163
- name: fdDisplay.name,
1164
- accountNumber: appData.account_number || appData.accountNumber || '',
1165
- roi: `${fdDisplay.returns}% p.a.`,
1166
- tenure: fdDisplay.maturityDate || '-',
1167
- amount: Number(fdDisplay.invested) || 0,
1168
- maturityDate: fdDisplay.maturityDate || '-',
1169
- status: 'active' as const,
1170
- creditRating: getStaticCreditRatingForFD({
1171
- name: fdDisplay.name,
1172
- providerId: providerIdFromApp,
1173
- }),
1174
- companyName: fdDisplay.name,
1175
- fdRate: `${fdDisplay.returns}% p.a.`,
1176
- interestPayout: fdDisplay.interestPayout || 'Yearly',
1177
- } : null;
1178
- const masterDataPayment = ShriramMasterData;
1179
- navigate('ExternalSDK', {
1180
- sdkType: 'shriram',
1181
- providerId: providerIdFromApp,
1182
- fdListSelectedData: fdListSelectedDataPayment,
1183
- masterData: masterDataPayment,
1184
- onboardingIds: onboardingIdsFromApp || onboardingIds,
1185
- initialRouteName: 'PaymentStatus',
1186
- initialPaymentStatusParams: { status: 'pending', transactionId, fdData: fdDataParam },
1187
- shriramSDKGlobalData: ShriramSDKGlobalDataPayment,
1188
- } as any);
1189
- return;
1190
- }
1191
- navigate('PaymentStatus', { status: 'pending', transactionId, fdData: fdDataParam } as any);
1192
- return;
1193
- }
1194
- }
1195
-
1196
- // Determine provider and navigate to appropriate SDK
1197
- if (appData) {
1198
- let providerIdFromApp = resolveProviderIdFromApp(appData, MahindraProviderId, ShriramProviderId);
1199
- if (forcedSdkType === 'mahindra') providerIdFromApp = MahindraProviderId;
1200
- if (forcedSdkType === 'shriram') providerIdFromApp = ShriramProviderId;
1201
- const providerIdLower = (providerIdFromApp || '').toLowerCase();
1202
- const isMahindra = providerIdLower === MahindraProviderId.toLowerCase();
1203
- const isShriram = providerIdLower === ShriramProviderId.toLowerCase();
1204
-
1205
- // Check for complete applications and create global data constants
1206
- let ShriramSDKGlobalData: any = null;
1207
- let MahindraSDKGlobalData: any = null;
1208
-
1209
- if (customerApplications && !isCustomerApplicationsEmpty()) {
1210
- let applicationsData: any[] = [];
1211
- if (Array.isArray(customerApplications)) {
1212
- applicationsData = customerApplications;
1213
- } else if (customerApplications.data && Array.isArray(customerApplications.data)) {
1214
- applicationsData = customerApplications.data;
1215
- } else if (customerApplications.applications && Array.isArray(customerApplications.applications)) {
1216
- applicationsData = customerApplications.applications;
1217
- }
1218
-
1219
- // Find complete applications
1220
- const completeApplications = applicationsData.filter((app: any) => {
1221
- const status = (app.wf_status || app.status || '').toString().toLowerCase();
1222
- return status === 'completed' || status === 'complete';
1223
- });
1224
-
1225
- // Separate by provider
1226
- completeApplications.forEach((app: any) => {
1227
- const appProviderId = (app.fd_provider_id || app.fdProviderId || app.provider_id || app.providerId || '').toLowerCase();
1228
- if (appProviderId === ShriramProviderId.toLowerCase()) {
1229
- ShriramSDKGlobalData = app;
1230
- } else if (appProviderId === MahindraProviderId.toLowerCase()) {
1231
- MahindraSDKGlobalData = app;
1232
- }
1233
- });
1234
- }
1235
-
1236
- // Helper function to map workflow state to screen name
1237
- const getScreenNameFromState = (state: number, isMahindraSDK: boolean, isShriramSDK: boolean): string => {
1238
- if (isMahindraSDK) {
1239
- if (state === MAHINDRA_WORKFLOW_STATES.KYC_SUCCESS) return 'KYCSuccess';
1240
- if (state === MAHINDRA_WORKFLOW_STATES.KYC) return 'ReviewKYC';
1241
- if (state === MAHINDRA_WORKFLOW_STATES.OCCUPATION) return 'Employee';
1242
- if (state === MAHINDRA_WORKFLOW_STATES.NOMINEE) return 'NomineeDetail';
1243
- if (state === MAHINDRA_WORKFLOW_STATES.BANK_DETAILS) return 'BankDetail';
1244
- if (state === MAHINDRA_WORKFLOW_STATES.AADHAAR_VERIFICATION) return 'AadhaarVerification';
1245
- if (state === MAHINDRA_WORKFLOW_STATES.PAYMENT) return 'PayNow';
1246
- if (state === MAHINDRA_WORKFLOW_STATES.CREATE_FD) return 'FDCalculator';
1247
- } else if (isShriramSDK) {
1248
- if (state === SHRIRAM_WORKFLOW_STATES.KYC) return 'ReviewKYC';
1249
- if (state === SHRIRAM_WORKFLOW_STATES.OCCUPATION) return 'Employee';
1250
- if (state === SHRIRAM_WORKFLOW_STATES.NOMINEE) return 'NomineeDetail';
1251
- if (state === SHRIRAM_WORKFLOW_STATES.BANK_DETAILS) return 'BankDetail';
1252
- if (state === SHRIRAM_WORKFLOW_STATES.PAYMENT) return 'PayNow';
1253
- // State 411 (CREATE_FD): user has completed up to FD creation; next step is payment → open PayNow
1254
- if (state === SHRIRAM_WORKFLOW_STATES.CREATE_FD) return 'PayNow';
1255
- }
1256
- // Default fallback
1257
- return 'FDCalculator';
1258
- };
1259
-
1260
- // Align pending flow with new-FD reliability: fetch provider-scoped master data right before navigation.
1261
- let providerMasterData = isMahindra ? MahindraMasterData : isShriram ? ShriramMasterData : undefined;
1262
- try {
1263
- if (isMahindra) {
1264
- const refreshed = await refetchMahindraMasterData();
1265
- providerMasterData = refreshed?.data ?? providerMasterData;
1266
- } else if (isShriram) {
1267
- const refreshed = await refetchShriramMasterData();
1268
- providerMasterData = refreshed?.data ?? providerMasterData;
1269
- }
1270
- } catch {
1271
- // keep previously cached provider-specific master data
1272
- }
1273
-
1274
- const masterDataToPass = providerMasterData || (masterData || masterDataContext);
1275
-
1276
- // Get onboardingIds - use the one from appData if available, otherwise from Redux store
1277
- const onboardingIdsToPass = onboardingIdsFromApp || onboardingIds;
1278
-
1279
- // Get fdListSelectedData (already set in Redux, but we need it for navigation)
1280
- // Use fdDisplay (derived from the tapped app) not activeFD (always first active app)
1281
- const fdListSelectedDataToPass = fdDisplay ? {
1282
- id: appData.fd_id || appData.fdId || 'active-fd',
1283
- providerId: providerIdFromApp,
1284
- name: fdDisplay.name,
1285
- accountNumber: appData.account_number || appData.accountNumber || '',
1286
- roi: `${fdDisplay.returns}% p.a.`,
1287
- tenure: appData.tenure_in_months
1288
- ? `${appData.tenure_in_months} Months`
1289
- : '-',
1290
- amount: Number(fdDisplay.invested) || 0,
1291
- maturityDate: fdDisplay.maturityDate || '-',
1292
- status: 'active' as const,
1293
- creditRating: getStaticCreditRatingForFD({
1294
- name: fdDisplay.name,
1295
- providerId: providerIdFromApp,
1296
- }),
1297
- companyName: fdDisplay.name,
1298
- fdRate: `${fdDisplay.returns}% p.a.`,
1299
- interestPayout: fdDisplay.interestPayout || '',
1300
- } : null;
1301
-
1302
- // Navigate to appropriate SDK if provider is detected
1303
- if (isMahindra || isShriram) {
1304
- const screenName = getScreenNameFromState(currentState, isMahindra, isShriram);
1305
- const sdkType = isMahindra ? 'mahindra' : 'shriram';
1306
- if (!initializeExternalSDKFromFDList(sdkType)) return;
1307
- // #region agent log
1308
- fetch('http://127.0.0.1:7653/ingest/94b18a39-caf3-48fd-98f5-bf1838d9491a', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Debug-Session-Id': '04da7c' }, body: JSON.stringify({ sessionId: '04da7c', runId: 'pending-masterdata-trace', hypothesisId: 'H2', location: 'fdsdk/screens/FDList.tsx:1315', message: 'Pending continue pre-navigation payload', data: { forcedSdkType: forcedSdkType || null, resolvedSdkType: sdkType, providerIdFromApp: providerIdFromApp || null, onboardingProviderId: onboardingIdsToPass?.providerId || null, fdListSelectedProviderId: fdListSelectedDataToPass?.providerId || null, usingMahindraMasterData: !!(sdkType === 'mahindra' && masterDataToPass), usingShriramMasterData: !!(sdkType === 'shriram' && masterDataToPass), masterDataHasDataKey: !!masterDataToPass?.data }, timestamp: Date.now() }) }).catch(() => { });
1309
- // #endregion agent log
1310
-
1311
- navigate('ExternalSDK', {
1312
- sdkType,
1313
- providerId: providerIdFromApp,
1314
- fdListSelectedData: fdListSelectedDataToPass,
1315
- masterData: masterDataToPass,
1316
- onboardingIds: onboardingIdsToPass,
1317
- initialRouteName: screenName, // Pass the screen name to navigate to
1318
- shriramSDKGlobalData: ShriramSDKGlobalData, // Pass complete Shriram application data
1319
- mahindraSDKGlobalData: MahindraSDKGlobalData, // Pass complete Mahindra application data
1320
- // Let Mahindra SDK fetch details itself when launched from host (Mahindra only)
1321
- ...(isMahindra ? { forceFetchCustomerDetails: true } : {}),
1322
- });
1323
- return;
1324
- }
1325
- }
1326
-
1327
- // Fallback: Use workflow navigation based on current state (non-payment states or no transactionId)
1328
- if (currentState && Object.values(WORKFLOW_STATES).includes(currentState)) {
1329
- handleWorkflowNavigation({
1330
- workflowState: currentState,
1331
- investmentData: undefined,
1332
- customerData: undefined,
1333
- appData: undefined,
1334
- fdData: undefined,
1335
- completedApplications: [],
1336
- transactionId,
1337
- });
1338
- } else {
1339
- navigate('Employee');
1340
- }
1341
- };
1342
-
1343
- // Derive identifiers for workflow termination from applications API
1344
- // Derive Active FD card data from customer applications response
1345
- const activeFD = React.useMemo(() => {
1346
- try {
1347
- if (!customerApplications || isCustomerApplicationsEmpty()) {
1348
- return {
1349
- name: 'Mahindra Finance Ltd',
1350
- invested: 0,
1351
- value: 0,
1352
- returns: 0,
1353
- maturityDate: '--',
1354
- tenure_in_months: 0,
1355
- interestPayout: 'Yearly',
1356
- logoType: 'mahindra' as const,
1357
- };
1358
- }
1359
-
1360
- // Normalize list
1361
- let applicationsData: any[] = [];
1362
- if (Array.isArray(customerApplications)) {
1363
- applicationsData = customerApplications;
1364
- } else if (customerApplications.data && Array.isArray(customerApplications.data)) {
1365
- applicationsData = customerApplications.data;
1366
- } else if (customerApplications.applications && Array.isArray(customerApplications.applications)) {
1367
- applicationsData = customerApplications.applications;
1368
- }
1369
-
1370
- if (applicationsData.length === 0) {
1371
- return {
1372
- name: 'Mahindra Finance Ltd',
1373
- invested: 0,
1374
- value: 0,
1375
- returns: 0,
1376
- maturityDate: '--',
1377
- tenure_in_months: 0,
1378
- interestPayout: 'Yearly',
1379
- logoType: 'mahindra' as const,
1380
- };
1381
- }
1382
-
1383
- // Pick an active application if available, otherwise first
1384
- const activeApp = applicationsData.find((a: any) => {
1385
- const s = (a.wf_status || a.status || '').toString();
1386
- return s === 'Active';
1387
- }) || applicationsData[0];
1388
-
1389
- const invested = Number(
1390
- activeApp.investment_amount ?? activeApp.amount ?? activeApp.investmentAmount ?? 0
1391
- );
1392
- const rate = Number(
1393
- activeApp.interest_rate ?? activeApp.interestRate ?? 0
1394
- );
1395
-
1396
- // Prefer a provided current value, then maturity amount
1397
- const value = Number(
1398
- activeApp.current_value ?? activeApp.currentValue ?? activeApp.maturity_amount ?? activeApp.maturityAmount ?? 0
1399
- );
1400
-
1401
- const returns = rate || 0; // display interest rate as returns %
1402
-
1403
- // Format maturity date to 'Mon YYYY'
1404
- const maturityDateStr: string = activeApp.maturity_date || activeApp.maturityDate || '';
1405
- let maturityDate = '--';
1406
- if (maturityDateStr) {
1407
- const d = new Date(maturityDateStr);
1408
- if (!isNaN(d.getTime())) {
1409
- const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
1410
- maturityDate = `${months[d.getMonth()]} ${d.getFullYear()}`;
1411
- }
1412
- }
1413
-
1414
- // Get interest payout term from activeApp
1415
- const interestPayout = activeApp.interest_payout_term || activeApp.interestPayoutTerm || activeApp.payout_term || 'Yearly';
1416
-
1417
- const providerName = activeApp.fd_provider_name || activeApp.providerName || 'Mahindra Finance Ltd';
1418
- const logoType = getLogoTypeForFD({ name: providerName, providerId: activeApp.fd_provider_id || activeApp.fdProviderId || '' });
1419
-
1420
- return {
1421
- name: providerName,
1422
- invested,
1423
- value,
1424
- returns,
1425
- maturityDate,
1426
- tenure_in_months: activeApp.tenure_in_months ?? 0,
1427
- interestPayout,
1428
- logoType,
1429
- };
1430
- } catch (e) {
1431
- return {
1432
- name: 'Mahindra Finance Ltd',
1433
- invested: 0,
1434
- value: 0,
1435
- returns: 0,
1436
- maturityDate: '--',
1437
- tenure_in_months: 0,
1438
- interestPayout: 'Yearly',
1439
- logoType: 'mahindra' as const,
1440
- };
1441
- }
1442
- }, [customerApplications]);
1443
-
1444
- // All pending/active FDs (one per application with Active or pending/in_progress) – for multiple providers
1445
- const activeFDs = React.useMemo(() => {
1446
- try {
1447
- if (!customerApplications || isCustomerApplicationsEmpty()) return [];
1448
- let applicationsData: any[] = [];
1449
- if (Array.isArray(customerApplications)) {
1450
- applicationsData = customerApplications;
1451
- } else if (customerApplications.data && Array.isArray(customerApplications.data)) {
1452
- applicationsData = customerApplications.data;
1453
- } else if (customerApplications.applications && Array.isArray(customerApplications.applications)) {
1454
- applicationsData = customerApplications.applications;
1455
- }
1456
- const pendingActive = applicationsData.filter((a: any) => {
1457
- const s = (a.wf_status || a.status || '').toString().toLowerCase();
1458
- return PENDING_ACTIVE_STATUSES.includes(s);
1459
- });
1460
- return pendingActive.map((app: any) => ({
1461
- ...appToPendingFDData(app),
1462
- app,
1463
- }));
1464
- } catch (e) {
1465
- return [];
1466
- }
1467
- }, [customerApplications]);
1468
-
1469
- // Normalized applications list for provider-scoped lookups
1470
- const applicationsDataNormalized = React.useMemo(() => {
1471
- if (!customerApplications || isCustomerApplicationsEmpty()) return [];
1472
- if (Array.isArray(customerApplications)) return customerApplications;
1473
- if (customerApplications.data && Array.isArray(customerApplications.data)) return customerApplications.data;
1474
- if (customerApplications.applications && Array.isArray(customerApplications.applications)) return customerApplications.applications;
1475
- return [];
1476
- }, [customerApplications]);
1477
-
1478
- // Helper function to check if there's any active/pending FD
1479
- const hasActiveFD = React.useMemo(() => activeFDs.length > 0, [activeFDs]);
1480
-
1481
- // Pending FD data for bottom sheet: from the app we're showing (provider they tapped), or fallback to first active
1482
- const pendingFDDataForSheet = React.useMemo(() => {
1483
- if (pendingContextApp) return appToPendingFDData(pendingContextApp);
1484
- if (activeFDs.length > 0) return activeFDs[0]; // fallback: first card data (no app ref needed for display)
1485
- return activeFD;
1486
- }, [pendingContextApp, activeFDs, activeFD]);
1487
-
1488
- // Build FD options list from interest-rate API (show all FDs)
1489
- const fdList = React.useMemo(() => {
1490
- if (interestRates && (interestRates.data?.sdrScheme?.length > 0 || interestRates.data?.fdrScheme?.length > 0)) {
1491
- const sdr = Array.isArray(interestRates.data?.sdrScheme) ? interestRates.data.sdrScheme : [];
1492
- const fdr = Array.isArray(interestRates.data?.fdrScheme) ? interestRates.data.fdrScheme : [];
1493
- const combined = [...sdr, ...fdr];
1494
-
1495
- // Building FD options from rates
1496
-
1497
- return combined.map((rate: any, index: number) => ({
1498
- id: `${rate.providerId || 'provider'}-${rate.perdMonth || index}`,
1499
- providerId: rate.providerId || `provider-${index}`,
1500
- name: rate.providerName || '', // Use actual provider name from API
1501
- accountNumber: `FD${String(index + 1).padStart(9, '0')}`,
1502
- roi: `${Number(rate.rate ?? rate.interestRate ?? 0).toFixed(2)}% p.a.`,
1503
- tenure: `${rate.perdMonth} Months`,
1504
- amount: 5000,
1505
- maturityDate: '2025-12-31',
1506
- status: 'active' as const,
1507
- creditRating: getStaticCreditRatingForFD({
1508
- name: rate.providerName || '',
1509
- providerId: rate.providerId || `provider-${index}`,
1510
- }),
1511
- interestPayout: rate.interestPayout || rate.interest_payout_term || rate.payout_term || 'Yearly'
1512
- }));
1513
- }
1514
-
1515
- // If rates are not available yet, show empty list
1516
- return [];
1517
- }, [interestRates]);
1518
-
1519
-
1520
- // Get filtered FD list based on active tab
1521
- const filteredFDList = React.useMemo(() => {
1522
- if (!fdList || fdList.length === 0) return [];
1523
-
1524
- // Always create a new filtered array
1525
- const filtered = filterFDsByTenure([...fdList], activeTab); // spread to ensure new array
1526
-
1527
- return filtered;
1528
- }, [fdList, activeTab]);
1529
-
1530
-
1531
- const tabs = [FD_STRINGS.ALL_FDS_TAB, FD_STRINGS.LESS_THAN_1Y_TAB, FD_STRINGS.LESS_THAN_1_3Y_TAB, FD_STRINGS.LESS_THAN_3_5Y_TAB, FD_STRINGS.GREATER_THAN_5Y_TAB];
1532
- const poweredByLogo = (getAppData()?.poweredByLogo || '').trim();
1533
-
1534
- if (!isAllDataReady) {
1535
- return (
1536
- <SafeAreaWrapper
1537
- style={customStyles.container}
1538
- includeTop={false}
1539
- statusBarColor={colors.headerBg}
1540
- statusBarStyle="light-content"
1541
- >
1542
- <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
1543
- <ActivityIndicator size="large" color={colors.primary} />
1544
- <Text style={{ marginTop: 12, color: colors.textSecondary }}>{FD_STRINGS.LOADING_FD_DATA}</Text>
1545
- </View>
1546
- </SafeAreaWrapper>
1547
- );
1548
- }
1549
-
1550
- return (
1551
- <SafeAreaWrapper
1552
- style={customStyles.container}
1553
- includeTop={false}
1554
- statusBarColor={colors.headerBg}
1555
- statusBarStyle="light-content"
1556
- >
1557
- {/* Custom FDList Header - same structure as Shriram FDList */}
1558
- <View style={styles.customHeader}>
1559
- <TouchableOpacity
1560
- style={styles.backButton}
1561
- onPress={onGoBack}
1562
- activeOpacity={0.7}
1563
- >
1564
- <Image
1565
- source={{ uri: base64Images.backArrow }}
1566
- style={styles.backIcon}
1567
- resizeMode="contain"
1568
- />
1569
- </TouchableOpacity>
1570
-
1571
- <View style={styles.headerContent}>
1572
- <Text style={styles.headerTitle}>{FD_STRINGS.CORPORATE_FDS_TITLE}</Text>
1573
- {poweredByLogo ? (
1574
- <View style={styles.poweredByContainer}>
1575
- <Text style={styles.poweredByText}>Powered by</Text>
1576
- <Image
1577
- source={{ uri: poweredByLogo }}
1578
- style={styles.simplifyLogo}
1579
- resizeMode="contain"
1580
- />
1581
- </View>
1582
- ) : null}
1583
- </View>
1584
- </View>
1585
-
1586
- <ScrollView style={styles.content}>
1587
-
1588
- {/* Active FDs Section - One card per pending/active FD (e.g. one Mahindra, one Shriram) */}
1589
- {hasActiveFD && (
1590
- <View style={styles.section}>
1591
- <Text style={styles.sectionTitle}>{FD_STRINGS.ACTIVE_FDS_SECTION}</Text>
1592
- {activeFDs.map((item, index) => (
1593
- <TouchableOpacity
1594
- key={item.app?.application_id || item.app?.applicationId || index}
1595
- activeOpacity={0.8}
1596
- onPress={() => handlePendingFDContinue(item.app, item.logoType)}
1597
- style={index > 0 ? styles.activeFDCardSpacer : undefined}
1598
- >
1599
- <ActiveFDCard
1600
- name={item.name}
1601
- invested={item.invested}
1602
- value={item.value}
1603
- returns={item.returns}
1604
- maturityDate={item.maturityDate}
1605
- logoType={item.logoType}
1606
- />
1607
- </TouchableOpacity>
1608
- ))}
1609
- </View>
1610
- )}
1611
-
1612
- {/* Tabs */}
1613
- <View style={styles.tabsContainer}>
1614
- <View style={styles.tabsRow}>
1615
- {tabs.map((tab) => (
1616
- <TouchableOpacity
1617
- key={tab}
1618
- style={[
1619
- styles.tab,
1620
- activeTab === tab && styles.activeTab
1621
- ]}
1622
- onPress={() => {
1623
- // Other tabs filter the data
1624
- setActiveTab(tab);
1625
- }}
1626
- >
1627
- <Text style={[
1628
- styles.tabText,
1629
- activeTab === tab && styles.activeTabText
1630
- ]}>
1631
- {tab}
1632
- </Text>
1633
- </TouchableOpacity>
1634
- ))}
1635
- </View>
1636
- </View>
1637
-
1638
- {/* Filter Status removed as requested */}
1639
-
1640
- {/* FD List */}
1641
- <View style={styles.fdOptionsContainer}>
1642
- {filteredFDList.length > 0 ? (
1643
- filteredFDList.map((fd: FDItem) => (
1644
- <FDCard
1645
- key={fd.id}
1646
- id={fd.id}
1647
- name={fd.name}
1648
- accountNumber={fd.accountNumber}
1649
- roi={fd.roi}
1650
- tenure={fd.tenure}
1651
- amount={fd.amount}
1652
- maturityDate={fd.maturityDate}
1653
- status={fd.status as 'active' | 'matured' | 'pending'}
1654
- creditRating={fd.creditRating}
1655
- logoType={getLogoTypeForFD(fd)}
1656
- onPress={(fdId) => {
1657
-
1658
-
1659
- // Log the selection
1660
- onSelectFD?.(fdId);
1661
-
1662
- // Store the selected FD data locally
1663
- setSelectedFD(fd);
1664
-
1665
- // Store the selected FD data in Redux for PayNow screen
1666
- const fdListSelectedData = {
1667
- id: fd.id,
1668
- providerId: fd.providerId,
1669
- name: fd.name,
1670
- accountNumber: fd.accountNumber,
1671
- roi: fd.roi,
1672
- tenure: fd.tenure,
1673
- amount: fd.amount,
1674
- maturityDate: fd.maturityDate,
1675
- status: fd.status as 'active' | 'matured' | 'pending',
1676
- creditRating: fd.creditRating,
1677
- // Additional fields for PayNow display
1678
- companyName: fd.name,
1679
- fdRate: fd.roi,
1680
- interestPayout: fd.interestPayout || 'Yearly', // Use FD's interest payout or default
1681
- };
1682
-
1683
- dispatch(setFDListSelected(fdListSelectedData));
1684
-
1685
- // 1st Place: Set onboardingIds when customer taps on any FD from FDList
1686
- // Start with existing onboardingIds, but prefer the freshly computed ones for this tap.
1687
- let onboardingIdsForNav: any = onboardingIds;
1688
- try {
1689
- let idsToSet: any = { providerId: fd.providerId };
1690
-
1691
- // If we already have customer applications, enrich onboardingIds
1692
- try {
1693
- if (customerApplications && !isCustomerApplicationsEmpty()) {
1694
- // Normalize list
1695
- let applicationsData: any[] = [];
1696
- if (Array.isArray(customerApplications)) {
1697
- applicationsData = customerApplications;
1698
- } else if (customerApplications.data && Array.isArray(customerApplications.data)) {
1699
- applicationsData = customerApplications.data;
1700
- } else if (customerApplications.applications && Array.isArray(customerApplications.applications)) {
1701
- applicationsData = customerApplications.applications;
1702
- }
1703
-
1704
- if (applicationsData.length > 0) {
1705
- // Prefer applications that belong to the same provider as the tapped FD
1706
- const fdProviderIdLower = (fd.providerId || '').toLowerCase();
1707
- const appsForProvider = applicationsData.filter((a: any) => {
1708
- const appProviderId =
1709
- (a.fd_provider_id ||
1710
- a.fdProviderId ||
1711
- a.provider_id ||
1712
- a.providerId ||
1713
- '').toLowerCase();
1714
- return appProviderId === fdProviderIdLower;
1715
- });
1716
-
1717
- const scopedApplications =
1718
- appsForProvider.length > 0 ? appsForProvider : applicationsData;
1719
-
1720
- // Within the scoped list, still prefer the Active application
1721
- const appData = scopedApplications.find((a: any) => {
1722
- const s = (a.wf_status || a.status || '').toString();
1723
- return s === 'Active';
1724
- }) || scopedApplications[0];
1725
-
1726
- idsToSet = {
1727
- workflowInstanceId: appData.workflow_instance_id || appData.workflowInstanceId,
1728
- applicationId: appData.application_id || appData.applicationId,
1729
- entityid: appData.entity_id || appData.entityId,
1730
- customerId: appData.customer_id || appData.customerId,
1731
- fdId: appData.fd_id || appData.fdId,
1732
- currentState: appData.current_state || appData.currentState,
1733
- currentTask: appData.current_task || appData.currentTask,
1734
- // Check both snake_case (fd_provider_id) and camelCase (fdProviderId) variants
1735
- providerId: fd.providerId,
1736
- wfStatus: appData.wf_status || appData.wfStatus,
1737
- };
1738
- }
1739
- }
1740
- } catch (innerError) {
1741
- // If enrichment fails, fall back to providerId only
1742
- }
1743
-
1744
- dispatch(setOnboardingIds(idsToSet));
1745
- onboardingIdsForNav = idsToSet;
1746
- } catch (e) {
1747
- }
1748
-
1749
- // Check if this provider has a pending/active FD – show sheet only for that provider
1750
- const pendingAppForProvider = getPendingOrActiveAppForProvider(applicationsDataNormalized, fd.providerId || '');
1751
- if (pendingAppForProvider) {
1752
- setPendingContextApp(pendingAppForProvider);
1753
- setShowPendingFDBottomSheet(true);
1754
- } else {
1755
- // If no active FD, navigate to appropriate SDK FD calculator
1756
- const fdProviderId = (fd.providerId || '').toLowerCase();
1757
- const isMahindra = fdProviderId === MahindraProviderId.toLowerCase();
1758
- const isShriram = fdProviderId === ShriramProviderId.toLowerCase();
1759
-
1760
- // Check for complete applications and create global data constants
1761
- let ShriramSDKGlobalData: any = null;
1762
- let MahindraSDKGlobalData: any = null;
1763
-
1764
- if (customerApplications && !isCustomerApplicationsEmpty()) {
1765
- let applicationsData: any[] = [];
1766
- if (Array.isArray(customerApplications)) {
1767
- applicationsData = customerApplications;
1768
- } else if (customerApplications.data && Array.isArray(customerApplications.data)) {
1769
- applicationsData = customerApplications.data;
1770
- } else if (customerApplications.applications && Array.isArray(customerApplications.applications)) {
1771
- applicationsData = customerApplications.applications;
1772
- }
1773
-
1774
- // Find complete applications
1775
- const completeApplications = applicationsData.filter((app: any) => {
1776
- const status = (app.wf_status || app.status || '').toString().toLowerCase();
1777
- return status === 'completed' || status === 'complete';
1778
- });
1779
-
1780
- // Separate by provider
1781
- completeApplications.forEach((app: any) => {
1782
- const appProviderId = (app.fd_provider_id || app.fdProviderId || app.provider_id || app.providerId || '').toLowerCase();
1783
- if (appProviderId === ShriramProviderId.toLowerCase()) {
1784
- ShriramSDKGlobalData = app;
1785
- } else if (appProviderId === MahindraProviderId.toLowerCase()) {
1786
- MahindraSDKGlobalData = app;
1787
- }
1788
- });
1789
- }
1790
-
1791
- if (isShriram) {
1792
- navigate('ExternalSDK', {
1793
- sdkType: 'shriram',
1794
- providerId: fd.providerId,
1795
- fdListSelectedData: fdListSelectedData,
1796
- masterData: ShriramMasterData,
1797
- // Use the freshest onboardingIds (from this tap if available)
1798
- onboardingIds: onboardingIdsForNav,
1799
- shriramSDKGlobalData: ShriramSDKGlobalData, // Pass complete Shriram application data
1800
- mahindraSDKGlobalData: MahindraSDKGlobalData, // Pass complete Mahindra application data
1801
- });
1802
- } else if (isMahindra) {
1803
- // Navigate to Mahindra SDK FD calculator
1804
- navigate('ExternalSDK', {
1805
- sdkType: 'mahindra',
1806
- providerId: fd.providerId,
1807
- fdListSelectedData: fdListSelectedData,
1808
- masterData: MahindraMasterData,
1809
- // Use the freshest onboardingIds (from this tap if available)
1810
- onboardingIds: onboardingIdsForNav,
1811
- shriramSDKGlobalData: ShriramSDKGlobalData, // Pass complete Shriram application data
1812
- mahindraSDKGlobalData: MahindraSDKGlobalData, // Pass complete Mahindra application data
1813
- });
1814
- } else {
1815
- // Fallback to default FD calculator
1816
- onNavigateToFDCalculator?.(fd, masterData);
1817
- }
1818
- }
1819
- }}
1820
- // onPress={(fdId) => {
1821
- // const fdProviderId = (fd.providerId || '').toLowerCase();
1822
- // const isMahindra = fdProviderId === MahindraProviderId.toLowerCase();
1823
- // const isShriram = fdProviderId === ShriramProviderId.toLowerCase();
1824
-
1825
- // // Construct FDListSelectedData object
1826
- // const fdListSelectedData = {
1827
- // id: fd.id,
1828
- // providerId: fd.providerId,
1829
- // name: fd.name,
1830
- // accountNumber: fd.accountNumber,
1831
- // roi: fd.roi,
1832
- // tenure: fd.tenure,
1833
- // amount: fd.amount,
1834
- // maturityDate: fd.maturityDate,
1835
- // status: fd.status as 'active' | 'matured' | 'pending',
1836
- // creditRating: fd.creditRating,
1837
- // // Additional fields for PayNow display
1838
- // companyName: fd.name,
1839
- // fdRate: fd.roi,
1840
- // interestPayout: fd.interestPayout || 'Yearly', // Use FD's interest payout or default
1841
- // };
1842
-
1843
- // console.log('[FDList][onPress] ===== FD Tapped - Data Logs =====');
1844
- // console.log('[FDList][onPress] FDListSelectedData:', fdListSelectedData);
1845
- // console.log('[FDList][onPress] OnboardingIds:', onboardingIds);
1846
-
1847
- // if (isMahindra) {
1848
- // console.log('[FDList][onPress] Mahindra MasterData:', MahindraMasterData);
1849
- // } else if (isShriram) {
1850
- // console.log('[FDList][onPress] Shriram MasterData:', ShriramMasterData);
1851
- // } else {
1852
- // console.log('[FDList][onPress] MasterData (fallback):', masterData || masterDataContext);
1853
- // }
1854
-
1855
- // console.log('[FDList][onPress] ====================================');
1856
- // }}
1857
- customStyles={customStyles}
1858
- />
1859
- ))
1860
- ) : (
1861
- <View style={styles.noDataContainer}>
1862
- <Text style={styles.noDataText}>
1863
- {isLoadingRates || isLoadingApplications ? FD_STRINGS.LOADING_FD_DATA :
1864
- interestRatesError || customerApplicationsError ? FD_STRINGS.FAILED_TO_LOAD_FD_DATA :
1865
- fdList.length === 0 ? (!isCustomerApplicationsEmpty() ? FD_STRINGS.NO_CUSTOMER_FDS_FOUND : FD_STRINGS.NO_FD_OPTIONS_AVAILABLE) :
1866
- `${FD_STRINGS.NO_FDS_FOUND_FOR_TENURE} "${activeTab}" tenure range.`}
1867
- </Text>
1868
-
1869
- </View>
1870
- )}
1871
- </View>
1872
- </ScrollView>
1873
-
1874
- {/* Pending FD Bottom Sheet - shows pending/active FD for the provider they tapped */}
1875
- <PendingFDBottomSheet
1876
- visible={showPendingFDBottomSheet}
1877
- onClose={() => {
1878
- setShowPendingFDBottomSheet(false);
1879
- setPendingContextApp(null);
1880
- }}
1881
- pendingFDData={pendingFDDataForSheet}
1882
- isBookingNewLoading={isTerminatingWorkflow}
1883
- onContinue={async () => {
1884
- setShowPendingFDBottomSheet(false);
1885
- const forcedFromSelected = getSdkTypeFromProviderId(
1886
- selectedFD?.providerId || '',
1887
- MahindraProviderId,
1888
- ShriramProviderId
1889
- ) || undefined;
1890
- await handlePendingFDContinue(undefined, forcedFromSelected);
1891
- }}
1892
- onBookNew={async () => {
1893
- try {
1894
- // Terminate only the workflow for the provider we're showing (the one they tapped)
1895
- const appToTerminate = pendingContextApp || (activeFDs.length > 0 ? activeFDs[0].app : null);
1896
- const providerIdForTerminate = appToTerminate
1897
- ? (appToTerminate.fd_provider_id || appToTerminate.fdProviderId || appToTerminate.provider_id || appToTerminate.providerId)
1898
- : (selectedFD?.providerId || inferredProviderId);
1899
- const requestParams = {
1900
- providerId: providerIdForTerminate,
1901
- workflowInstanceId: appToTerminate?.workflow_instance_id || appToTerminate?.workflowInstanceId || terminateIdentifiers.workflowInstanceId || 'wf-instance-unknown',
1902
- entityId: appToTerminate?.entity_id || appToTerminate?.entityId || terminateIdentifiers.entityId || 'entity-unknown',
1903
- applicationId: appToTerminate?.application_id || appToTerminate?.applicationId || terminateIdentifiers.applicationId || 'application-unknown',
1904
- } as const;
1905
-
1906
- // Call workflow terminate API (only for this provider's workflow)
1907
- await terminateWorkflow(requestParams);
1908
-
1909
- // 2nd Place: Set onboardingIds when user taps "I want to create new"
1910
- try {
1911
- const providerIdToSet = selectedFD?.providerId || inferredProviderId || getProviderIdFromInterestRates();
1912
- if (providerIdToSet) {
1913
- const idsToSet = { providerId: providerIdToSet };
1914
- dispatch(setOnboardingIds(idsToSet));
1915
- } else {
1916
- }
1917
- } catch (e) {
1918
- }
1919
-
1920
- // Dismiss bottom sheet
1921
- setShowPendingFDBottomSheet(false);
1922
-
1923
- // Navigate to provider-specific SDK (Mahindra/Shriram) just like new FD tap
1924
- const providerIdToUse = (selectedFD?.providerId || inferredProviderId || getProviderIdFromInterestRates() || '').toLowerCase();
1925
- const isMahindra = providerIdToUse === MahindraProviderId.toLowerCase();
1926
- const isShriram = providerIdToUse === ShriramProviderId.toLowerCase();
1927
-
1928
- // Build fdListSelectedData from selectedFD when available (best effort)
1929
- const fdListSelectedDataToPass = selectedFD ? {
1930
- id: selectedFD.id || 'new-fd',
1931
- providerId: selectedFD.providerId,
1932
- name: selectedFD.name,
1933
- accountNumber: selectedFD.accountNumber,
1934
- roi: selectedFD.roi,
1935
- tenure: selectedFD.tenure,
1936
- amount: selectedFD.amount,
1937
- maturityDate: selectedFD.maturityDate,
1938
- status: selectedFD.status as 'active' | 'matured' | 'pending' | undefined,
1939
- creditRating: getStaticCreditRatingForFD({
1940
- name: selectedFD.name,
1941
- providerId: selectedFD.providerId,
1942
- }),
1943
- companyName: selectedFD.name,
1944
- fdRate: selectedFD.roi,
1945
- interestPayout: selectedFD.interestPayout || 'Yearly',
1946
- } : undefined;
1947
-
1948
- const masterDataToPass = isMahindra
1949
- ? MahindraMasterData
1950
- : isShriram
1951
- ? ShriramMasterData
1952
- : (masterData || masterDataContext);
1953
-
1954
- // Recompute complete application global data constants (same as tap flow)
1955
- let ShriramSDKGlobalData: any = null;
1956
- let MahindraSDKGlobalData: any = null;
1957
- try {
1958
- if (customerApplications && !isCustomerApplicationsEmpty()) {
1959
- let applicationsData: any[] = [];
1960
- if (Array.isArray(customerApplications)) {
1961
- applicationsData = customerApplications;
1962
- } else if (customerApplications.data && Array.isArray(customerApplications.data)) {
1963
- applicationsData = customerApplications.data;
1964
- } else if (customerApplications.applications && Array.isArray(customerApplications.applications)) {
1965
- applicationsData = customerApplications.applications;
1966
- }
1967
-
1968
- const completeApplications = applicationsData.filter((app: any) => {
1969
- const status = (app.wf_status || app.status || '').toString().toLowerCase();
1970
- return status === 'completed' || status === 'complete';
1971
- });
1972
-
1973
- completeApplications.forEach((app: any) => {
1974
- const appProviderId = (app.fd_provider_id || app.fdProviderId || app.provider_id || app.providerId || '').toLowerCase();
1975
- if (appProviderId === ShriramProviderId.toLowerCase()) {
1976
- ShriramSDKGlobalData = app;
1977
- } else if (appProviderId === MahindraProviderId.toLowerCase()) {
1978
- MahindraSDKGlobalData = app;
1979
- }
1980
- });
1981
- }
1982
- } catch (e) {
1983
- // ignore errors
1984
- }
1985
-
1986
- if (isMahindra || isShriram) {
1987
- navigate('ExternalSDK', {
1988
- sdkType: isMahindra ? 'mahindra' : 'shriram',
1989
- providerId: selectedFD?.providerId || inferredProviderId,
1990
- fdListSelectedData: fdListSelectedDataToPass,
1991
- masterData: masterDataToPass,
1992
- onboardingIds: onboardingIds,
1993
- shriramSDKGlobalData: ShriramSDKGlobalData,
1994
- mahindraSDKGlobalData: MahindraSDKGlobalData,
1995
- });
1996
- } else {
1997
- // Fallback to host FD calculator
1998
- onNavigateToFDCalculator?.();
1999
- }
2000
-
2001
- } catch (error) {
2002
- // Handle error silently
2003
-
2004
- // Still dismiss bottom sheet and navigate even if API fails
2005
- // setShowPendingFDBottomSheet(false);
2006
- // onNavigateToFDCalculator?.();
2007
-
2008
- // Show error alert
2009
- Alert.alert(
2010
- 'Warning',
2011
- 'Failed to terminate existing workflow, but continuing with new FD booking.',
2012
- [{ text: 'OK' }]
2013
- );
2014
- }
2015
- }}
2016
- />
2017
-
2018
- {/* Full-screen loader while Mahindra customer/application/details are being fetched
2019
- (for Mahindra-only pending flows). */}
2020
- {isLoadingMahindraCustomerDetails && (
2021
- <View style={styles.loadingOverlay} pointerEvents="auto">
2022
- <ActivityIndicator size="large" color={colors.primary} />
2023
- </View>
2024
- )}
2025
- </SafeAreaWrapper>
2026
- );
191
+ const [activeTab, setActiveTab] = useState < string > (FD_STRINGS.ALL_FDS_TAB);
192
+ const [showPendingFDBottomSheet, setShowPendingFDBottomSheet] = useState(false);
193
+ const [selectedFD, setSelectedFD] = useState < FDItem | null > (null);
194
+ /** Application for which the pending bottom sheet is shown (matches the FD list card provider they tapped). Used for terminate/continue. */
195
+ const [pendingContextApp, setPendingContextApp] = useState < any > (null);
196
+ const [isInitialized, setIsInitialized] = useState(false);
197
+ /** Loading state for Mahindra customer/application/details prefetch (used for pending FD continue). */
198
+ const [isLoadingMahindraCustomerDetails, setIsLoadingMahindraCustomerDetails] = useState(false);
199
+ /** True while handlePendingFDContinue is in-flight (tap → navigate). Blocks the UI with a loader. */
200
+ const [isPendingFDLoading, setIsPendingFDLoading] = useState(false);
201
+ const colors = useColors();
202
+ const typography = useTypography();
203
+ const spacing = useSpacing();
204
+ const { themeName } = useTheme();
205
+ const dispatch = useAppDispatch();
206
+ const hasFetchedRatesRef = React.useRef(false);
207
+ const isCallingInterestRatesRef = React.useRef(false);
208
+ const MahindraProviderId = '881d0b98-78d7-4668-931c-f4f47d753e9d';
209
+ const ShriramProviderId = '92d94e74-c5cb-4141-91ad-ffa05df7fbff';
210
+
211
+ // API calls to fetch
212
+ const [getInterestRates, {
213
+ data: interestRates,
214
+ error: interestRatesError,
215
+ isLoading: isLoadingRates,
216
+ }] = useGetInterestRatesMutation();
217
+
218
+ const [getCustomerApplications, {
219
+ data: customerApplications,
220
+ error: customerApplicationsError,
221
+ isLoading: isLoadingApplications,
222
+ }] = useGetCustomerApplicationsMutation();
223
+ const [getCustomerApplicationDetails] = useGetCustomerApplicationDetailsMutation();
224
+
225
+ const [terminateWorkflow, {
226
+ data: terminateWorkflowData,
227
+ error: terminateWorkflowError,
228
+ isLoading: isTerminatingWorkflow,
229
+ }] = useTerminateWorkflowMutation();
230
+
231
+ // Payment Reverse Feed API
232
+ const [paymentReverseFeed, {
233
+ data: paymentReverseFeedResponse,
234
+ error: paymentReverseFeedError,
235
+ isLoading: isLoadingPaymentReverseFeed,
236
+ }] = usePaymentReverseFeedMutation();
237
+
238
+ const [getPaymentUrl] = useLazyGetPaymentUrlQuery();
239
+
240
+ const styles = createStyles(colors, typography, spacing, themeName);
241
+ const { masterData: masterDataContext, setMasterData } = useMasterData();
242
+
243
+ // Redux selectors for workflow IDs
244
+ const workflowInstanceId = useAppSelector((state: any) => state?.onboarding?.workflowInstanceId);
245
+ const applicationId = useAppSelector((state: any) => state?.onboarding?.applicationId);
246
+ const entityId = useAppSelector((state: any) => state?.onboarding?.entityid);
247
+ const providerId = useAppSelector((state: any) => state?.onboarding?.providerId);
248
+ const onboardingIds = useAppSelector((state: any) => state?.onboarding);
249
+
250
+ // Shared promise ref so FDList pending-flow and eager prefetch can wait
251
+ // on the same Mahindra customer/application/details fetch.
252
+ const mahindraDetailsPrefetchPromiseRef = React.useRef < Promise < void> | null > (null);
253
+
254
+ const initializeExternalSDKFromFDList = useCallback((
255
+ sdkType: 'shriram' | 'mahindra',
256
+ options?: { silent?: boolean }
257
+ ): boolean => {
258
+ const silent = options?.silent === true;
259
+ const currentAppData = getAppData();
260
+ const storedEnvData = getEnvironmentData();
261
+ const currentEnvData = storedEnvData as ShriramEnvironmentData & MahindraEnvironmentData | null;
262
+
263
+ if (!currentAppData) {
264
+ if (!silent) {
265
+ Alert.alert('Error', 'SDK app data not available. Please restart FD flow.');
266
+ }
267
+ return false;
268
+ }
269
+ if (!currentEnvData) {
270
+ if (!silent) {
271
+ Alert.alert('Error', 'SDK environment data not available. Please initialize environment from host app.');
272
+ }
273
+ return false;
274
+ }
275
+
276
+ try {
277
+ if (sdkType === 'shriram') {
278
+ initializeShriramEnvironment(currentEnvData as ShriramEnvironmentData);
279
+ initializeShriramSDK(currentAppData as ShriramAppData);
280
+ } else {
281
+ initializeMahindraEnvironment(currentEnvData as MahindraEnvironmentData);
282
+ initializeMahindraSDK(currentAppData as MahindraAppData);
283
+ }
284
+ return true;
285
+ } catch (error: any) {
286
+ if (!silent) {
287
+ Alert.alert(
288
+ 'Error',
289
+ `Failed to initialize ${sdkType === 'shriram' ? 'Shriram' : 'Mahindra'} SDK: ${error?.message || 'Unknown error'}`
290
+ );
291
+ }
292
+ return false;
293
+ }
294
+ }, []);
295
+
296
+ // As soon as FDList mounts, warm-initialize embedded SDKs with the same FDSDK data/env.
297
+ useEffect(() => {
298
+ initializeExternalSDKFromFDList('shriram', { silent: true });
299
+ initializeExternalSDKFromFDList('mahindra', { silent: true });
300
+ }, [initializeExternalSDKFromFDList]);
301
+
302
+ // Helper function to check if customer applications is empty
303
+ const isCustomerApplicationsEmpty = () => {
304
+ if (!customerApplications) {
305
+ return true;
306
+ }
307
+
308
+ // Handle direct array response
309
+ if (Array.isArray(customerApplications)) {
310
+ const isEmpty = customerApplications.length === 0;
311
+ return isEmpty;
312
+ }
313
+
314
+ // Handle response with data property
315
+ if (customerApplications.data && Array.isArray(customerApplications.data)) {
316
+ const isEmpty = customerApplications.data.length === 0;
317
+ return isEmpty;
318
+ }
319
+
320
+ // Handle response with applications property
321
+ if (customerApplications.applications && Array.isArray(customerApplications.applications)) {
322
+ const isEmpty = customerApplications.applications.length === 0;
323
+ return isEmpty;
324
+ }
325
+
326
+ // If customerApplications exists but doesn't match expected structure, consider it not empty
327
+ return false;
328
+ };
329
+
330
+ // Helper function to get provider ID from interest rates response (robust to varying shapes)
331
+ const getProviderIdFromInterestRates = () => {
332
+ try {
333
+ const data = interestRates?.data;
334
+ if (!data) return undefined;
335
+
336
+ const extractId = (rate: any) => {
337
+ return (
338
+ rate?.providerId ||
339
+ rate?.provider_id ||
340
+ rate?.fd_provider_id ||
341
+ rate?.fdProviderId ||
342
+ rate?.provider?.id ||
343
+ undefined
344
+ );
345
+ };
346
+
347
+ // Prefer SDR
348
+ const sdr = Array.isArray(data?.sdrScheme) ? data.sdrScheme : [];
349
+ for (const r of sdr) {
350
+ const id = extractId(r);
351
+ if (id) {
352
+ return id;
353
+ }
354
+ }
355
+
356
+ // Fallback to FDR
357
+ const fdr = Array.isArray(data?.fdrScheme) ? data.fdrScheme : [];
358
+ for (const r of fdr) {
359
+ const id = extractId(r);
360
+ if (id) {
361
+ return id;
362
+ }
363
+ }
364
+
365
+ return undefined;
366
+ } catch (e) {
367
+ return undefined;
368
+ }
369
+ };
370
+
371
+ /**
372
+ * Prefetch Mahindra customer/application/details for the currently pending/active
373
+ * Mahindra FD application. This is used to ensure that when we navigate into
374
+ * Mahindra SDK screens (especially via "Continue" on a pending FD), all
375
+ * customer details are already available, and we show a loader while fetching.
376
+ */
377
+ const prefetchMahindraCustomerApplicationDetails = useCallback(async (overrideMahindraApp?: any) => {
378
+ // Avoid duplicate fetches – reuse in-flight promise if any
379
+ if (mahindraDetailsPrefetchPromiseRef.current) {
380
+ return mahindraDetailsPrefetchPromiseRef.current;
381
+ }
382
+
383
+ const promise = (async () => {
384
+ try {
385
+ let mahindraApp: any = null;
386
+ const overrideProviderId = (
387
+ overrideMahindraApp?.fd_provider_id ||
388
+ overrideMahindraApp?.fdProviderId ||
389
+ overrideMahindraApp?.provider_id ||
390
+ overrideMahindraApp?.providerId ||
391
+ ''
392
+ ).toString();
393
+
394
+ if (overrideMahindraApp && overrideProviderId.toLowerCase() === MahindraProviderId.toLowerCase()) {
395
+ mahindraApp = overrideMahindraApp;
396
+ } else {
397
+ if (!customerApplications || isCustomerApplicationsEmpty()) {
398
+ return;
399
+ }
400
+
401
+ // Normalize list
402
+ let applicationsData: any[] = [];
403
+ if (Array.isArray(customerApplications)) {
404
+ applicationsData = customerApplications;
405
+ } else if (customerApplications.data && Array.isArray(customerApplications.data)) {
406
+ applicationsData = customerApplications.data;
407
+ } else if (customerApplications.applications && Array.isArray(customerApplications.applications)) {
408
+ applicationsData = customerApplications.applications;
409
+ }
410
+
411
+ if (!applicationsData.length) return;
412
+
413
+ // Find Mahindra pending/active app for MahindraProviderId
414
+ mahindraApp = getPendingOrActiveAppForProvider(applicationsData, MahindraProviderId);
415
+ }
416
+ if (!mahindraApp) return;
417
+
418
+ const ids = {
419
+ workflowInstanceId:
420
+ mahindraApp.workflow_instance_id ||
421
+ mahindraApp.workflowInstanceId ||
422
+ mahindraApp.workflowinstanceid ||
423
+ mahindraApp.workflow_instance ||
424
+ mahindraApp.workflowInstance ||
425
+ undefined,
426
+ applicationId:
427
+ mahindraApp.application_id ||
428
+ mahindraApp.applicationId ||
429
+ mahindraApp.applicationid ||
430
+ undefined,
431
+ entityid:
432
+ mahindraApp.entity_id ||
433
+ mahindraApp.entityId ||
434
+ mahindraApp.entityid ||
435
+ undefined,
436
+ customerId:
437
+ mahindraApp.customer_id ||
438
+ mahindraApp.customerId ||
439
+ mahindraApp.customerid ||
440
+ undefined,
441
+ providerId:
442
+ mahindraApp.fd_provider_id ||
443
+ mahindraApp.fdProviderId ||
444
+ mahindraApp.provider_id ||
445
+ mahindraApp.providerId ||
446
+ MahindraProviderId,
447
+ };
448
+
449
+ if (!ids.applicationId || !ids.customerId || !ids.providerId) {
450
+ return;
451
+ }
452
+
453
+ setIsLoadingMahindraCustomerDetails(true);
454
+
455
+ const userInfo = getUserInfoForAPI();
456
+ const detailsResp = await getCustomerApplicationDetails({
457
+ providerId: ids.providerId,
458
+ applicationId: ids.applicationId,
459
+ customerId: ids.customerId,
460
+ workflowInstanceId: ids.workflowInstanceId,
461
+ userreferenceid: userInfo.userReferenceId,
462
+ applicationid: ids.applicationId,
463
+ entityid: ids.entityid,
464
+ }).unwrap();
465
+
466
+ const detailsData = (detailsResp as any)?.data || detailsResp;
467
+ if (detailsData) {
468
+ setGlobalData({ mahindraCustomerDetails: detailsData });
469
+ setMahindraGlobalData({ mahindraCustomerDetails: detailsData });
470
+ }
471
+ } catch (err) {
472
+ } finally {
473
+ setIsLoadingMahindraCustomerDetails(false);
474
+ mahindraDetailsPrefetchPromiseRef.current = null;
475
+ }
476
+ })();
477
+
478
+ mahindraDetailsPrefetchPromiseRef.current = promise;
479
+ return promise;
480
+ }, [customerApplications, getCustomerApplicationDetails]);
481
+
482
+ // Helper function to filter FDs based on tenure
483
+ const filterFDsByTenure = (fdList: FDItem[], filter: string): FDItem[] => {
484
+ if (filter === FD_STRINGS.ALL_FDS_TAB) return fdList;
485
+
486
+ return fdList.filter(fd => {
487
+ // Extract tenure in months from the fd.tenure string (e.g., "12 Months" -> 12)
488
+ const tenureMatch = fd.tenure.match(/(\d+)\s*Months?/i);
489
+ if (!tenureMatch) return false;
490
+
491
+ const tenureMonths = parseInt(tenureMatch[1], 10);
492
+ const tenureYears = tenureMonths / 12;
493
+
494
+ switch (filter) {
495
+ case '<1Y':
496
+ return tenureYears <= 1;
497
+ case '<1-3Y':
498
+ return tenureYears > 1 && tenureYears <= 3;
499
+ case '<3-5Y':
500
+ return tenureYears > 3 && tenureYears <= 5;
501
+ case '>5Y':
502
+ return tenureYears > 5;
503
+ default:
504
+ return true;
505
+ }
506
+ });
507
+ };
508
+
509
+
510
+
511
+ // Derive identifiers for workflow termination from applications API (for active FD/provider)
512
+ const terminateIdentifiers = React.useMemo(() => {
513
+ try {
514
+ if (!customerApplications || isCustomerApplicationsEmpty()) {
515
+ return {
516
+ applicationId: undefined as string | undefined,
517
+ workflowInstanceId: undefined as string | undefined,
518
+ entityId: undefined as string | undefined,
519
+ providerIdFromApp: undefined as string | undefined,
520
+ };
521
+ }
522
+
523
+ // Normalize list
524
+ let applicationsData: any[] = [];
525
+ if (Array.isArray(customerApplications)) {
526
+ applicationsData = customerApplications;
527
+ } else if (customerApplications.data && Array.isArray(customerApplications.data)) {
528
+ applicationsData = customerApplications.data;
529
+ } else if (customerApplications.applications && Array.isArray(customerApplications.applications)) {
530
+ applicationsData = customerApplications.applications;
531
+ }
532
+
533
+ if (applicationsData.length === 0) {
534
+ return {
535
+ applicationId: undefined,
536
+ workflowInstanceId: undefined,
537
+ entityId: undefined,
538
+ providerIdFromApp: undefined,
539
+ };
540
+ }
541
+
542
+ // Prefer a pending/in-progress one for termination, else first
543
+ const appForTerminate = applicationsData.find((a: any) => {
544
+ const s = (a.wf_status || a.status || '').toString().toLowerCase();
545
+ return s === 'pending' || s === 'in_progress' || s === 'in-progress';
546
+ }) || applicationsData[0];
547
+
548
+ const applicationId: string | undefined =
549
+ appForTerminate.application_id || appForTerminate.applicationId || appForTerminate.id;
550
+ const workflowInstanceId: string | undefined =
551
+ appForTerminate.workflow_instance_id || appForTerminate.workflowInstanceId || appForTerminate.workflow?.instanceId;
552
+ const entityId: string | undefined =
553
+ appForTerminate.entity_id || appForTerminate.entityId || appForTerminate.entity?.id;
554
+ const providerIdFromApp: string | undefined =
555
+ appForTerminate.provider_id || appForTerminate.providerId || appForTerminate.fd_provider_id || appForTerminate.fdProviderId;
556
+
557
+ return { applicationId, workflowInstanceId, entityId, providerIdFromApp };
558
+ } catch (e) {
559
+ return {
560
+ applicationId: undefined,
561
+ workflowInstanceId: undefined,
562
+ entityId: undefined,
563
+ providerIdFromApp: undefined,
564
+ };
565
+ }
566
+ }, [customerApplications]);
567
+
568
+ const activeProviderId = terminateIdentifiers.providerIdFromApp;
569
+
570
+ // Master data fetch - prefer tapped FD provider, then active app provider, then rates
571
+ const defaultProviderId = ''; // Default provider ID for initial call
572
+ const inferredProviderId = React.useMemo(() => {
573
+ if (selectedFD?.providerId) return selectedFD.providerId;
574
+ if (activeProviderId) return activeProviderId;
575
+ if (interestRates?.data?.sdrScheme?.[0]?.providerId) return interestRates.data.sdrScheme[0].providerId;
576
+ if (interestRates?.data?.fdrScheme?.[0]?.providerId) return interestRates.data.fdrScheme[0].providerId;
577
+ return defaultProviderId;
578
+ }, [selectedFD, activeProviderId, interestRates]);
579
+
580
+ const { data: masterData, isLoading: isLoadingMaster } = useGetMasterDataQuery(
581
+ { providerId: inferredProviderId },
582
+ { skip: !inferredProviderId }
583
+ );
584
+ const { data: MahindraMasterData, refetch: refetchMahindraMasterData } = useGetMasterDataQuery(
585
+ { providerId: MahindraProviderId },
586
+ { skip: false }
587
+ );
588
+ const { data: ShriramMasterData, refetch: refetchShriramMasterData } = useGetMasterDataQuery(
589
+ { providerId: ShriramProviderId },
590
+ { skip: false }
591
+ );
592
+
593
+ // Only render once all three API calls have completed (success or error)
594
+ // Show loading initially until all APIs have completed
595
+ const isAllDataReady = React.useMemo(() => {
596
+ // If not initialized yet, show loading
597
+ if (!isInitialized) return false;
598
+
599
+ // Step 1: Check if interest rates API has been called and completed
600
+ // If currently loading, show loading
601
+ if (isLoadingRates) return false;
602
+ // If not loading but no data and no error, API hasn't been called yet - show loading
603
+ if (!interestRates && !interestRatesError) return false;
604
+
605
+ // Step 2: Once interest rates are done, customer applications should be called
606
+ // If currently loading, show loading
607
+ if (isLoadingApplications) return false;
608
+ // If interest rates succeeded, we must wait for customer applications
609
+ // If interest rates failed, we can still proceed (but applications might not be called)
610
+ if (interestRates) {
611
+ // Interest rates succeeded, so applications should be called
612
+ // If not loading but no data and no error, applications haven't been called yet - show loading
613
+ if (!customerApplications && !customerApplicationsError) return false;
614
+ }
615
+
616
+ // Step 3: Check master data - only required if providerId is available
617
+ const masterShouldRun = !!inferredProviderId;
618
+ if (masterShouldRun) {
619
+ // Master data should be done (either loaded or error, but not loading)
620
+ if (isLoadingMaster) return false;
621
+ }
622
+
623
+ return true;
624
+ }, [isInitialized, interestRates, interestRatesError, isLoadingRates, customerApplications, customerApplicationsError, isLoadingApplications, inferredProviderId, isLoadingMaster]);
625
+
626
+ useEffect(() => {
627
+ if (masterData) {
628
+ setMasterData(masterData);
629
+ }
630
+ }, [masterData, setMasterData]);
631
+
632
+ // Function to refresh all data - Required sequence: Interest -> Applications -> Master
633
+ const refreshData = useCallback(async () => {
634
+ if (!isInitialized) {
635
+ return;
636
+ }
637
+
638
+ // Prevent multiple simultaneous calls
639
+ if (isCallingInterestRatesRef.current || isLoadingRates) {
640
+ return;
641
+ }
642
+
643
+ try {
644
+ isCallingInterestRatesRef.current = true;
645
+ // Add 500ms delay before calling interest rate API
646
+ await new Promise(resolve => setTimeout(resolve, 500));
647
+ await getInterestRates({});
648
+ } catch (error) {
649
+ // Handle error silently
650
+ } finally {
651
+ isCallingInterestRatesRef.current = false;
652
+ }
653
+ }, [getInterestRates, isInitialized, isLoadingRates]);
654
+
655
+ // Initialize component after mount
656
+ useEffect(() => {
657
+ const initializeComponent = async () => {
658
+ // Validate required user data before proceeding
659
+ const appData = getAppData();
660
+
661
+ const requiredFields = [
662
+ { field: 'id', label: 'User ID' },
663
+ { field: 'name', label: 'User Name' },
664
+ { field: 'dob', label: 'Date of Birth' },
665
+ { field: 'gender', label: 'Gender' },
666
+ { field: 'mobNo', label: 'Mobile Number' },
667
+ { field: 'email', label: 'Email' },
668
+ ];
669
+
670
+ const missingFields: string[] = [];
671
+
672
+ requiredFields.forEach(({ field, label }) => {
673
+ const value = appData?.[field as keyof typeof appData];
674
+ if (!value || (typeof value === 'string' && !value.trim())) {
675
+ missingFields.push(label);
676
+ }
677
+ });
678
+
679
+ // Show alert if required fields are missing
680
+ if (missingFields.length > 0) {
681
+ Alert.alert(
682
+ 'Cannot Proceed with FD Booking',
683
+ `The following required information is missing from the main app:\n\n${missingFields.join('\n')}\n\nPlease provide all required user information to proceed.`,
684
+ [
685
+ {
686
+ text: 'Cancel FD Booking',
687
+ onPress: () => {
688
+ // Redirect back to main app
689
+ if (onGoBack) {
690
+ onGoBack();
691
+ } else {
692
+ // No-op when onGoBack is not provided
693
+ }
694
+ },
695
+ style: 'cancel',
696
+ },
697
+ ],
698
+ { cancelable: false }
699
+ );
700
+ return; // Don't initialize if validation fails
701
+ }
702
+
703
+ // Additional value validation (case-insensitive): gender, maritalStatus, typeOfAccount
704
+ const errors: string[] = [];
705
+
706
+ // Gender must be Male, Female, or Other (case-insensitive)
707
+ const gender = String(appData?.gender || '').trim().toLowerCase();
708
+ const allowedGenders = ['male', 'female', 'other'];
709
+ if (gender && !allowedGenders.includes(gender)) {
710
+ errors.push("Gender must be one of: Male, Female, Other");
711
+ }
712
+
713
+ // Marital status must be Married, Unmarried, or Other (case-insensitive)
714
+ const maritalStatus = String(appData?.maritalStatus || '').trim().toLowerCase();
715
+ const allowedMarital = ['married', 'unmarried', 'other'];
716
+ if (maritalStatus && !allowedMarital.includes(maritalStatus)) {
717
+ errors.push("Marital Status must be one of: Married, Unmarried, Other");
718
+ }
719
+
720
+ // Account type must be 'Saving A/c' or 'Current A/c' (support legacy codes like SB/CA)
721
+ const typeOfAccountRaw = String(appData?.typeOfAccount || '').trim();
722
+ const normalizeAccountType = (value: string) =>
723
+ value.replace(/[^a-z]/gi, '').toLowerCase();
724
+ const accountTypeMap: Record<string, 'Saving A/c' | 'Current A/c'> = {
725
+ savingac: 'Saving A/c',
726
+ savingaccount: 'Saving A/c',
727
+ savingsaccount: 'Saving A/c',
728
+ savings: 'Saving A/c',
729
+ saving: 'Saving A/c',
730
+ sb: 'Saving A/c',
731
+ currentac: 'Current A/c',
732
+ currentaccount: 'Current A/c',
733
+ current: 'Current A/c',
734
+ ca: 'Current A/c',
735
+ };
736
+ const normalizedAccountType = normalizeAccountType(typeOfAccountRaw);
737
+ const canonicalAccountType = accountTypeMap[normalizedAccountType];
738
+ if (typeOfAccountRaw && !canonicalAccountType) {
739
+ errors.push("Type of Account must be one of: 'Saving A/c', 'Current A/c'");
740
+ }
741
+
742
+ if (errors.length > 0) {
743
+ Alert.alert(
744
+ 'Invalid User Data',
745
+ `${errors.join('\n')}`,
746
+ [
747
+ {
748
+ text: 'OK',
749
+ onPress: () => {
750
+ if (onGoBack) {
751
+ onGoBack();
752
+ }
753
+ },
754
+ style: 'cancel',
755
+ },
756
+ ],
757
+ { cancelable: false }
758
+ );
759
+ return; // Stop initialization on invalid values
760
+ }
761
+
762
+ // Small delay to ensure component is fully mounted
763
+ await new Promise(resolve => setTimeout(resolve, 100));
764
+ setIsInitialized(true);
765
+ };
766
+
767
+ initializeComponent();
768
+ }, [onGoBack]);
769
+
770
+ // Removed screen-level hardware back handler to allow only header back button navigation
771
+
772
+
773
+ // Removed init-time fetch to avoid duplicate calls; we'll fetch on first focus only
774
+
775
+ // Refresh data when screen comes into focus (only if initialized)
776
+ useFocusEffect(
777
+ useCallback(() => {
778
+ if (!isInitialized) return;
779
+ if (hasFetchedRatesRef.current) return; // ensure called only once when screen first shows
780
+ // Prevent multiple simultaneous calls
781
+ if (isCallingInterestRatesRef.current || isLoadingRates) return;
782
+ hasFetchedRatesRef.current = true;
783
+ refreshData();
784
+ }, [refreshData, isInitialized, isLoadingRates])
785
+ );
786
+
787
+ // As soon as FDList has customer applications, eagerly prefetch Mahindra
788
+ // customer/application/details so that if user taps a Mahindra pending FD,
789
+ // details are already cached (or at least being fetched with a loader).
790
+ useEffect(() => {
791
+ if (!isInitialized) return;
792
+ if (!customerApplications || isCustomerApplicationsEmpty()) return;
793
+ prefetchMahindraCustomerApplicationDetails();
794
+ }, [isInitialized, customerApplications, prefetchMahindraCustomerApplicationDetails]);
795
+
796
+ // After interest rates are received, call Customer Applications
797
+ useEffect(() => {
798
+ if (!interestRates) return;
799
+ try {
800
+ const userInfo = getUserInfoForAPI();
801
+ getCustomerApplications({ userReferenceId: userInfo.id });
802
+ } catch (e) {
803
+ }
804
+ // eslint-disable-next-line react-hooks/exhaustive-deps
805
+ }, [!!interestRates]);
806
+
807
+ // Handle API errors
808
+ useEffect(() => {
809
+ if (interestRatesError) {
810
+ Alert.alert(
811
+ 'API Error',
812
+ 'Failed to fetch interest rates. Please try again.',
813
+ [
814
+ {
815
+ text: 'Retry',
816
+ onPress: async () => {
817
+ // Prevent multiple simultaneous calls
818
+ if (isCallingInterestRatesRef.current || isLoadingRates) {
819
+ return;
820
+ }
821
+ try {
822
+ isCallingInterestRatesRef.current = true;
823
+ // Add 500ms delay before retrying interest rate API
824
+ await new Promise(resolve => setTimeout(resolve, 500));
825
+ await getInterestRates({});
826
+ } catch (error) {
827
+ // Handle error silently
828
+ } finally {
829
+ isCallingInterestRatesRef.current = false;
830
+ }
831
+ }
832
+ },
833
+ { text: 'Continue', style: 'cancel' }
834
+ ]
835
+ );
836
+ }
837
+ }, [interestRatesError, getInterestRates]);
838
+
839
+ // Log successful data fetch
840
+ useEffect(() => {
841
+ if (interestRates && interestRates.data?.sdrScheme?.length > 0) {
842
+ // Interest rates loaded successfully
843
+ }
844
+ }, [interestRates]);
845
+
846
+ // Persist providerId to onboarding store as soon as we have rates
847
+ // useEffect(() => {
848
+ // const pid = getProviderIdFromInterestRates();
849
+ // if (pid) {
850
+ // try {
851
+ // dispatch(setOnboardingIds({ providerId: pid }));
852
+ // } catch (e) {
853
+ // // Handle error silently
854
+ // }
855
+ // }
856
+ // }, [interestRates, dispatch]);
857
+
858
+ // useEffect(() => {
859
+ // if (customerApplications) {
860
+ // // Customer applications loaded successfully
861
+
862
+ // // Persist onboarding identifiers globally for subsequent API calls (like FDCalculator)
863
+ // try {
864
+ // // Extract workflow parameters from response
865
+ // const responseData = customerApplications?.data || customerApplications;
866
+ // let applicationData = Array.isArray(responseData) ? responseData[0] : responseData;
867
+
868
+ // // Prefer the application with wf_status === 'Active'
869
+ // if (Array.isArray(responseData)) {
870
+ // const activeOnly = responseData.find((app: any) => {
871
+ // const status = (app.wf_status || app.status || '').toString();
872
+ // return status === 'Active';
873
+ // });
874
+ // if (activeOnly) applicationData = activeOnly;
875
+ // }
876
+
877
+ // if (Array.isArray(responseData)) {
878
+ // completeFDData = responseData.find((app: any) => {
879
+ // const status = (app.wf_status || app.status || '').toString();
880
+ // return status === 'Completed';
881
+ // });
882
+ // setGlobalData({ completeFDData: !!completeFDData });
883
+ // }
884
+ // if (applicationData) {
885
+ // const ids = {
886
+ // workflowInstanceId: applicationData.workflow_instance_id,
887
+ // applicationId: applicationData.application_id,
888
+ // entityid: applicationData.entity_id,
889
+ // customerId: applicationData.customer_id,
890
+ // fdId: applicationData.fd_id,
891
+ // currentState: applicationData.current_state,
892
+ // currentTask: applicationData.current_task,
893
+ // // Persist providerId from interest-rate API (prefers SDR, fallback FDR)
894
+ // providerId: getProviderIdFromInterestRates(),
895
+ // wfStatus: applicationData.wf_status,
896
+ // };
897
+ // if (dispatch && setOnboardingIds) {
898
+ // dispatch(setOnboardingIds(ids));
899
+ // } else {
900
+ // // Dispatch or setOnboardingIds unavailable
901
+ // }
902
+ // }
903
+ // } catch (error) {
904
+ // // Handle error silently
905
+ // }
906
+ // }
907
+ // }, [customerApplications, dispatch]);
908
+
909
+ // Unified handler: behave like bottom sheet Continue flow
910
+ const handlePendingFDContinue = async (
911
+ overrideApp?: any,
912
+ forcedSdkType?: 'mahindra' | 'shriram'
913
+ ) => {
914
+ setIsPendingFDLoading(true);
915
+ try {
916
+ // Get current state from customer applications for workflow navigation
917
+ let currentStateCaption: string = "";
918
+ let transactionId: string | undefined = undefined;
919
+ let appData: any = overrideApp || pendingContextApp;
920
+
921
+ let fdDisplay: ReturnType<typeof appToPendingFDData> | null = null;
922
+ let onboardingIdsFromApp: any = null;
923
+
924
+ try {
925
+ if (!appData && customerApplications && !isCustomerApplicationsEmpty()) {
926
+ // Normalize list
927
+ let applicationsData: any[] = [];
928
+ if (Array.isArray(customerApplications)) {
929
+ applicationsData = customerApplications;
930
+ } else if (customerApplications.data && Array.isArray(customerApplications.data)) {
931
+ applicationsData = customerApplications.data;
932
+ } else if (customerApplications.applications && Array.isArray(customerApplications.applications)) {
933
+ applicationsData = customerApplications.applications;
934
+ }
935
+
936
+ if (applicationsData.length > 0) {
937
+ // Fallback: find first Active application when no override/pendingContext
938
+ appData = applicationsData.find((a: any) => {
939
+ const s = (a.wf_status || a.status || '').toString();
940
+ return s === 'Active';
941
+ }) || applicationsData.find((a: any) => {
942
+ const s = (a.wf_status || a.status || '').toString().toLowerCase();
943
+ return PENDING_ACTIVE_STATUSES.includes(s);
944
+ }) || applicationsData[0];
945
+ }
946
+ }
947
+ if (appData) {
948
+ currentStateCaption = appData.current_state_caption;
949
+ transactionId = appData.transaction_id || appData.transactionId || appData.Transactionid || appData.TransactionId;
950
+ fdDisplay = appToPendingFDData(appData);
951
+ let providerIdFromApp = resolveProviderIdFromApp(appData, MahindraProviderId, ShriramProviderId);
952
+ if (forcedSdkType === 'mahindra') {
953
+ providerIdFromApp = MahindraProviderId;
954
+ } else if (forcedSdkType === 'shriram') {
955
+ providerIdFromApp = ShriramProviderId;
956
+ }
957
+ const providerIdLower = (providerIdFromApp || '').toLowerCase();
958
+ const isMahindra = providerIdLower === MahindraProviderId.toLowerCase();
959
+
960
+ // For Mahindra flows, ensure customer/application/details are fully loaded
961
+ // before navigating into ExternalSDK so downstream screens see complete
962
+ // nominee/bank/KYC/guardian data. Show a loader while we wait.
963
+ if (isMahindra) {
964
+ await prefetchMahindraCustomerApplicationDetails(appData);
965
+ }
966
+
967
+ console.log("fdDisplay===>", fdDisplay);
968
+
969
+ try {
970
+ const fdListSelectedData = {
971
+ id: appData.fd_id || appData.fdId || 'active-fd',
972
+ providerId: providerIdFromApp,
973
+ name: fdDisplay.name,
974
+ accountNumber: appData.account_number || appData.accountNumber || '',
975
+ roi: `${fdDisplay.returns}% p.a.`,
976
+ tenure: fdDisplay.tenure || '-',
977
+ amount: Number(fdDisplay.invested) || 0,
978
+ maturityDate: fdDisplay.maturityDate || '-',
979
+ status: 'active' as const,
980
+ creditRating: getStaticCreditRatingForFD({
981
+ name: fdDisplay.name,
982
+ providerId: providerIdFromApp,
983
+ }),
984
+ companyName: fdDisplay.name,
985
+ fdRate: `${fdDisplay.returns}% p.a.`,
986
+ interestPayout: fdDisplay.interestPayout || '',
987
+ };
988
+ dispatch(setFDListSelected(fdListSelectedData));
989
+ } catch (e) {
990
+ // Handle error silently
991
+ }
992
+
993
+ try {
994
+ const ids = {
995
+ workflowInstanceId: appData.workflow_instance_id || appData.workflowInstanceId,
996
+ applicationId: appData.application_id || appData.applicationId,
997
+ entityid: appData.entity_id || appData.entityId,
998
+ customerId: appData.customer_id || appData.customerId,
999
+ fdId: appData.fd_id || appData.fdId,
1000
+ currentState: appData.current_state || appData.currentState,
1001
+ currentTask: appData.current_task || appData.currentTask,
1002
+ providerId: providerIdFromApp || undefined,
1003
+ wfStatus: appData.wf_status || appData.wfStatus,
1004
+ };
1005
+ onboardingIdsFromApp = ids;
1006
+ dispatch(setOnboardingIds(ids));
1007
+ } catch (e) {
1008
+ }
1009
+ }
1010
+ } catch (error) {
1011
+ // Handle error silently
1012
+ }
1013
+
1014
+ if (!fdDisplay && appData) fdDisplay = appToPendingFDData(appData);
1015
+
1016
+ // If current state is payment and transactionId is available, call payment reverse feed API
1017
+ // Check for both Mahindra and Shriram payment states
1018
+ const isPaymentState =
1019
+ currentStateCaption === MAHINDRA_WORKFLOW_STATES.PAYMENT ||
1020
+ currentStateCaption === SHRIRAM_WORKFLOW_STATES.PAYMENT;
1021
+
1022
+ if (isPaymentState && transactionId) {
1023
+ try {
1024
+ // Get user info and required IDs (prefer appData from continue context)
1025
+ const userInfo = getUserInfoForAPI();
1026
+ let providerIdFromApp = resolveProviderIdFromApp(appData, MahindraProviderId, ShriramProviderId);
1027
+ if (forcedSdkType === 'mahindra') providerIdFromApp = MahindraProviderId;
1028
+ if (forcedSdkType === 'shriram') providerIdFromApp = ShriramProviderId;
1029
+ const paymentReverseFeedRequest = {
1030
+ providerId: providerIdFromApp || providerId,
1031
+ workflowInstanceId: appData?.workflow_instance_id || appData?.workflowInstanceId || workflowInstanceId,
1032
+ userreferenceid: userInfo.id,
1033
+ applicationid: appData?.application_id || appData?.applicationId || applicationId,
1034
+ entityid: appData?.entity_id || appData?.entityId || entityId,
1035
+ transactionId: transactionId,
1036
+ };
1037
+
1038
+ const response = await paymentReverseFeed(paymentReverseFeedRequest).unwrap();
1039
+
1040
+ // Handle the response based on payment status (normalize to uppercase)
1041
+ const paymentStatus = (response?.data?.paymentStatus || response?.paymentStatus || '').toUpperCase();
1042
+ const statusParam = paymentStatus === 'SUCCESS' ? 'success' : paymentStatus === 'FAILED' ? 'failed' : 'pending';
1043
+
1044
+ const fdDataParam = fdDisplay ? {
1045
+ companyName: fdDisplay.name,
1046
+ amount: Number(fdDisplay.invested) || 0,
1047
+ fdRate: `${fdDisplay.returns}% p.a.`,
1048
+ tenure: fdDisplay.tenure ? `${fdDisplay.tenure}` : '-',
1049
+ interestPayout: fdDisplay.interestPayout || 'Yearly',
1050
+ } : activeFD ? {
1051
+ companyName: activeFD.name,
1052
+ amount: Number(activeFD.invested) || 0,
1053
+ fdRate: `${activeFD.returns}% p.a.`,
1054
+ tenure: activeFD.maturityDate ? `${activeFD.maturityDate}` : '-',
1055
+ interestPayout: activeFD.interestPayout || 'Yearly',
1056
+ } : undefined;
1057
+
1058
+ // Shriram: always navigate to Shriram SDK PaymentStatus, never FDSDK PaymentStatus
1059
+ let paymentProviderId = resolveProviderIdFromApp(appData, MahindraProviderId, ShriramProviderId).toLowerCase();
1060
+ if (forcedSdkType === 'mahindra') paymentProviderId = MahindraProviderId.toLowerCase();
1061
+ if (forcedSdkType === 'shriram') paymentProviderId = ShriramProviderId.toLowerCase();
1062
+ const isShriramPayment = paymentProviderId === ShriramProviderId.toLowerCase();
1063
+ const isMahindraPayment = paymentProviderId === MahindraProviderId.toLowerCase();
1064
+
1065
+ if (isShriramPayment && appData) {
1066
+ if (!initializeExternalSDKFromFDList('shriram')) return;
1067
+ let ShriramSDKGlobalDataPayment: any = null;
1068
+ if (customerApplications && !isCustomerApplicationsEmpty()) {
1069
+ let applicationsData: any[] = [];
1070
+ if (Array.isArray(customerApplications)) applicationsData = customerApplications;
1071
+ else if (customerApplications?.data && Array.isArray(customerApplications.data)) applicationsData = customerApplications.data;
1072
+ else if (customerApplications?.applications && Array.isArray(customerApplications.applications)) applicationsData = customerApplications.applications;
1073
+ const completeApplications = applicationsData.filter((app: any) => {
1074
+ const s = (app.wf_status || app.status || '').toString().toLowerCase();
1075
+ return s === 'completed' || s === 'complete';
1076
+ });
1077
+ const shriramComplete = completeApplications.find((app: any) => (app.fd_provider_id || app.fdProviderId || app.provider_id || app.providerId || '').toLowerCase() === ShriramProviderId.toLowerCase());
1078
+ if (shriramComplete) ShriramSDKGlobalDataPayment = shriramComplete;
1079
+ }
1080
+ const fdListSelectedDataPayment = fdDisplay ? {
1081
+ id: appData.fd_id || appData.fdId || 'active-fd',
1082
+ providerId: providerIdFromApp,
1083
+ name: fdDisplay.name,
1084
+ accountNumber: appData.account_number || appData.accountNumber || '',
1085
+ roi: `${fdDisplay.returns}% p.a.`,
1086
+ tenure: fdDisplay.tenure || '-',
1087
+ amount: Number(fdDisplay.invested) || 0,
1088
+ maturityDate: fdDisplay.maturityDate || '-',
1089
+ status: 'active' as const,
1090
+ creditRating: getStaticCreditRatingForFD({
1091
+ name: fdDisplay.name,
1092
+ providerId: providerIdFromApp,
1093
+ }),
1094
+ companyName: fdDisplay.name,
1095
+ fdRate: `${fdDisplay.returns}% p.a.`,
1096
+ interestPayout: fdDisplay.interestPayout || 'Yearly',
1097
+ } : null;
1098
+ const masterDataPayment = ShriramMasterData;
1099
+
1100
+ navigate('ExternalSDK', {
1101
+ sdkType: 'shriram',
1102
+ providerId: providerIdFromApp,
1103
+ fdListSelectedData: fdListSelectedDataPayment,
1104
+ masterData: masterDataPayment,
1105
+ onboardingIds: onboardingIdsFromApp || onboardingIds,
1106
+ initialRouteName: 'PaymentStatus',
1107
+ initialPaymentStatusParams: { status: statusParam, transactionId, fdData: fdDataParam },
1108
+ shriramSDKGlobalData: ShriramSDKGlobalDataPayment,
1109
+ } as any);
1110
+ return;
1111
+ }
1112
+
1113
+ if (isMahindraPayment && appData) {
1114
+ if (!initializeExternalSDKFromFDList('mahindra')) return;
1115
+ const fdListSelectedDataPayment = fdDisplay ? {
1116
+ id: appData.fd_id || appData.fdId || 'active-fd',
1117
+ providerId: providerIdFromApp,
1118
+ name: fdDisplay.name,
1119
+ accountNumber: appData.account_number || appData.accountNumber || '',
1120
+ roi: `${fdDisplay.returns}% p.a.`,
1121
+ tenure: fdDisplay.tenure || '-',
1122
+ amount: Number(fdDisplay.invested) || 0,
1123
+ maturityDate: fdDisplay.maturityDate || '-',
1124
+ status: 'active' as const,
1125
+ creditRating: getStaticCreditRatingForFD({
1126
+ name: fdDisplay.name,
1127
+ providerId: providerIdFromApp,
1128
+ }),
1129
+ companyName: fdDisplay.name,
1130
+ fdRate: `${fdDisplay.returns}% p.a.`,
1131
+ interestPayout: fdDisplay.interestPayout || 'Yearly',
1132
+ } : null;
1133
+ const masterDataPayment = MahindraMasterData;
1134
+
1135
+ console.log("paymentStatus===>", paymentStatus);
1136
+
1137
+ if (paymentStatus === 'IN_PROGRESS') {
1138
+ const userInfo = getUserInfoForAPI();
1139
+ const mahindraDetails = (getGlobalData() as any)?.mahindraCustomerDetails;
1140
+
1141
+ // PAN is optional at SDK init — collect it from every available source
1142
+ const panNo = (
1143
+ userInfo?.panNumber ??
1144
+ (userInfo as any)?.panNo ??
1145
+ (userInfo as any)?.pan ??
1146
+ mahindraDetails?.pan_number ??
1147
+ mahindraDetails?.panNumber ??
1148
+ mahindraDetails?.pan ??
1149
+ mahindraDetails?.kyc_pan ??
1150
+ mahindraDetails?.kycPan ??
1151
+ appData?.pan_number ??
1152
+ appData?.panNumber ??
1153
+ appData?.pan ??
1154
+ ''
1155
+ ).trim();
1156
+
1157
+ const appIdForUrl = appData?.application_id || appData?.applicationId || '';
1158
+
1159
+ if (__DEV__) {
1160
+ console.log('[FDList][IN_PROGRESS] panNo:', panNo, '| appId:', appIdForUrl, '| provider:', providerIdFromApp);
1161
+ }
1162
+
1163
+ let resolvedPaymentUrl: string | undefined;
1164
+ let resolvedTransactionId: string | undefined = transactionId;
1165
+
1166
+ try {
1167
+ const urlResult = await getPaymentUrl({
1168
+ providerId: providerIdFromApp,
1169
+ workflowInstanceId: appData?.workflow_instance_id || appData?.workflowInstanceId || workflowInstanceId,
1170
+ userreferenceid: userInfo.id,
1171
+ applicationid: appIdForUrl,
1172
+ entityid: appData?.entity_id || appData?.entityId || entityId,
1173
+ applicationId: appIdForUrl,
1174
+ panNo,
1175
+ isRetry: true,
1176
+ }).unwrap();
1177
+
1178
+ if (__DEV__) {
1179
+ console.log('[FDList][IN_PROGRESS] getPaymentUrl result:', urlResult);
1180
+ }
1181
+
1182
+ resolvedPaymentUrl = urlResult?.data?.[0]?.paymentUrl;
1183
+ resolvedTransactionId = urlResult?.data?.[0]?.transactionId || transactionId;
1184
+ } catch (err) {
1185
+ if (__DEV__) {
1186
+ console.log('[FDList][IN_PROGRESS] getPaymentUrl error — navigating to Payment with existing session:', err);
1187
+ }
1188
+ }
1189
+
1190
+ // Always navigate to Payment for IN_PROGRESS.
1191
+ // Pass initialPaymentUrl only when getPaymentUrl returned one;
1192
+ // otherwise Payment screen reads from mahindra's existing paymentSession.
1193
+ navigate('ExternalSDK', {
1194
+ sdkType: 'mahindra',
1195
+ providerId: providerIdFromApp,
1196
+ fdListSelectedData: fdListSelectedDataPayment,
1197
+ masterData: masterDataPayment,
1198
+ onboardingIds: onboardingIdsFromApp || onboardingIds,
1199
+ initialRouteName: 'Payment',
1200
+ ...(resolvedPaymentUrl ? {
1201
+ initialPaymentUrl: resolvedPaymentUrl,
1202
+ initialPaymentTransactionId: resolvedTransactionId,
1203
+ } : {}),
1204
+ } as any);
1205
+ return;
1206
+ } else {
1207
+ navigate('ExternalSDK', {
1208
+ sdkType: 'mahindra',
1209
+ providerId: providerIdFromApp,
1210
+ fdListSelectedData: fdListSelectedDataPayment,
1211
+ masterData: masterDataPayment,
1212
+ onboardingIds: onboardingIdsFromApp || onboardingIds,
1213
+ initialRouteName: 'PaymentStatus',
1214
+ initialPaymentStatusParams: { status: statusParam, transactionId, fdData: fdDataParam },
1215
+ } as any);
1216
+ }
1217
+
1218
+
1219
+ return;
1220
+ }
1221
+
1222
+ navigate('PaymentStatus', { status: statusParam, transactionId, fdData: fdDataParam } as any);
1223
+ return;
1224
+
1225
+ } catch (error) {
1226
+ // Handle error silently
1227
+ const fdDataParam = fdDisplay ? {
1228
+ companyName: fdDisplay.name,
1229
+ amount: Number(fdDisplay.invested) || 0,
1230
+ fdRate: `${fdDisplay.returns}% p.a.`,
1231
+ tenure: fdDisplay.tenure ? `${fdDisplay.tenure}` : '-',
1232
+ interestPayout: fdDisplay.interestPayout || 'Yearly',
1233
+ } : activeFD ? {
1234
+ companyName: activeFD.name,
1235
+ amount: Number(activeFD.invested) || 0,
1236
+ fdRate: `${activeFD.returns}% p.a.`,
1237
+ tenure: activeFD.maturityDate ? `${activeFD.maturityDate}` : '-',
1238
+ interestPayout: activeFD.interestPayout || 'Yearly',
1239
+ } : undefined;
1240
+ let providerIdFromApp = resolveProviderIdFromApp(appData, MahindraProviderId, ShriramProviderId);
1241
+ if (forcedSdkType === 'mahindra') providerIdFromApp = MahindraProviderId;
1242
+ if (forcedSdkType === 'shriram') providerIdFromApp = ShriramProviderId;
1243
+ const paymentProviderIdCatch = providerIdFromApp.toLowerCase();
1244
+ const isShriramPaymentCatch = paymentProviderIdCatch === ShriramProviderId.toLowerCase();
1245
+ const isMahindraPaymentCatch = paymentProviderIdCatch === MahindraProviderId.toLowerCase();
1246
+
1247
+ if (isShriramPaymentCatch && appData) {
1248
+ if (!initializeExternalSDKFromFDList('shriram')) return;
1249
+ let ShriramSDKGlobalDataPayment: any = null;
1250
+ if (customerApplications && !isCustomerApplicationsEmpty()) {
1251
+ let applicationsData: any[] = [];
1252
+ if (Array.isArray(customerApplications)) applicationsData = customerApplications;
1253
+ else if (customerApplications?.data && Array.isArray(customerApplications.data)) applicationsData = customerApplications.data;
1254
+ else if (customerApplications?.applications && Array.isArray(customerApplications.applications)) applicationsData = customerApplications.applications;
1255
+ const completeApplications = applicationsData.filter((app: any) => {
1256
+ const s = (app.wf_status || app.status || '').toString().toLowerCase();
1257
+ return s === 'completed' || s === 'complete';
1258
+ });
1259
+ const shriramComplete = completeApplications.find((app: any) => (app.fd_provider_id || app.fdProviderId || app.provider_id || app.providerId || '').toLowerCase() === ShriramProviderId.toLowerCase());
1260
+ if (shriramComplete) ShriramSDKGlobalDataPayment = shriramComplete;
1261
+ }
1262
+ const fdListSelectedDataPayment = fdDisplay ? {
1263
+ id: appData.fd_id || appData.fdId || 'active-fd',
1264
+ providerId: providerIdFromApp,
1265
+ name: fdDisplay.name,
1266
+ accountNumber: appData.account_number || appData.accountNumber || '',
1267
+ roi: `${fdDisplay.returns}% p.a.`,
1268
+ tenure: fdDisplay.tenure || '-',
1269
+ amount: Number(fdDisplay.invested) || 0,
1270
+ maturityDate: fdDisplay.maturityDate || '-',
1271
+ status: 'active' as const,
1272
+ creditRating: getStaticCreditRatingForFD({
1273
+ name: fdDisplay.name,
1274
+ providerId: providerIdFromApp,
1275
+ }),
1276
+ companyName: fdDisplay.name,
1277
+ fdRate: `${fdDisplay.returns}% p.a.`,
1278
+ interestPayout: fdDisplay.interestPayout || 'Yearly',
1279
+ } : null;
1280
+ const masterDataPayment = ShriramMasterData;
1281
+ navigate('ExternalSDK', {
1282
+ sdkType: 'shriram',
1283
+ providerId: providerIdFromApp,
1284
+ fdListSelectedData: fdListSelectedDataPayment,
1285
+ masterData: masterDataPayment,
1286
+ onboardingIds: onboardingIdsFromApp || onboardingIds,
1287
+ initialRouteName: 'PaymentStatus',
1288
+ initialPaymentStatusParams: { status: 'pending', transactionId, fdData: fdDataParam },
1289
+ shriramSDKGlobalData: ShriramSDKGlobalDataPayment,
1290
+ } as any);
1291
+ return;
1292
+ }
1293
+
1294
+ if (isMahindraPaymentCatch && appData) {
1295
+ if (!initializeExternalSDKFromFDList('mahindra')) return;
1296
+ const fdListSelectedDataPayment = fdDisplay ? {
1297
+ id: appData.fd_id || appData.fdId || 'active-fd',
1298
+ providerId: providerIdFromApp,
1299
+ name: fdDisplay.name,
1300
+ accountNumber: appData.account_number || appData.accountNumber || '',
1301
+ roi: `${fdDisplay.returns}% p.a.`,
1302
+ tenure: fdDisplay.tenure || '-',
1303
+ amount: Number(fdDisplay.invested) || 0,
1304
+ maturityDate: fdDisplay.maturityDate || '-',
1305
+ status: 'active' as const,
1306
+ creditRating: getStaticCreditRatingForFD({
1307
+ name: fdDisplay.name,
1308
+ providerId: providerIdFromApp,
1309
+ }),
1310
+ companyName: fdDisplay.name,
1311
+ fdRate: `${fdDisplay.returns}% p.a.`,
1312
+ interestPayout: fdDisplay.interestPayout || 'Yearly',
1313
+ } : null;
1314
+ const masterDataPayment = MahindraMasterData;
1315
+ navigate('ExternalSDK', {
1316
+ sdkType: 'mahindra',
1317
+ providerId: providerIdFromApp,
1318
+ fdListSelectedData: fdListSelectedDataPayment,
1319
+ masterData: masterDataPayment,
1320
+ onboardingIds: onboardingIdsFromApp || onboardingIds,
1321
+ initialRouteName: 'PaymentStatus',
1322
+ initialPaymentStatusParams: { status: 'pending', transactionId, fdData: fdDataParam },
1323
+ } as any);
1324
+ return;
1325
+ }
1326
+
1327
+ navigate('PaymentStatus', { status: 'pending', transactionId, fdData: fdDataParam } as any);
1328
+ return;
1329
+ ;
1330
+ }
1331
+ }
1332
+
1333
+ // Determine provider and navigate to appropriate SDK
1334
+ if (appData) {
1335
+ let providerIdFromApp = resolveProviderIdFromApp(appData, MahindraProviderId, ShriramProviderId);
1336
+ if (forcedSdkType === 'mahindra') providerIdFromApp = MahindraProviderId;
1337
+ if (forcedSdkType === 'shriram') providerIdFromApp = ShriramProviderId;
1338
+ const providerIdLower = (providerIdFromApp || '').toLowerCase();
1339
+ const isMahindra = providerIdLower === MahindraProviderId.toLowerCase();
1340
+ const isShriram = providerIdLower === ShriramProviderId.toLowerCase();
1341
+
1342
+ // Check for complete applications and create global data constants
1343
+ let ShriramSDKGlobalData: any = null;
1344
+ let MahindraSDKGlobalData: any = null;
1345
+
1346
+ if (customerApplications && !isCustomerApplicationsEmpty()) {
1347
+ let applicationsData: any[] = [];
1348
+ if (Array.isArray(customerApplications)) {
1349
+ applicationsData = customerApplications;
1350
+ } else if (customerApplications.data && Array.isArray(customerApplications.data)) {
1351
+ applicationsData = customerApplications.data;
1352
+ } else if (customerApplications.applications && Array.isArray(customerApplications.applications)) {
1353
+ applicationsData = customerApplications.applications;
1354
+ }
1355
+
1356
+ // Find complete applications
1357
+ const completeApplications = applicationsData.filter((app: any) => {
1358
+ const status = (app.wf_status || app.status || '').toString().toLowerCase();
1359
+ return status === 'completed' || status === 'complete';
1360
+ });
1361
+
1362
+ // Separate by provider
1363
+ completeApplications.forEach((app: any) => {
1364
+ const appProviderId = (app.fd_provider_id || app.fdProviderId || app.provider_id || app.providerId || '').toLowerCase();
1365
+ if (appProviderId === ShriramProviderId.toLowerCase()) {
1366
+ ShriramSDKGlobalData = app;
1367
+ } else if (appProviderId === MahindraProviderId.toLowerCase()) {
1368
+ MahindraSDKGlobalData = app;
1369
+ }
1370
+ });
1371
+ }
1372
+
1373
+ // Helper function to map workflow state to screen name
1374
+ const getScreenNameFromState = (state: string, isMahindraSDK: boolean, isShriramSDK: boolean): string => {
1375
+ if (isMahindraSDK) {
1376
+ if (state === MAHINDRA_WORKFLOW_STATES.KYC) return 'AadhaarVerification';
1377
+ if (state === MAHINDRA_WORKFLOW_STATES.DEMOGRAPHIC_INFO) return 'ReviewKYC';
1378
+ if (state === MAHINDRA_WORKFLOW_STATES.OCCUPATION) return 'Employee';
1379
+ if (state === MAHINDRA_WORKFLOW_STATES.NOMINEE) return 'NomineeDetail';
1380
+ if (state === MAHINDRA_WORKFLOW_STATES.BANK_DETAILS) return 'BankDetail';
1381
+ if (state === MAHINDRA_WORKFLOW_STATES.AADHAAR_VERIFICATION) return 'AadhaarVerification';
1382
+ if (state === MAHINDRA_WORKFLOW_STATES.PAYMENT) return 'KYCSuccess';
1383
+ if (state === MAHINDRA_WORKFLOW_STATES.CREATE_FD) return 'PayNow';
1384
+ } else if (isShriramSDK) {
1385
+ if (state === SHRIRAM_WORKFLOW_STATES.KYC) return 'ReviewKYC';
1386
+ if (state === SHRIRAM_WORKFLOW_STATES.OCCUPATION) return 'Employee';
1387
+ if (state === SHRIRAM_WORKFLOW_STATES.NOMINEE) return 'NomineeDetail';
1388
+ if (state === SHRIRAM_WORKFLOW_STATES.BANK_DETAILS) return 'BankDetail';
1389
+ if (state === SHRIRAM_WORKFLOW_STATES.PAYMENT) return 'PayNow';
1390
+ // State 411 (CREATE_FD): user has completed up to FD creation; next step is payment → open PayNow
1391
+ if (state === SHRIRAM_WORKFLOW_STATES.CREATE_FD) return 'PayNow';
1392
+ if (state === SHRIRAM_WORKFLOW_STATES.END) return 'PayNow';
1393
+ }
1394
+ // Default fallback
1395
+ return 'FDCalculator';
1396
+ };
1397
+
1398
+ // Align pending flow with new-FD reliability: fetch provider-scoped master data right before navigation.
1399
+ let providerMasterData = isMahindra ? MahindraMasterData : isShriram ? ShriramMasterData : undefined;
1400
+ try {
1401
+ if (isMahindra) {
1402
+ const refreshed = await refetchMahindraMasterData();
1403
+ providerMasterData = refreshed?.data ?? providerMasterData;
1404
+ } else if (isShriram) {
1405
+ const refreshed = await refetchShriramMasterData();
1406
+ providerMasterData = refreshed?.data ?? providerMasterData;
1407
+ }
1408
+ } catch {
1409
+ // keep previously cached provider-specific master data
1410
+ }
1411
+
1412
+ const masterDataToPass = providerMasterData || (masterData || masterDataContext);
1413
+
1414
+ // Get onboardingIds - use the one from appData if available, otherwise from Redux store
1415
+ const onboardingIdsToPass = onboardingIdsFromApp || onboardingIds;
1416
+
1417
+ // Get fdListSelectedData (already set in Redux, but we need it for navigation)
1418
+ // Use fdDisplay (derived from the tapped app) not activeFD (always first active app)
1419
+ const fdListSelectedDataToPass = fdDisplay ? {
1420
+ id: appData.fd_id || appData.fdId || 'active-fd',
1421
+ providerId: providerIdFromApp,
1422
+ name: fdDisplay.name,
1423
+ accountNumber: appData.account_number || appData.accountNumber || '',
1424
+ roi: `${fdDisplay.returns}% p.a.`,
1425
+ tenure: appData.tenure_in_months
1426
+ ? `${appData.tenure_in_months} Months`
1427
+ : '-',
1428
+ amount: Number(fdDisplay.invested) || 0,
1429
+ maturityDate: fdDisplay.maturityDate || '-',
1430
+ status: 'active' as const,
1431
+ creditRating: getStaticCreditRatingForFD({
1432
+ name: fdDisplay.name,
1433
+ providerId: providerIdFromApp,
1434
+ }),
1435
+ companyName: fdDisplay.name,
1436
+ fdRate: `${fdDisplay.returns}% p.a.`,
1437
+ interestPayout: fdDisplay.interestPayout || '',
1438
+ } : null;
1439
+
1440
+ // Navigate to appropriate SDK if provider is detected
1441
+ if (isMahindra || isShriram) {
1442
+ const screenName = getScreenNameFromState(currentStateCaption, isMahindra, isShriram);
1443
+ const sdkType = isMahindra ? 'mahindra' : 'shriram';
1444
+ if (!initializeExternalSDKFromFDList(sdkType)) return;
1445
+ // #region agent log
1446
+ fetch('http://127.0.0.1:7653/ingest/94b18a39-caf3-48fd-98f5-bf1838d9491a', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Debug-Session-Id': '04da7c' }, body: JSON.stringify({ sessionId: '04da7c', runId: 'pending-masterdata-trace', hypothesisId: 'H2', location: 'fdsdk/screens/FDList.tsx:1315', message: 'Pending continue pre-navigation payload', data: { forcedSdkType: forcedSdkType || null, resolvedSdkType: sdkType, providerIdFromApp: providerIdFromApp || null, onboardingProviderId: onboardingIdsToPass?.providerId || null, fdListSelectedProviderId: fdListSelectedDataToPass?.providerId || null, usingMahindraMasterData: !!(sdkType === 'mahindra' && masterDataToPass), usingShriramMasterData: !!(sdkType === 'shriram' && masterDataToPass), masterDataHasDataKey: !!masterDataToPass?.data }, timestamp: Date.now() }) }).catch(() => { });
1447
+ // #endregion agent log
1448
+
1449
+ navigate('ExternalSDK', {
1450
+ sdkType,
1451
+ providerId: providerIdFromApp,
1452
+ fdListSelectedData: fdListSelectedDataToPass,
1453
+ masterData: masterDataToPass,
1454
+ onboardingIds: onboardingIdsToPass,
1455
+ initialRouteName: screenName, // Pass the screen name to navigate to
1456
+ shriramSDKGlobalData: ShriramSDKGlobalData, // Pass complete Shriram application data
1457
+ mahindraSDKGlobalData: MahindraSDKGlobalData, // Pass complete Mahindra application data
1458
+ // Let Mahindra SDK fetch details itself when launched from host (Mahindra only)
1459
+ ...(isMahindra ? { forceFetchCustomerDetails: true } : {}),
1460
+ });
1461
+ return;
1462
+ }
1463
+ }
1464
+
1465
+ // Fallback: Use workflow navigation based on current state (non-payment states or no transactionId)
1466
+ if (currentStateCaption) {
1467
+ handleWorkflowNavigation({
1468
+ workflowState: currentStateCaption,
1469
+ investmentData: undefined,
1470
+ customerData: undefined,
1471
+ appData: undefined,
1472
+ fdData: undefined,
1473
+ completedApplications: [],
1474
+ transactionId,
1475
+ });
1476
+ } else {
1477
+ navigate('Employee');
1478
+ }
1479
+ } finally {
1480
+ setIsPendingFDLoading(false);
1481
+ }
1482
+ };
1483
+
1484
+ // Derive identifiers for workflow termination from applications API
1485
+ // Derive Active FD card data from customer applications response
1486
+ const activeFD = React.useMemo(() => {
1487
+ try {
1488
+ if (!customerApplications || isCustomerApplicationsEmpty()) {
1489
+ return {
1490
+ name: 'Mahindra Finance Ltd',
1491
+ invested: 0,
1492
+ value: 0,
1493
+ returns: 0,
1494
+ maturityDate: '--',
1495
+ tenure_in_months: 0,
1496
+ interestPayout: 'Yearly',
1497
+ logoType: 'mahindra' as const,
1498
+ };
1499
+ }
1500
+
1501
+ // Normalize list
1502
+ let applicationsData: any[] = [];
1503
+ if (Array.isArray(customerApplications)) {
1504
+ applicationsData = customerApplications;
1505
+ } else if (customerApplications.data && Array.isArray(customerApplications.data)) {
1506
+ applicationsData = customerApplications.data;
1507
+ } else if (customerApplications.applications && Array.isArray(customerApplications.applications)) {
1508
+ applicationsData = customerApplications.applications;
1509
+ }
1510
+
1511
+ if (applicationsData.length === 0) {
1512
+ return {
1513
+ name: 'Mahindra Finance Ltd',
1514
+ invested: 0,
1515
+ value: 0,
1516
+ returns: 0,
1517
+ maturityDate: '--',
1518
+ tenure_in_months: 0,
1519
+ interestPayout: 'Yearly',
1520
+ logoType: 'mahindra' as const,
1521
+ };
1522
+ }
1523
+
1524
+ // Pick an active application if available, otherwise first
1525
+ const activeApp = applicationsData.find((a: any) => {
1526
+ const s = (a.wf_status || a.status || '').toString();
1527
+ return s === 'Active';
1528
+ }) || applicationsData[0];
1529
+
1530
+ const invested = Number(
1531
+ activeApp.investment_amount ?? activeApp.amount ?? activeApp.investmentAmount ?? 0
1532
+ );
1533
+ const rate = Number(
1534
+ activeApp.interest_rate ?? activeApp.interestRate ?? 0
1535
+ );
1536
+
1537
+ // Prefer a provided current value, then maturity amount
1538
+ const value = Number(
1539
+ activeApp.current_value ?? activeApp.currentValue ?? activeApp.maturity_amount ?? activeApp.maturityAmount ?? 0
1540
+ );
1541
+
1542
+ const returns = rate || 0; // display interest rate as returns %
1543
+
1544
+ // Format maturity date to 'Mon YYYY'
1545
+ const maturityDateStr: string = activeApp.maturity_date || activeApp.maturityDate || '';
1546
+ let maturityDate = '--';
1547
+ if (maturityDateStr) {
1548
+ const d = new Date(maturityDateStr);
1549
+ if (!isNaN(d.getTime())) {
1550
+ const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
1551
+ maturityDate = `${months[d.getMonth()]} ${d.getFullYear()}`;
1552
+ }
1553
+ }
1554
+
1555
+ // Get interest payout term from activeApp
1556
+ const interestPayout = activeApp.interest_payout_term || activeApp.interestPayoutTerm || activeApp.payout_term || 'Yearly';
1557
+
1558
+ const providerName = activeApp.fd_provider_name || activeApp.providerName || 'Mahindra Finance Ltd';
1559
+ const logoType = getLogoTypeForFD({ name: providerName, providerId: activeApp.fd_provider_id || activeApp.fdProviderId || '' });
1560
+
1561
+ return {
1562
+ name: providerName,
1563
+ invested,
1564
+ value,
1565
+ returns,
1566
+ maturityDate,
1567
+ tenure_in_months: activeApp.tenure_in_months ?? 0,
1568
+ interestPayout,
1569
+ logoType,
1570
+ };
1571
+ } catch (e) {
1572
+ return {
1573
+ name: 'Mahindra Finance Ltd',
1574
+ invested: 0,
1575
+ value: 0,
1576
+ returns: 0,
1577
+ maturityDate: '--',
1578
+ tenure_in_months: 0,
1579
+ interestPayout: 'Yearly',
1580
+ logoType: 'mahindra' as const,
1581
+ };
1582
+ }
1583
+ }, [customerApplications]);
1584
+
1585
+ // All pending/active FDs (one per application with Active or pending/in_progress) – for multiple providers
1586
+ const activeFDs = React.useMemo(() => {
1587
+ try {
1588
+ if (!customerApplications || isCustomerApplicationsEmpty()) return [];
1589
+ let applicationsData: any[] = [];
1590
+ if (Array.isArray(customerApplications)) {
1591
+ applicationsData = customerApplications;
1592
+ } else if (customerApplications.data && Array.isArray(customerApplications.data)) {
1593
+ applicationsData = customerApplications.data;
1594
+ } else if (customerApplications.applications && Array.isArray(customerApplications.applications)) {
1595
+ applicationsData = customerApplications.applications;
1596
+ }
1597
+ const pendingActive = applicationsData.filter((a: any) => {
1598
+ const s = (a.wf_status || a.status || '').toString().toLowerCase();
1599
+ return PENDING_ACTIVE_STATUSES.includes(s);
1600
+ });
1601
+ return pendingActive.map((app: any) => ({
1602
+ ...appToPendingFDData(app),
1603
+ app,
1604
+ }));
1605
+ } catch (e) {
1606
+ return [];
1607
+ }
1608
+ }, [customerApplications]);
1609
+
1610
+ // Normalized applications list for provider-scoped lookups
1611
+ const applicationsDataNormalized = React.useMemo(() => {
1612
+ if (!customerApplications || isCustomerApplicationsEmpty()) return [];
1613
+ if (Array.isArray(customerApplications)) return customerApplications;
1614
+ if (customerApplications.data && Array.isArray(customerApplications.data)) return customerApplications.data;
1615
+ if (customerApplications.applications && Array.isArray(customerApplications.applications)) return customerApplications.applications;
1616
+ return [];
1617
+ }, [customerApplications]);
1618
+
1619
+ // Helper function to check if there's any active/pending FD
1620
+ const hasActiveFD = React.useMemo(() => activeFDs.length > 0, [activeFDs]);
1621
+
1622
+ // Pending FD data for bottom sheet: from the app we're showing (provider they tapped), or fallback to first active
1623
+ const pendingFDDataForSheet = React.useMemo(() => {
1624
+ if (pendingContextApp) return appToPendingFDData(pendingContextApp);
1625
+ if (activeFDs.length > 0) return activeFDs[0]; // fallback: first card data (no app ref needed for display)
1626
+ return activeFD;
1627
+ }, [pendingContextApp, activeFDs, activeFD]);
1628
+
1629
+ // Build FD options list from interest-rate API (show all FDs)
1630
+ const fdList = React.useMemo(() => {
1631
+ if (interestRates && (interestRates.data?.sdrScheme?.length > 0 || interestRates.data?.fdrScheme?.length > 0)) {
1632
+ const sdr = Array.isArray(interestRates.data?.sdrScheme) ? interestRates.data.sdrScheme : [];
1633
+ const fdr = Array.isArray(interestRates.data?.fdrScheme) ? interestRates.data.fdrScheme : [];
1634
+ const combined = [...sdr, ...fdr];
1635
+
1636
+ // Building FD options from rates
1637
+
1638
+ return combined.map((rate: any, index: number) => ({
1639
+ id: `${rate.providerId || 'provider'}-${rate.perdMonth || index}`,
1640
+ providerId: rate.providerId || `provider-${index}`,
1641
+ name: rate.providerName || '', // Use actual provider name from API
1642
+ accountNumber: `FD${String(index + 1).padStart(9, '0')}`,
1643
+ roi: `${Number(rate.rate ?? rate.interestRate ?? 0).toFixed(2)}% p.a.`,
1644
+ tenure: `${rate.perdMonth} Months`,
1645
+ amount: 5000,
1646
+ maturityDate: '2025-12-31',
1647
+ status: 'active' as const,
1648
+ creditRating: getStaticCreditRatingForFD({
1649
+ name: rate.providerName || '',
1650
+ providerId: rate.providerId || `provider-${index}`,
1651
+ }),
1652
+ interestPayout: rate.interestPayout || rate.interest_payout_term || rate.payout_term || 'Yearly'
1653
+ }));
1654
+ }
1655
+
1656
+ // If rates are not available yet, show empty list
1657
+ return [];
1658
+ }, [interestRates]);
1659
+
1660
+
1661
+ // Get filtered FD list based on active tab
1662
+ const filteredFDList = React.useMemo(() => {
1663
+ if (!fdList || fdList.length === 0) return [];
1664
+
1665
+ // Always create a new filtered array
1666
+ const filtered = filterFDsByTenure([...fdList], activeTab); // spread to ensure new array
1667
+
1668
+ return filtered;
1669
+ }, [fdList, activeTab]);
1670
+
1671
+
1672
+ const tabs = [FD_STRINGS.ALL_FDS_TAB, FD_STRINGS.LESS_THAN_1Y_TAB, FD_STRINGS.LESS_THAN_1_3Y_TAB, FD_STRINGS.LESS_THAN_3_5Y_TAB, FD_STRINGS.GREATER_THAN_5Y_TAB];
1673
+ const poweredByLogo = (getAppData()?.poweredByLogo || '').trim();
1674
+
1675
+ if (!isAllDataReady) {
1676
+ return (
1677
+ <SafeAreaWrapper
1678
+ style={customStyles.container}
1679
+ includeTop={false}
1680
+ statusBarColor={colors.headerBg}
1681
+ statusBarStyle="light-content"
1682
+ >
1683
+ <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
1684
+ <ActivityIndicator size="large" color={colors.primary} />
1685
+ <Text style={{ marginTop: 12, color: colors.textSecondary }}>{FD_STRINGS.LOADING_FD_DATA}</Text>
1686
+ </View>
1687
+ </SafeAreaWrapper>
1688
+ );
1689
+ }
1690
+
1691
+ return (
1692
+ <SafeAreaWrapper
1693
+ style={customStyles.container}
1694
+ includeTop={false}
1695
+ statusBarColor={colors.headerBg}
1696
+ statusBarStyle="light-content"
1697
+ >
1698
+ {/* Custom FDList Header - same structure as Shriram FDList */}
1699
+ <View style={styles.customHeader}>
1700
+ <TouchableOpacity
1701
+ style={styles.backButton}
1702
+ onPress={onGoBack}
1703
+ activeOpacity={0.7}
1704
+ >
1705
+ <Image
1706
+ source={{ uri: base64Images.backArrow }}
1707
+ style={styles.backIcon}
1708
+ resizeMode="contain"
1709
+ />
1710
+ </TouchableOpacity>
1711
+
1712
+ <View style={styles.headerContent}>
1713
+ <Text style={styles.headerTitle}>{FD_STRINGS.CORPORATE_FDS_TITLE}</Text>
1714
+ {poweredByLogo ? (
1715
+ <View style={styles.poweredByContainer}>
1716
+ <Text style={styles.poweredByText}>Powered by</Text>
1717
+ <Image
1718
+ source={{ uri: poweredByLogo }}
1719
+ style={styles.simplifyLogo}
1720
+ resizeMode="contain"
1721
+ />
1722
+ </View>
1723
+ ) : null}
1724
+ </View>
1725
+ </View>
1726
+
1727
+ <ScrollView style={styles.content}>
1728
+
1729
+ {/* Active FDs Section - One card per pending/active FD (e.g. one Mahindra, one Shriram) */}
1730
+ {hasActiveFD && (
1731
+ <View style={styles.section}>
1732
+ <Text style={styles.sectionTitle}>{FD_STRINGS.ACTIVE_FDS_SECTION}</Text>
1733
+ {activeFDs.map((item, index) => (
1734
+ <TouchableOpacity
1735
+ key={item.app?.application_id || item.app?.applicationId || index}
1736
+ activeOpacity={0.8}
1737
+ onPress={() => handlePendingFDContinue(item.app, item.logoType)}
1738
+ style={index > 0 ? styles.activeFDCardSpacer : undefined}
1739
+ >
1740
+ <ActiveFDCard
1741
+ name={item.name}
1742
+ invested={item.invested}
1743
+ value={item.value}
1744
+ returns={item.returns}
1745
+ maturityDate={item.maturityDate}
1746
+ logoType={item.logoType}
1747
+ />
1748
+ </TouchableOpacity>
1749
+ ))}
1750
+ </View>
1751
+ )}
1752
+
1753
+ {/* Tabs */}
1754
+ <View style={styles.tabsContainer}>
1755
+ <View style={styles.tabsRow}>
1756
+ {tabs.map((tab) => (
1757
+ <TouchableOpacity
1758
+ key={tab}
1759
+ style={[
1760
+ styles.tab,
1761
+ activeTab === tab && styles.activeTab
1762
+ ]}
1763
+ onPress={() => {
1764
+ // Other tabs filter the data
1765
+ setActiveTab(tab);
1766
+ }}
1767
+ >
1768
+ <Text style={[
1769
+ styles.tabText,
1770
+ activeTab === tab && styles.activeTabText
1771
+ ]}>
1772
+ {tab}
1773
+ </Text>
1774
+ </TouchableOpacity>
1775
+ ))}
1776
+ </View>
1777
+ </View>
1778
+
1779
+ {/* Filter Status removed as requested */}
1780
+
1781
+ {/* FD List */}
1782
+ <View style={styles.fdOptionsContainer}>
1783
+ {filteredFDList.length > 0 ? (
1784
+ filteredFDList.map((fd: FDItem) => (
1785
+ <FDCard
1786
+ key={fd.id}
1787
+ id={fd.id}
1788
+ name={fd.name}
1789
+ accountNumber={fd.accountNumber}
1790
+ roi={fd.roi}
1791
+ tenure={fd.tenure}
1792
+ amount={fd.amount}
1793
+ maturityDate={fd.maturityDate}
1794
+ status={fd.status as 'active' | 'matured' | 'pending'}
1795
+ creditRating={fd.creditRating}
1796
+ logoType={getLogoTypeForFD(fd)}
1797
+ onPress={(fdId) => {
1798
+
1799
+
1800
+ // Log the selection
1801
+ onSelectFD?.(fdId);
1802
+
1803
+ // Store the selected FD data locally
1804
+ setSelectedFD(fd);
1805
+
1806
+ // Store the selected FD data in Redux for PayNow screen
1807
+ const fdListSelectedData = {
1808
+ id: fd.id,
1809
+ providerId: fd.providerId,
1810
+ name: fd.name,
1811
+ accountNumber: fd.accountNumber,
1812
+ roi: fd.roi,
1813
+ tenure: fd.tenure,
1814
+ amount: fd.amount,
1815
+ maturityDate: fd.maturityDate,
1816
+ status: fd.status as 'active' | 'matured' | 'pending',
1817
+ creditRating: fd.creditRating,
1818
+ // Additional fields for PayNow display
1819
+ companyName: fd.name,
1820
+ fdRate: fd.roi,
1821
+ interestPayout: fd.interestPayout || 'Yearly', // Use FD's interest payout or default
1822
+ };
1823
+
1824
+ dispatch(setFDListSelected(fdListSelectedData));
1825
+
1826
+ // 1st Place: Set onboardingIds when customer taps on any FD from FDList
1827
+ // Start with existing onboardingIds, but prefer the freshly computed ones for this tap.
1828
+ let onboardingIdsForNav: any = onboardingIds;
1829
+ try {
1830
+ let idsToSet: any = { providerId: fd.providerId };
1831
+
1832
+ // If we already have customer applications, enrich onboardingIds
1833
+ try {
1834
+ if (customerApplications && !isCustomerApplicationsEmpty()) {
1835
+ // Normalize list
1836
+ let applicationsData: any[] = [];
1837
+ if (Array.isArray(customerApplications)) {
1838
+ applicationsData = customerApplications;
1839
+ } else if (customerApplications.data && Array.isArray(customerApplications.data)) {
1840
+ applicationsData = customerApplications.data;
1841
+ } else if (customerApplications.applications && Array.isArray(customerApplications.applications)) {
1842
+ applicationsData = customerApplications.applications;
1843
+ }
1844
+
1845
+ if (applicationsData.length > 0) {
1846
+ // Prefer applications that belong to the same provider as the tapped FD
1847
+ const fdProviderIdLower = (fd.providerId || '').toLowerCase();
1848
+ const appsForProvider = applicationsData.filter((a: any) => {
1849
+ const appProviderId =
1850
+ (a.fd_provider_id ||
1851
+ a.fdProviderId ||
1852
+ a.provider_id ||
1853
+ a.providerId ||
1854
+ '').toLowerCase();
1855
+ return appProviderId === fdProviderIdLower;
1856
+ });
1857
+
1858
+ const scopedApplications =
1859
+ appsForProvider.length > 0 ? appsForProvider : applicationsData;
1860
+
1861
+ // Within the scoped list, still prefer the Active application
1862
+ const appData = scopedApplications.find((a: any) => {
1863
+ const s = (a.wf_status || a.status || '').toString();
1864
+ return s === 'Active';
1865
+ }) || scopedApplications[0];
1866
+
1867
+ idsToSet = {
1868
+ workflowInstanceId: appData.workflow_instance_id || appData.workflowInstanceId,
1869
+ applicationId: appData.application_id || appData.applicationId,
1870
+ entityid: appData.entity_id || appData.entityId,
1871
+ customerId: appData.customer_id || appData.customerId,
1872
+ fdId: appData.fd_id || appData.fdId,
1873
+ currentState: appData.current_state || appData.currentState,
1874
+ currentTask: appData.current_task || appData.currentTask,
1875
+ // Check both snake_case (fd_provider_id) and camelCase (fdProviderId) variants
1876
+ providerId: fd.providerId,
1877
+ wfStatus: appData.wf_status || appData.wfStatus,
1878
+ };
1879
+ }
1880
+ }
1881
+ } catch (innerError) {
1882
+ // If enrichment fails, fall back to providerId only
1883
+ }
1884
+
1885
+ dispatch(setOnboardingIds(idsToSet));
1886
+ onboardingIdsForNav = idsToSet;
1887
+ } catch (e) {
1888
+ }
1889
+
1890
+ // Check if this provider has a pending/active FD – show sheet only for that provider
1891
+ const pendingAppForProvider = getPendingOrActiveAppForProvider(applicationsDataNormalized, fd.providerId || '');
1892
+ if (pendingAppForProvider) {
1893
+ setPendingContextApp(pendingAppForProvider);
1894
+ setShowPendingFDBottomSheet(true);
1895
+ } else {
1896
+ // If no active FD, navigate to appropriate SDK FD calculator
1897
+ const fdProviderId = (fd.providerId || '').toLowerCase();
1898
+ const isMahindra = fdProviderId === MahindraProviderId.toLowerCase();
1899
+ const isShriram = fdProviderId === ShriramProviderId.toLowerCase();
1900
+
1901
+ // Check for complete applications and create global data constants
1902
+ let ShriramSDKGlobalData: any = null;
1903
+ let MahindraSDKGlobalData: any = null;
1904
+
1905
+ if (customerApplications && !isCustomerApplicationsEmpty()) {
1906
+ let applicationsData: any[] = [];
1907
+ if (Array.isArray(customerApplications)) {
1908
+ applicationsData = customerApplications;
1909
+ } else if (customerApplications.data && Array.isArray(customerApplications.data)) {
1910
+ applicationsData = customerApplications.data;
1911
+ } else if (customerApplications.applications && Array.isArray(customerApplications.applications)) {
1912
+ applicationsData = customerApplications.applications;
1913
+ }
1914
+
1915
+ // Find complete applications
1916
+ const completeApplications = applicationsData.filter((app: any) => {
1917
+ const status = (app.wf_status || app.status || '').toString().toLowerCase();
1918
+ return status === 'completed' || status === 'complete';
1919
+ });
1920
+
1921
+ // Separate by provider
1922
+ completeApplications.forEach((app: any) => {
1923
+ const appProviderId = (app.fd_provider_id || app.fdProviderId || app.provider_id || app.providerId || '').toLowerCase();
1924
+ if (appProviderId === ShriramProviderId.toLowerCase()) {
1925
+ ShriramSDKGlobalData = app;
1926
+ } else if (appProviderId === MahindraProviderId.toLowerCase()) {
1927
+ MahindraSDKGlobalData = app;
1928
+ }
1929
+ });
1930
+ }
1931
+
1932
+ if (isShriram) {
1933
+ navigate('ExternalSDK', {
1934
+ sdkType: 'shriram',
1935
+ providerId: fd.providerId,
1936
+ fdListSelectedData: fdListSelectedData,
1937
+ masterData: ShriramMasterData,
1938
+ // Use the freshest onboardingIds (from this tap if available)
1939
+ onboardingIds: onboardingIdsForNav,
1940
+ shriramSDKGlobalData: ShriramSDKGlobalData, // Pass complete Shriram application data
1941
+ mahindraSDKGlobalData: MahindraSDKGlobalData, // Pass complete Mahindra application data
1942
+ });
1943
+ } else if (isMahindra) {
1944
+ // Navigate to Mahindra SDK FD calculator
1945
+ navigate('ExternalSDK', {
1946
+ sdkType: 'mahindra',
1947
+ providerId: fd.providerId,
1948
+ fdListSelectedData: fdListSelectedData,
1949
+ masterData: MahindraMasterData,
1950
+ // Use the freshest onboardingIds (from this tap if available)
1951
+ onboardingIds: onboardingIdsForNav,
1952
+ shriramSDKGlobalData: ShriramSDKGlobalData, // Pass complete Shriram application data
1953
+ mahindraSDKGlobalData: MahindraSDKGlobalData, // Pass complete Mahindra application data
1954
+ });
1955
+ } else {
1956
+ // Fallback to default FD calculator
1957
+ onNavigateToFDCalculator?.(fd, masterData);
1958
+ }
1959
+ }
1960
+ }}
1961
+ // onPress={(fdId) => {
1962
+ // const fdProviderId = (fd.providerId || '').toLowerCase();
1963
+ // const isMahindra = fdProviderId === MahindraProviderId.toLowerCase();
1964
+ // const isShriram = fdProviderId === ShriramProviderId.toLowerCase();
1965
+
1966
+ // // Construct FDListSelectedData object
1967
+ // const fdListSelectedData = {
1968
+ // id: fd.id,
1969
+ // providerId: fd.providerId,
1970
+ // name: fd.name,
1971
+ // accountNumber: fd.accountNumber,
1972
+ // roi: fd.roi,
1973
+ // tenure: fd.tenure,
1974
+ // amount: fd.amount,
1975
+ // maturityDate: fd.maturityDate,
1976
+ // status: fd.status as 'active' | 'matured' | 'pending',
1977
+ // creditRating: fd.creditRating,
1978
+ // // Additional fields for PayNow display
1979
+ // companyName: fd.name,
1980
+ // fdRate: fd.roi,
1981
+ // interestPayout: fd.interestPayout || 'Yearly', // Use FD's interest payout or default
1982
+ // };
1983
+
1984
+ // console.log('[FDList][onPress] ===== FD Tapped - Data Logs =====');
1985
+ // console.log('[FDList][onPress] FDListSelectedData:', fdListSelectedData);
1986
+ // console.log('[FDList][onPress] OnboardingIds:', onboardingIds);
1987
+
1988
+ // if (isMahindra) {
1989
+ // console.log('[FDList][onPress] Mahindra MasterData:', MahindraMasterData);
1990
+ // } else if (isShriram) {
1991
+ // console.log('[FDList][onPress] Shriram MasterData:', ShriramMasterData);
1992
+ // } else {
1993
+ // console.log('[FDList][onPress] MasterData (fallback):', masterData || masterDataContext);
1994
+ // }
1995
+
1996
+ // console.log('[FDList][onPress] ====================================');
1997
+ // }}
1998
+ customStyles={customStyles}
1999
+ />
2000
+ ))
2001
+ ) : (
2002
+ <View style={styles.noDataContainer}>
2003
+ <Text style={styles.noDataText}>
2004
+ {isLoadingRates || isLoadingApplications ? FD_STRINGS.LOADING_FD_DATA :
2005
+ interestRatesError || customerApplicationsError ? FD_STRINGS.FAILED_TO_LOAD_FD_DATA :
2006
+ fdList.length === 0 ? (!isCustomerApplicationsEmpty() ? FD_STRINGS.NO_CUSTOMER_FDS_FOUND : FD_STRINGS.NO_FD_OPTIONS_AVAILABLE) :
2007
+ `${FD_STRINGS.NO_FDS_FOUND_FOR_TENURE} "${activeTab}" tenure range.`}
2008
+ </Text>
2009
+
2010
+ </View>
2011
+ )}
2012
+ </View>
2013
+ </ScrollView>
2014
+
2015
+ {/* Pending FD Bottom Sheet - shows pending/active FD for the provider they tapped */}
2016
+ <PendingFDBottomSheet
2017
+ visible={showPendingFDBottomSheet}
2018
+ onClose={() => {
2019
+ setShowPendingFDBottomSheet(false);
2020
+ setPendingContextApp(null);
2021
+ }}
2022
+ pendingFDData={pendingFDDataForSheet}
2023
+ isBookingNewLoading={isTerminatingWorkflow}
2024
+ onContinue={async () => {
2025
+ setShowPendingFDBottomSheet(false);
2026
+ const forcedFromSelected = getSdkTypeFromProviderId(
2027
+ selectedFD?.providerId || '',
2028
+ MahindraProviderId,
2029
+ ShriramProviderId
2030
+ ) || undefined;
2031
+ await handlePendingFDContinue(undefined, forcedFromSelected);
2032
+ }}
2033
+ onBookNew={async () => {
2034
+ try {
2035
+ // Terminate only the workflow for the provider we're showing (the one they tapped)
2036
+ const appToTerminate = pendingContextApp || (activeFDs.length > 0 ? activeFDs[0].app : null);
2037
+ const providerIdForTerminate = appToTerminate
2038
+ ? (appToTerminate.fd_provider_id || appToTerminate.fdProviderId || appToTerminate.provider_id || appToTerminate.providerId)
2039
+ : (selectedFD?.providerId || inferredProviderId);
2040
+ const requestParams = {
2041
+ providerId: providerIdForTerminate,
2042
+ workflowInstanceId: appToTerminate?.workflow_instance_id || appToTerminate?.workflowInstanceId || terminateIdentifiers.workflowInstanceId || 'wf-instance-unknown',
2043
+ entityId: appToTerminate?.entity_id || appToTerminate?.entityId || terminateIdentifiers.entityId || 'entity-unknown',
2044
+ applicationId: appToTerminate?.application_id || appToTerminate?.applicationId || terminateIdentifiers.applicationId || 'application-unknown',
2045
+ } as const;
2046
+
2047
+ // Call workflow terminate API (only for this provider's workflow)
2048
+ await terminateWorkflow(requestParams);
2049
+
2050
+ // 2nd Place: Set onboardingIds when user taps "I want to create new"
2051
+ try {
2052
+ const providerIdToSet = selectedFD?.providerId || inferredProviderId || getProviderIdFromInterestRates();
2053
+ if (providerIdToSet) {
2054
+ const idsToSet = { providerId: providerIdToSet };
2055
+ dispatch(setOnboardingIds(idsToSet));
2056
+ } else {
2057
+ }
2058
+ } catch (e) {
2059
+ }
2060
+
2061
+ // Dismiss bottom sheet
2062
+ setShowPendingFDBottomSheet(false);
2063
+
2064
+ // Navigate to provider-specific SDK (Mahindra/Shriram) just like new FD tap
2065
+ const providerIdToUse = (selectedFD?.providerId || inferredProviderId || getProviderIdFromInterestRates() || '').toLowerCase();
2066
+ const isMahindra = providerIdToUse === MahindraProviderId.toLowerCase();
2067
+ const isShriram = providerIdToUse === ShriramProviderId.toLowerCase();
2068
+
2069
+ // Build fdListSelectedData from selectedFD when available (best effort)
2070
+ const fdListSelectedDataToPass = selectedFD ? {
2071
+ id: selectedFD.id || 'new-fd',
2072
+ providerId: selectedFD.providerId,
2073
+ name: selectedFD.name,
2074
+ accountNumber: selectedFD.accountNumber,
2075
+ roi: selectedFD.roi,
2076
+ tenure: selectedFD.tenure,
2077
+ amount: selectedFD.amount,
2078
+ maturityDate: selectedFD.maturityDate,
2079
+ status: selectedFD.status as 'active' | 'matured' | 'pending' | undefined,
2080
+ creditRating: getStaticCreditRatingForFD({
2081
+ name: selectedFD.name,
2082
+ providerId: selectedFD.providerId,
2083
+ }),
2084
+ companyName: selectedFD.name,
2085
+ fdRate: selectedFD.roi,
2086
+ interestPayout: selectedFD.interestPayout || 'Yearly',
2087
+ } : undefined;
2088
+
2089
+ const masterDataToPass = isMahindra
2090
+ ? MahindraMasterData
2091
+ : isShriram
2092
+ ? ShriramMasterData
2093
+ : (masterData || masterDataContext);
2094
+
2095
+ // Recompute complete application global data constants (same as tap flow)
2096
+ let ShriramSDKGlobalData: any = null;
2097
+ let MahindraSDKGlobalData: any = null;
2098
+ try {
2099
+ if (customerApplications && !isCustomerApplicationsEmpty()) {
2100
+ let applicationsData: any[] = [];
2101
+ if (Array.isArray(customerApplications)) {
2102
+ applicationsData = customerApplications;
2103
+ } else if (customerApplications.data && Array.isArray(customerApplications.data)) {
2104
+ applicationsData = customerApplications.data;
2105
+ } else if (customerApplications.applications && Array.isArray(customerApplications.applications)) {
2106
+ applicationsData = customerApplications.applications;
2107
+ }
2108
+
2109
+ const completeApplications = applicationsData.filter((app: any) => {
2110
+ const status = (app.wf_status || app.status || '').toString().toLowerCase();
2111
+ return status === 'completed' || status === 'complete';
2112
+ });
2113
+
2114
+ completeApplications.forEach((app: any) => {
2115
+ const appProviderId = (app.fd_provider_id || app.fdProviderId || app.provider_id || app.providerId || '').toLowerCase();
2116
+ if (appProviderId === ShriramProviderId.toLowerCase()) {
2117
+ ShriramSDKGlobalData = app;
2118
+ } else if (appProviderId === MahindraProviderId.toLowerCase()) {
2119
+ MahindraSDKGlobalData = app;
2120
+ }
2121
+ });
2122
+ }
2123
+ } catch (e) {
2124
+ // ignore errors
2125
+ }
2126
+
2127
+ if (isMahindra || isShriram) {
2128
+ navigate('ExternalSDK', {
2129
+ sdkType: isMahindra ? 'mahindra' : 'shriram',
2130
+ providerId: selectedFD?.providerId || inferredProviderId,
2131
+ fdListSelectedData: fdListSelectedDataToPass,
2132
+ masterData: masterDataToPass,
2133
+ onboardingIds: onboardingIds,
2134
+ shriramSDKGlobalData: ShriramSDKGlobalData,
2135
+ mahindraSDKGlobalData: MahindraSDKGlobalData,
2136
+ });
2137
+ } else {
2138
+ // Fallback to host FD calculator
2139
+ onNavigateToFDCalculator?.();
2140
+ }
2141
+
2142
+ } catch (error) {
2143
+ // Handle error silently
2144
+
2145
+ // Still dismiss bottom sheet and navigate even if API fails
2146
+ // setShowPendingFDBottomSheet(false);
2147
+ // onNavigateToFDCalculator?.();
2148
+
2149
+ // Show error alert
2150
+ Alert.alert(
2151
+ 'Warning',
2152
+ 'Failed to terminate existing workflow, but continuing with new FD booking.',
2153
+ [{ text: 'OK' }]
2154
+ );
2155
+ }
2156
+ }}
2157
+ />
2158
+
2159
+ {/* Full-screen loader: shown from pending FD tap until navigation completes */}
2160
+ {(isPendingFDLoading || isLoadingMahindraCustomerDetails) && (
2161
+ <View style={styles.loadingOverlay} pointerEvents="auto">
2162
+ <ActivityIndicator size="large" color={colors.primary} />
2163
+ </View>
2164
+ )}
2165
+ </SafeAreaWrapper>
2166
+ );
2027
2167
  };
2028
2168
 
2029
2169
  const createStyles = (colors: ColorScheme, typography: any, spacing: any, themeName: ThemeName) => StyleSheet.create({
2030
- container: {
2031
- flex: 1,
2032
- backgroundColor: colors.surface,
2033
- },
2034
- customHeader: {
2035
- backgroundColor: colors.headerBg,
2036
- paddingTop: Platform.OS === 'ios' ? 50 : 20,
2037
- paddingBottom: spacing.md,
2038
- paddingHorizontal: spacing.lg,
2039
- flexDirection: 'row',
2040
- alignItems: 'center',
2041
- },
2042
- backButton: {
2043
- width: 40,
2044
- height: 40,
2045
- alignItems: 'center',
2046
- justifyContent: 'center',
2047
- },
2048
- backIcon: {
2049
- width: 24,
2050
- height: 24,
2051
- tintColor: colors.headerText,
2052
- },
2053
- headerContent: {
2054
- flex: 1,
2055
- alignItems: 'flex-start',
2056
- marginRight: 40, // Balance the back button width
2057
- },
2058
- headerTitle: {
2059
- ...typography.styles.h3,
2060
- color: colors.headerText,
2061
- marginBottom: spacing.xs,
2062
- },
2063
- poweredByContainer: {
2064
- flexDirection: 'row',
2065
- alignItems: 'center',
2066
- },
2067
- poweredByText: {
2068
- ...typography.styles.text10Regular,
2069
- color: colors.headerText,
2070
- opacity: 0.7,
2071
- marginRight: 0,
2072
- },
2073
- simplifyLogo: {
2074
- width: 70,
2075
- height: 20,
2076
- },
2077
- content: {
2078
- flex: 1,
2079
- },
2080
- section: {
2081
- paddingHorizontal: spacing.lg,
2082
- paddingTop: spacing.xl,
2083
- },
2084
- sectionTitle: {
2085
- ...typography.styles.h3,
2086
- color: colors.text,
2087
- marginBottom: spacing.lg,
2088
- },
2089
- activeFDCardSpacer: {
2090
- marginTop: spacing.md,
2091
- },
2092
- tabsContainer: {
2093
- paddingHorizontal: spacing.lg,
2094
- paddingTop: spacing.xxl,
2095
- paddingBottom: spacing.lg,
2096
- },
2097
- tabsRow: {
2098
- flexDirection: 'row',
2099
- justifyContent: 'space-around',
2100
- alignItems: 'center',
2101
- },
2102
- tab: {
2103
- flex: 1,
2104
- paddingHorizontal: spacing.sm,
2105
- paddingVertical: spacing.md,
2106
- marginHorizontal: 2,
2107
- borderBottomWidth: 3,
2108
- borderBottomColor: colors.surface,
2109
- borderBottomLeftRadius: 8,
2110
- borderBottomRightRadius: 8,
2111
- backgroundColor: 'transparent',
2112
- },
2113
- activeTab: {
2114
- borderBottomColor: colors.tabSelected,
2115
- backgroundColor: colors.background,
2116
- borderBottomLeftRadius: 12,
2117
- borderBottomRightRadius: 12,
2118
- },
2119
- tabText: {
2120
- ...typography.styles.text12Medium,
2121
- color: colors.textSecondary,
2122
- textAlign: 'center',
2123
- },
2124
- activeTabText: {
2125
- color: colors.tabSelected,
2126
- fontWeight: '600',
2127
- textAlign: 'center',
2128
- },
2129
- filterStatusContainer: {
2130
- paddingHorizontal: spacing.lg,
2131
- paddingVertical: spacing.sm,
2132
- backgroundColor: colors.background + '80', // 50% opacity
2133
- borderBottomWidth: 1,
2134
- borderBottomColor: colors.border + '20', // 20% opacity
2135
- },
2136
- filterStatusText: {
2137
- ...typography.styles.text12Regular,
2138
- color: colors.textSecondary,
2139
- textAlign: 'center',
2140
- fontStyle: 'italic',
2141
- },
2142
- fdOptionsContainer: {
2143
- paddingHorizontal: spacing.lg,
2144
- paddingBottom: spacing.xl,
2145
- },
2146
- // API Status Styles
2147
- apiStatusContainer: {
2148
- flexDirection: 'row',
2149
- justifyContent: 'space-between',
2150
- alignItems: 'center',
2151
- paddingHorizontal: spacing.lg,
2152
- paddingVertical: spacing.sm,
2153
- backgroundColor: colors.background,
2154
- borderBottomWidth: 1,
2155
- borderBottomColor: colors.border + '20', // 20% opacity
2156
- },
2157
- apiStatusIndicator: {
2158
- flexDirection: 'row',
2159
- alignItems: 'center',
2160
- flex: 1,
2161
- },
2162
- apiStatusText: {
2163
- ...typography.styles.text10Regular,
2164
- color: colors.textSecondary,
2165
- marginLeft: spacing.xs,
2166
- },
2167
- apiSuccessText: {
2168
- ...typography.styles.text10Regular,
2169
- color: colors.success,
2170
- marginLeft: spacing.xs,
2171
- },
2172
- apiErrorText: {
2173
- ...typography.styles.text10Regular,
2174
- color: colors.error,
2175
- marginLeft: spacing.xs,
2176
- },
2177
- refreshButton: {
2178
- paddingHorizontal: spacing.sm,
2179
- paddingVertical: spacing.xs,
2180
- backgroundColor: colors.primary + '20', // 20% opacity
2181
- borderRadius: spacing.xs,
2182
- },
2183
- refreshButtonText: {
2184
- ...typography.styles.text10Medium,
2185
- color: colors.primary,
2186
- },
2187
- noDataContainer: {
2188
- padding: spacing.lg,
2189
- alignItems: 'center',
2190
- justifyContent: 'center',
2191
- minHeight: 200,
2192
- },
2193
- noDataText: {
2194
- ...typography.styles.text14Regular,
2195
- color: colors.textSecondary,
2196
- textAlign: 'center',
2197
- lineHeight: 20,
2198
- marginBottom: spacing.md,
2199
- },
2200
- showAllButton: {
2201
- backgroundColor: colors.primary,
2202
- paddingHorizontal: spacing.lg,
2203
- paddingVertical: spacing.sm,
2204
- borderRadius: spacing.sm,
2205
- marginTop: spacing.md,
2206
- },
2207
- showAllButtonText: {
2208
- ...typography.styles.text14Medium,
2209
- color: colors.headerText,
2210
- textAlign: 'center',
2211
- },
2212
- loadingOverlay: {
2213
- position: 'absolute',
2214
- top: 0,
2215
- left: 0,
2216
- right: 0,
2217
- bottom: 0,
2218
- backgroundColor: 'rgba(0, 0, 0, 0.3)',
2219
- justifyContent: 'center',
2220
- alignItems: 'center',
2221
- zIndex: 1000,
2222
- },
2170
+ container: {
2171
+ flex: 1,
2172
+ backgroundColor: colors.surface,
2173
+ },
2174
+ customHeader: {
2175
+ backgroundColor: colors.headerBg,
2176
+ paddingTop: Platform.OS === 'ios' ? 50 : 20,
2177
+ paddingBottom: spacing.md,
2178
+ paddingHorizontal: spacing.lg,
2179
+ flexDirection: 'row',
2180
+ alignItems: 'center',
2181
+ },
2182
+ backButton: {
2183
+ width: 40,
2184
+ height: 40,
2185
+ alignItems: 'center',
2186
+ justifyContent: 'center',
2187
+ },
2188
+ backIcon: {
2189
+ width: 24,
2190
+ height: 24,
2191
+ tintColor: colors.headerText,
2192
+ },
2193
+ headerContent: {
2194
+ flex: 1,
2195
+ alignItems: 'flex-start',
2196
+ marginRight: 40, // Balance the back button width
2197
+ },
2198
+ headerTitle: {
2199
+ ...typography.styles.h3,
2200
+ color: colors.headerText,
2201
+ marginBottom: spacing.xs,
2202
+ },
2203
+ poweredByContainer: {
2204
+ flexDirection: 'row',
2205
+ alignItems: 'center',
2206
+ },
2207
+ poweredByText: {
2208
+ ...typography.styles.text10Regular,
2209
+ color: colors.headerText,
2210
+ opacity: 0.7,
2211
+ marginRight: 0,
2212
+ },
2213
+ simplifyLogo: {
2214
+ width: 70,
2215
+ height: 20,
2216
+ },
2217
+ content: {
2218
+ flex: 1,
2219
+ },
2220
+ section: {
2221
+ paddingHorizontal: spacing.lg,
2222
+ paddingTop: spacing.xl,
2223
+ },
2224
+ sectionTitle: {
2225
+ ...typography.styles.h3,
2226
+ color: colors.text,
2227
+ marginBottom: spacing.lg,
2228
+ },
2229
+ activeFDCardSpacer: {
2230
+ marginTop: spacing.md,
2231
+ },
2232
+ tabsContainer: {
2233
+ paddingHorizontal: spacing.lg,
2234
+ paddingTop: spacing.xxl,
2235
+ paddingBottom: spacing.lg,
2236
+ },
2237
+ tabsRow: {
2238
+ flexDirection: 'row',
2239
+ justifyContent: 'space-around',
2240
+ alignItems: 'center',
2241
+ },
2242
+ tab: {
2243
+ flex: 1,
2244
+ paddingHorizontal: spacing.sm,
2245
+ paddingVertical: spacing.md,
2246
+ marginHorizontal: 2,
2247
+ borderBottomWidth: 3,
2248
+ borderBottomColor: colors.surface,
2249
+ borderBottomLeftRadius: 8,
2250
+ borderBottomRightRadius: 8,
2251
+ backgroundColor: 'transparent',
2252
+ },
2253
+ activeTab: {
2254
+ borderBottomColor: colors.tabSelected,
2255
+ backgroundColor: colors.background,
2256
+ borderBottomLeftRadius: 12,
2257
+ borderBottomRightRadius: 12,
2258
+ },
2259
+ tabText: {
2260
+ ...typography.styles.text12Medium,
2261
+ color: colors.textSecondary,
2262
+ textAlign: 'center',
2263
+ },
2264
+ activeTabText: {
2265
+ color: colors.tabSelected,
2266
+ fontWeight: '600',
2267
+ textAlign: 'center',
2268
+ },
2269
+ filterStatusContainer: {
2270
+ paddingHorizontal: spacing.lg,
2271
+ paddingVertical: spacing.sm,
2272
+ backgroundColor: colors.background + '80', // 50% opacity
2273
+ borderBottomWidth: 1,
2274
+ borderBottomColor: colors.border + '20', // 20% opacity
2275
+ },
2276
+ filterStatusText: {
2277
+ ...typography.styles.text12Regular,
2278
+ color: colors.textSecondary,
2279
+ textAlign: 'center',
2280
+ fontStyle: 'italic',
2281
+ },
2282
+ fdOptionsContainer: {
2283
+ paddingHorizontal: spacing.lg,
2284
+ paddingBottom: spacing.xl,
2285
+ },
2286
+ // API Status Styles
2287
+ apiStatusContainer: {
2288
+ flexDirection: 'row',
2289
+ justifyContent: 'space-between',
2290
+ alignItems: 'center',
2291
+ paddingHorizontal: spacing.lg,
2292
+ paddingVertical: spacing.sm,
2293
+ backgroundColor: colors.background,
2294
+ borderBottomWidth: 1,
2295
+ borderBottomColor: colors.border + '20', // 20% opacity
2296
+ },
2297
+ apiStatusIndicator: {
2298
+ flexDirection: 'row',
2299
+ alignItems: 'center',
2300
+ flex: 1,
2301
+ },
2302
+ apiStatusText: {
2303
+ ...typography.styles.text10Regular,
2304
+ color: colors.textSecondary,
2305
+ marginLeft: spacing.xs,
2306
+ },
2307
+ apiSuccessText: {
2308
+ ...typography.styles.text10Regular,
2309
+ color: colors.success,
2310
+ marginLeft: spacing.xs,
2311
+ },
2312
+ apiErrorText: {
2313
+ ...typography.styles.text10Regular,
2314
+ color: colors.error,
2315
+ marginLeft: spacing.xs,
2316
+ },
2317
+ refreshButton: {
2318
+ paddingHorizontal: spacing.sm,
2319
+ paddingVertical: spacing.xs,
2320
+ backgroundColor: colors.primary + '20', // 20% opacity
2321
+ borderRadius: spacing.xs,
2322
+ },
2323
+ refreshButtonText: {
2324
+ ...typography.styles.text10Medium,
2325
+ color: colors.primary,
2326
+ },
2327
+ noDataContainer: {
2328
+ padding: spacing.lg,
2329
+ alignItems: 'center',
2330
+ justifyContent: 'center',
2331
+ minHeight: 200,
2332
+ },
2333
+ noDataText: {
2334
+ ...typography.styles.text14Regular,
2335
+ color: colors.textSecondary,
2336
+ textAlign: 'center',
2337
+ lineHeight: 20,
2338
+ marginBottom: spacing.md,
2339
+ },
2340
+ showAllButton: {
2341
+ backgroundColor: colors.primary,
2342
+ paddingHorizontal: spacing.lg,
2343
+ paddingVertical: spacing.sm,
2344
+ borderRadius: spacing.sm,
2345
+ marginTop: spacing.md,
2346
+ },
2347
+ showAllButtonText: {
2348
+ ...typography.styles.text14Medium,
2349
+ color: colors.headerText,
2350
+ textAlign: 'center',
2351
+ },
2352
+ loadingOverlay: {
2353
+ position: 'absolute',
2354
+ top: 0,
2355
+ left: 0,
2356
+ right: 0,
2357
+ bottom: 0,
2358
+ backgroundColor: 'rgba(0, 0, 0, 0.3)',
2359
+ justifyContent: 'center',
2360
+ alignItems: 'center',
2361
+ zIndex: 1000,
2362
+ },
2223
2363
  });
2224
2364
 
2225
2365
  export default FDList;