@finspringinnovations/fdsdk 0.0.1

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 (315) hide show
  1. package/README.md +184 -0
  2. package/lib/api/applicationApi.d.ts +1 -0
  3. package/lib/api/applicationApi.js +11 -0
  4. package/lib/api/bankApi.d.ts +352 -0
  5. package/lib/api/bankApi.js +54 -0
  6. package/lib/api/baseApi.d.ts +8 -0
  7. package/lib/api/baseApi.js +456 -0
  8. package/lib/api/customerApi.d.ts +855 -0
  9. package/lib/api/customerApi.js +213 -0
  10. package/lib/api/fdApi.d.ts +979 -0
  11. package/lib/api/fdApi.js +112 -0
  12. package/lib/api/fdCalculatorApi.d.ts +179 -0
  13. package/lib/api/fdCalculatorApi.js +36 -0
  14. package/lib/api/index.d.ts +14 -0
  15. package/lib/api/index.js +45 -0
  16. package/lib/api/interestRateApi.d.ts +585 -0
  17. package/lib/api/interestRateApi.js +101 -0
  18. package/lib/api/kycApi.d.ts +486 -0
  19. package/lib/api/kycApi.js +71 -0
  20. package/lib/api/masterDataApi.d.ts +158 -0
  21. package/lib/api/masterDataApi.js +32 -0
  22. package/lib/api/nomineeApi.d.ts +325 -0
  23. package/lib/api/nomineeApi.js +46 -0
  24. package/lib/api/onboardingApi.d.ts +192 -0
  25. package/lib/api/onboardingApi.js +41 -0
  26. package/lib/api/panApi.d.ts +0 -0
  27. package/lib/api/panApi.js +23 -0
  28. package/lib/api/paymentApi.d.ts +325 -0
  29. package/lib/api/paymentApi.js +46 -0
  30. package/lib/api/workflowApi.d.ts +654 -0
  31. package/lib/api/workflowApi.js +90 -0
  32. package/lib/assets/images/images.d.ts +4 -0
  33. package/lib/assets/images/images.js +10 -0
  34. package/lib/components/AadhaarInput.d.ts +13 -0
  35. package/lib/components/AadhaarInput.js +47 -0
  36. package/lib/components/ActionButton.d.ts +12 -0
  37. package/lib/components/ActionButton.js +87 -0
  38. package/lib/components/ActiveFDCard.d.ts +16 -0
  39. package/lib/components/ActiveFDCard.js +95 -0
  40. package/lib/components/AmountInput.d.ts +20 -0
  41. package/lib/components/AmountInput.js +144 -0
  42. package/lib/components/CheckboxOption.d.ts +11 -0
  43. package/lib/components/CheckboxOption.js +41 -0
  44. package/lib/components/CompanyHeader.d.ts +7 -0
  45. package/lib/components/CompanyHeader.js +57 -0
  46. package/lib/components/DropdownSelector.d.ts +9 -0
  47. package/lib/components/DropdownSelector.js +49 -0
  48. package/lib/components/EmptyState.d.ts +17 -0
  49. package/lib/components/EmptyState.js +44 -0
  50. package/lib/components/ErrorDisplay.d.ts +17 -0
  51. package/lib/components/ErrorDisplay.js +69 -0
  52. package/lib/components/FAQItem.d.ts +9 -0
  53. package/lib/components/FAQItem.js +52 -0
  54. package/lib/components/FDCard.d.ts +21 -0
  55. package/lib/components/FDCard.js +96 -0
  56. package/lib/components/FormDropdown.d.ts +18 -0
  57. package/lib/components/FormDropdown.js +155 -0
  58. package/lib/components/FormSection.d.ts +14 -0
  59. package/lib/components/FormSection.js +38 -0
  60. package/lib/components/Header.d.ts +14 -0
  61. package/lib/components/Header.js +52 -0
  62. package/lib/components/IFSCSearchResultCard.d.ts +13 -0
  63. package/lib/components/IFSCSearchResultCard.js +70 -0
  64. package/lib/components/InfoBox.d.ts +8 -0
  65. package/lib/components/InfoBox.js +39 -0
  66. package/lib/components/InterestRateCard.d.ts +8 -0
  67. package/lib/components/InterestRateCard.js +46 -0
  68. package/lib/components/LoadingIndicator.d.ts +12 -0
  69. package/lib/components/LoadingIndicator.js +30 -0
  70. package/lib/components/OTPInput.d.ts +17 -0
  71. package/lib/components/OTPInput.js +144 -0
  72. package/lib/components/PaymentDetailsCard.d.ts +20 -0
  73. package/lib/components/PaymentDetailsCard.js +68 -0
  74. package/lib/components/PendingFDBottomSheet.d.ts +18 -0
  75. package/lib/components/PendingFDBottomSheet.js +122 -0
  76. package/lib/components/SafeAreaWrapper.d.ts +13 -0
  77. package/lib/components/SafeAreaWrapper.js +41 -0
  78. package/lib/components/ScreenHeader.d.ts +11 -0
  79. package/lib/components/ScreenHeader.js +46 -0
  80. package/lib/components/StatusDisplay.d.ts +15 -0
  81. package/lib/components/StatusDisplay.js +88 -0
  82. package/lib/components/TextFieldWithLabel.d.ts +46 -0
  83. package/lib/components/TextFieldWithLabel.js +326 -0
  84. package/lib/components/TrustBox.d.ts +8 -0
  85. package/lib/components/TrustBox.js +45 -0
  86. package/lib/components/ValidationErrorAlert.d.ts +23 -0
  87. package/lib/components/ValidationErrorAlert.js +39 -0
  88. package/lib/components/ValidationMessage.d.ts +9 -0
  89. package/lib/components/ValidationMessage.js +98 -0
  90. package/lib/components/index.d.ts +35 -0
  91. package/lib/components/index.js +64 -0
  92. package/lib/config/apiConfig.d.ts +34 -0
  93. package/lib/config/apiConfig.js +158 -0
  94. package/lib/config/appDataConfig.d.ts +114 -0
  95. package/lib/config/appDataConfig.js +264 -0
  96. package/lib/config/encryptionConfig.d.ts +21 -0
  97. package/lib/config/encryptionConfig.js +61 -0
  98. package/lib/config/workflowConstants.d.ts +37 -0
  99. package/lib/config/workflowConstants.js +38 -0
  100. package/lib/constants/strings/bank.d.ts +72 -0
  101. package/lib/constants/strings/bank.js +86 -0
  102. package/lib/constants/strings/base64Images.d.ts +25 -0
  103. package/lib/constants/strings/base64Images.js +28 -0
  104. package/lib/constants/strings/common.d.ts +53 -0
  105. package/lib/constants/strings/common.js +62 -0
  106. package/lib/constants/strings/employee.d.ts +61 -0
  107. package/lib/constants/strings/employee.js +77 -0
  108. package/lib/constants/strings/faq.d.ts +14 -0
  109. package/lib/constants/strings/faq.js +20 -0
  110. package/lib/constants/strings/fd.d.ts +122 -0
  111. package/lib/constants/strings/fd.js +151 -0
  112. package/lib/constants/strings/home.d.ts +49 -0
  113. package/lib/constants/strings/home.js +62 -0
  114. package/lib/constants/strings/index.d.ts +16 -0
  115. package/lib/constants/strings/index.js +44 -0
  116. package/lib/constants/strings/kyc.d.ts +80 -0
  117. package/lib/constants/strings/kyc.js +94 -0
  118. package/lib/constants/strings/nominee.d.ts +64 -0
  119. package/lib/constants/strings/nominee.js +81 -0
  120. package/lib/hooks/useAuth.d.ts +25 -0
  121. package/lib/hooks/useAuth.js +39 -0
  122. package/lib/hooks/useFDData.d.ts +11 -0
  123. package/lib/hooks/useFDData.js +40 -0
  124. package/lib/index.d.ts +69 -0
  125. package/lib/index.js +182 -0
  126. package/lib/navigation/RootNavigator.d.ts +8 -0
  127. package/lib/navigation/RootNavigator.js +205 -0
  128. package/lib/navigation/SimpleNavigator.d.ts +11 -0
  129. package/lib/navigation/SimpleNavigator.js +107 -0
  130. package/lib/navigation/helpers.d.ts +11 -0
  131. package/lib/navigation/helpers.js +83 -0
  132. package/lib/navigation/index.d.ts +15 -0
  133. package/lib/navigation/index.js +42 -0
  134. package/lib/navigation/types.d.ts +113 -0
  135. package/lib/navigation/types.js +2 -0
  136. package/lib/navigation/workflowNavigator.d.ts +22 -0
  137. package/lib/navigation/workflowNavigator.js +104 -0
  138. package/lib/providers/ApiProvider.d.ts +7 -0
  139. package/lib/providers/ApiProvider.js +34 -0
  140. package/lib/providers/MasterDataProvider.d.ts +10 -0
  141. package/lib/providers/MasterDataProvider.js +54 -0
  142. package/lib/screens/AadhaarVerification.d.ts +7 -0
  143. package/lib/screens/AadhaarVerification.js +627 -0
  144. package/lib/screens/AddBankAccount.d.ts +22 -0
  145. package/lib/screens/AddBankAccount.js +381 -0
  146. package/lib/screens/BankDetail.d.ts +16 -0
  147. package/lib/screens/BankDetail.js +596 -0
  148. package/lib/screens/BookFD.d.ts +0 -0
  149. package/lib/screens/BookFD.js +315 -0
  150. package/lib/screens/Employee.d.ts +18 -0
  151. package/lib/screens/Employee.js +594 -0
  152. package/lib/screens/FDCalculator.d.ts +18 -0
  153. package/lib/screens/FDCalculator.js +759 -0
  154. package/lib/screens/FDList.d.ts +27 -0
  155. package/lib/screens/FDList.js +1008 -0
  156. package/lib/screens/FindIFSC.d.ts +16 -0
  157. package/lib/screens/FindIFSC.js +248 -0
  158. package/lib/screens/Home.d.ts +0 -0
  159. package/lib/screens/Home.js +143 -0
  160. package/lib/screens/NomineeDetail.d.ts +17 -0
  161. package/lib/screens/NomineeDetail.js +592 -0
  162. package/lib/screens/PayNow.d.ts +14 -0
  163. package/lib/screens/PayNow.js +230 -0
  164. package/lib/screens/Payment.d.ts +11 -0
  165. package/lib/screens/Payment.js +191 -0
  166. package/lib/screens/PaymentStatus.d.ts +16 -0
  167. package/lib/screens/PaymentStatus.js +397 -0
  168. package/lib/screens/ReviewKYC.d.ts +21 -0
  169. package/lib/screens/ReviewKYC.js +660 -0
  170. package/lib/state/paymentSession.d.ts +8 -0
  171. package/lib/state/paymentSession.js +13 -0
  172. package/lib/store/fdListSelectedSlice.d.ts +21 -0
  173. package/lib/store/fdListSelectedSlice.js +26 -0
  174. package/lib/store/hooks.d.ts +8 -0
  175. package/lib/store/hooks.js +31 -0
  176. package/lib/store/index.d.ts +3 -0
  177. package/lib/store/index.js +8 -0
  178. package/lib/store/onboardingSlice.d.ts +12 -0
  179. package/lib/store/onboardingSlice.js +32 -0
  180. package/lib/store/store.d.ts +13 -0
  181. package/lib/store/store.js +33 -0
  182. package/lib/theme/ThemeContext.d.ts +210 -0
  183. package/lib/theme/ThemeContext.js +90 -0
  184. package/lib/theme/colors.d.ts +80 -0
  185. package/lib/theme/colors.js +85 -0
  186. package/lib/theme/index.d.ts +34 -0
  187. package/lib/theme/index.js +69 -0
  188. package/lib/theme/shadows.d.ts +53 -0
  189. package/lib/theme/shadows.js +58 -0
  190. package/lib/theme/typography.d.ts +134 -0
  191. package/lib/theme/typography.js +143 -0
  192. package/lib/types/dataTypes.d.ts +34 -0
  193. package/lib/types/dataTypes.js +2 -0
  194. package/lib/types/workflowTypes.d.ts +2 -0
  195. package/lib/types/workflowTypes.js +2 -0
  196. package/lib/utils/apiLogger.d.ts +48 -0
  197. package/lib/utils/apiLogger.js +105 -0
  198. package/lib/utils/encryption.d.ts +28 -0
  199. package/lib/utils/encryption.js +113 -0
  200. package/lib/utils/getFDData.d.ts +48 -0
  201. package/lib/utils/getFDData.js +154 -0
  202. package/lib/utils/globalData.d.ts +2 -0
  203. package/lib/utils/globalData.js +10 -0
  204. package/package.json +76 -0
  205. package/src/api/applicationApi.ts +12 -0
  206. package/src/api/bankApi.ts +42 -0
  207. package/src/api/baseApi.ts +513 -0
  208. package/src/api/customerApi.ts +291 -0
  209. package/src/api/fdApi.ts +150 -0
  210. package/src/api/fdCalculatorApi.ts +41 -0
  211. package/src/api/index.ts +29 -0
  212. package/src/api/interestRateApi.ts +143 -0
  213. package/src/api/kycApi.ts +63 -0
  214. package/src/api/masterDataApi.ts +34 -0
  215. package/src/api/nomineeApi.ts +34 -0
  216. package/src/api/onboardingApi.ts +64 -0
  217. package/src/api/panApi.ts +25 -0
  218. package/src/api/paymentApi.ts +34 -0
  219. package/src/api/workflowApi.ts +94 -0
  220. package/src/assets/images/arrow-filled.png +0 -0
  221. package/src/assets/images/arrow-left.png +0 -0
  222. package/src/assets/images/backicon.png +0 -0
  223. package/src/assets/images/calendar.png +0 -0
  224. package/src/assets/images/chevron-down.png +0 -0
  225. package/src/assets/images/chevron-down@2x.png +0 -0
  226. package/src/assets/images/chevron-down@3x.png +0 -0
  227. package/src/assets/images/images.js +8 -0
  228. package/src/components/AadhaarInput.tsx +91 -0
  229. package/src/components/ActionButton.tsx +129 -0
  230. package/src/components/ActiveFDCard.tsx +158 -0
  231. package/src/components/AmountInput.tsx +217 -0
  232. package/src/components/CheckboxOption.tsx +93 -0
  233. package/src/components/CompanyHeader.tsx +78 -0
  234. package/src/components/DropdownSelector.tsx +77 -0
  235. package/src/components/EmptyState.tsx +109 -0
  236. package/src/components/ErrorDisplay.tsx +135 -0
  237. package/src/components/FAQItem.tsx +90 -0
  238. package/src/components/FDCard.tsx +165 -0
  239. package/src/components/FormDropdown.tsx +214 -0
  240. package/src/components/FormSection.tsx +86 -0
  241. package/src/components/Header.tsx +110 -0
  242. package/src/components/IFSCSearchResultCard.tsx +139 -0
  243. package/src/components/InfoBox.tsx +55 -0
  244. package/src/components/InterestRateCard.tsx +77 -0
  245. package/src/components/LoadingIndicator.tsx +63 -0
  246. package/src/components/OTPInput.tsx +213 -0
  247. package/src/components/PaymentDetailsCard.tsx +120 -0
  248. package/src/components/PendingFDBottomSheet.tsx +235 -0
  249. package/src/components/README.md +210 -0
  250. package/src/components/SafeAreaWrapper.tsx +68 -0
  251. package/src/components/ScreenHeader.tsx +83 -0
  252. package/src/components/StatusDisplay.tsx +139 -0
  253. package/src/components/TextFieldWithLabel.tsx +502 -0
  254. package/src/components/TrustBox.tsx +63 -0
  255. package/src/components/ValidationErrorAlert.tsx +57 -0
  256. package/src/components/ValidationMessage.tsx +134 -0
  257. package/src/components/index.tsx +47 -0
  258. package/src/config/apiConfig.ts +217 -0
  259. package/src/config/appDataConfig.ts +279 -0
  260. package/src/config/encryptionConfig.ts +65 -0
  261. package/src/config/workflowConstants.ts +43 -0
  262. package/src/constants/strings/README.md +146 -0
  263. package/src/constants/strings/bank.ts +92 -0
  264. package/src/constants/strings/base64Images.ts +29 -0
  265. package/src/constants/strings/common.ts +63 -0
  266. package/src/constants/strings/employee.ts +85 -0
  267. package/src/constants/strings/faq.ts +23 -0
  268. package/src/constants/strings/fd.ts +172 -0
  269. package/src/constants/strings/home.ts +67 -0
  270. package/src/constants/strings/index.ts +21 -0
  271. package/src/constants/strings/kyc.ts +100 -0
  272. package/src/constants/strings/nominee.ts +90 -0
  273. package/src/hooks/useAuth.ts +42 -0
  274. package/src/hooks/useFDData.ts +48 -0
  275. package/src/index.tsx +173 -0
  276. package/src/navigation/RootNavigator.tsx +352 -0
  277. package/src/navigation/SimpleNavigator.tsx +107 -0
  278. package/src/navigation/helpers.ts +85 -0
  279. package/src/navigation/index.tsx +81 -0
  280. package/src/navigation/types.ts +124 -0
  281. package/src/navigation/workflowNavigator.ts +131 -0
  282. package/src/providers/ApiProvider.tsx +43 -0
  283. package/src/providers/MasterDataProvider.tsx +30 -0
  284. package/src/screens/AadhaarVerification.tsx +809 -0
  285. package/src/screens/AddBankAccount.tsx +541 -0
  286. package/src/screens/BankDetail.tsx +826 -0
  287. package/src/screens/BookFD.tsx +330 -0
  288. package/src/screens/Employee.tsx +822 -0
  289. package/src/screens/FDCalculator.tsx +987 -0
  290. package/src/screens/FDList.tsx +1284 -0
  291. package/src/screens/FindIFSC.tsx +332 -0
  292. package/src/screens/Home.tsx +152 -0
  293. package/src/screens/NomineeDetail.tsx +800 -0
  294. package/src/screens/PayNow.tsx +282 -0
  295. package/src/screens/Payment.tsx +224 -0
  296. package/src/screens/PaymentStatus.tsx +561 -0
  297. package/src/screens/ReviewKYC.tsx +956 -0
  298. package/src/state/paymentSession.ts +13 -0
  299. package/src/store/fdListSelectedSlice.ts +42 -0
  300. package/src/store/hooks.ts +27 -0
  301. package/src/store/index.ts +3 -0
  302. package/src/store/onboardingSlice.ts +37 -0
  303. package/src/store/store.ts +35 -0
  304. package/src/theme/ThemeContext.tsx +82 -0
  305. package/src/theme/colors.ts +90 -0
  306. package/src/theme/index.ts +64 -0
  307. package/src/theme/shadows.ts +61 -0
  308. package/src/theme/typography.ts +151 -0
  309. package/src/types/dataTypes.ts +37 -0
  310. package/src/types/env.d.ts +93 -0
  311. package/src/types/workflowTypes.ts +12 -0
  312. package/src/utils/apiLogger.ts +166 -0
  313. package/src/utils/encryption.ts +159 -0
  314. package/src/utils/getFDData.ts +175 -0
  315. package/src/utils/globalData.ts +7 -0
@@ -0,0 +1,822 @@
1
+
2
+
3
+ import React, { useState, useEffect } from 'react';
4
+ import { View, Text, StyleSheet, ScrollView, TouchableOpacity, Alert, TouchableWithoutFeedback, BackHandler, Platform, ActivityIndicator, KeyboardAvoidingView } from 'react-native';
5
+ import Icon from 'react-native-vector-icons/Ionicons';
6
+ import SafeAreaWrapper from '../components/SafeAreaWrapper';
7
+ import { Header } from '../components';
8
+ import ActionButton from '../components/ActionButton';
9
+ import TextFieldWithLabel from '../components/TextFieldWithLabel';
10
+ import { useColors, useTypography, useTheme } from '../theme/ThemeContext';
11
+ import { useMasterData } from '../providers/MasterDataProvider';
12
+ import { useCustomerOccupationMutation, useGetCustomerApplicationDetailsMutation } from '../api/customerApi';
13
+ import { useAppSelector } from '../store';
14
+ import { getUserInfoForAPI } from '../config/appDataConfig';
15
+ import { usePreviousStateMutation } from '../api/workflowApi';
16
+ import { navigate } from '../navigation/helpers';
17
+ import { useFocusEffect } from '@react-navigation/native';
18
+ import { EMPLOYEE_STRINGS } from '../constants/strings/employee';
19
+ import { COMMON_STRINGS } from '../constants/strings/common';
20
+
21
+ export interface EmployeeProps {
22
+ onGoBack?: () => void;
23
+ onContinue?: (data: EmployeeData) => void;
24
+ initialData?: Partial<EmployeeData>;
25
+ }
26
+
27
+ export interface EmployeeData {
28
+ occupation: string;
29
+ natureOfBusiness: string;
30
+ customOccupation: string;
31
+ customNatureOfBusiness: string;
32
+ annualIncome: string;
33
+ sourceOfFund: string;
34
+ customSourceOfFund: string;
35
+ politicallyExposedPerson: string;
36
+ }
37
+
38
+ // These will be replaced/augmented with master data
39
+ const defaultOccupationOptions = ['Salaried', 'Self Employed', 'Professional', 'Student', 'Retired', 'Other'];
40
+ const defaultNatureOfBusinessOptions = ['IT Services', 'Manufacturing', 'Trading', 'Finance', 'Other'];
41
+ const defaultAnnualIncomeOptions = ['< 2 lakhs', '3-5 lakhs', '5-10 lakhs', '10-20 lakhs', '> 20 lakhs'];
42
+ const defaultSourceOfFundOptions = ['Salary', 'Business', 'Investment', 'Individual', 'Other'];
43
+ const defaultPepOptions = ['Individual', 'Family Member', 'Associate', 'No'];
44
+
45
+ const Employee: React.FC<EmployeeProps> = ({ onGoBack, onContinue, initialData }) => {
46
+
47
+ const colors = useColors();
48
+ const typography = useTypography();
49
+ const { themeName } = useTheme();
50
+ const styles = createStyles(colors, typography, themeName);
51
+ const { masterData, setMasterData } = useMasterData();
52
+ const workflowInstanceId = useAppSelector((state: any) => state?.onboarding?.workflowInstanceId);
53
+ const applicationId = useAppSelector((state: any) => state?.onboarding?.applicationId);
54
+ const customerId = useAppSelector((state: any) => state?.onboarding?.customerId);
55
+
56
+ // Get providerId from app data or use default
57
+ const defaultProviderId = useAppSelector((state: any) => state?.onboarding?.providerId);// Default Shriram provider ID
58
+
59
+
60
+ // Customer Occupation API
61
+ const [customerOccupation, {
62
+ data: customerOccupationResponse,
63
+ error: customerOccupationError,
64
+ isLoading: isLoadingCustomerOccupation,
65
+ }] = useCustomerOccupationMutation();
66
+
67
+ // Previous State API
68
+ const [previousState] = usePreviousStateMutation();
69
+
70
+ // Customer Application Details API (called on focus)
71
+ const [getCustomerApplicationDetails, {
72
+ data: customerApplicationDetailsResponse,
73
+ error: customerApplicationDetailsError,
74
+ isLoading: isLoadingCustomerApplicationDetails,
75
+ }] = useGetCustomerApplicationDetailsMutation();
76
+
77
+ // Call customer/application/details whenever this screen gains focus
78
+ useFocusEffect(
79
+ React.useCallback(() => {
80
+ let isActive = true;
81
+ (async () => {
82
+ try {
83
+ const userInfo = getUserInfoForAPI();
84
+ const req = {
85
+ providerId: defaultProviderId,
86
+ workflowInstanceId,
87
+ userreferenceid: userInfo.userReferenceId,
88
+ applicationid: applicationId,
89
+ entityid: '',
90
+ // Body params
91
+ applicationId: applicationId,
92
+ customerId: customerId,
93
+ } as any;
94
+ const res = await getCustomerApplicationDetails(req).unwrap();
95
+ if (!isActive) return;
96
+
97
+ // Set form data from employment object in API response
98
+ if (res?.data?.employment) {
99
+ const employment = res.data.employment;
100
+
101
+ setForm(prev => ({
102
+ ...prev,
103
+ occupation: employment.occupation || '',
104
+ natureOfBusiness: employment.nature_of_business || '',
105
+ customOccupation: employment.custom_occupation || '',
106
+ customNatureOfBusiness: employment.custom_nature_of_business || '',
107
+ annualIncome: employment.income || '',
108
+ sourceOfFund: employment.source_of_fund || '',
109
+ customSourceOfFund: employment.custom_source_of_fund || '',
110
+ politicallyExposedPerson: employment.politically_exposed_person || '',
111
+ }));
112
+ } else {
113
+ // If employment is empty, clear all fields
114
+ setForm(prev => ({
115
+ ...prev,
116
+ occupation: '',
117
+ natureOfBusiness: '',
118
+ customOccupation: '',
119
+ customNatureOfBusiness: '',
120
+ annualIncome: '',
121
+ sourceOfFund: '',
122
+ customSourceOfFund: '',
123
+ politicallyExposedPerson: '',
124
+ }));
125
+ }
126
+ } catch (e) {
127
+ if (!isActive) return;
128
+ }
129
+ })();
130
+ return () => { isActive = false; };
131
+ }, [defaultProviderId, workflowInstanceId, applicationId, customerId, getCustomerApplicationDetails])
132
+ );
133
+
134
+
135
+ // Customer occupation API status monitored
136
+
137
+ // Helper to normalize arrays/CSV strings to string[]
138
+ const normalizeOptions = (raw: any, fallback: string[]): string[] => {
139
+ if (Array.isArray(raw)) return raw.map((v) => String(v)).filter(Boolean);
140
+ if (typeof raw === 'string' && raw.trim().length) return raw.split(',').map((v) => v.trim()).filter(Boolean);
141
+ return fallback;
142
+ };
143
+
144
+ // Master data root may be data-wrapped
145
+ const md = (masterData as any)?.data || masterData || {};
146
+
147
+ // Get options from master data with fallbacks
148
+ const occupationOptions = React.useMemo(() => {
149
+ const fromGlobal = md?.occupation || masterData?.occupation;
150
+ return normalizeOptions(fromGlobal, defaultOccupationOptions);
151
+ }, [md, masterData]);
152
+
153
+ const natureOfBusinessOptions = React.useMemo(() => {
154
+ const raw = md?.natureOfBusiness || md?.natureOfBusinessOptions || md?.natureOfBusinessList;
155
+ return normalizeOptions(raw, defaultNatureOfBusinessOptions);
156
+ }, [md]);
157
+
158
+ const annualIncomeOptions = React.useMemo(() => {
159
+ const raw = md?.annualIncome || md?.annualIncomeOptions || md?.annualIncomeList;
160
+ return normalizeOptions(raw, defaultAnnualIncomeOptions);
161
+ }, [md]);
162
+
163
+ const sourceOfFundOptions = React.useMemo(() => {
164
+ const raw = md?.sourceOfFund || md?.sourceOfFundOptions || md?.sourceOfFundList || md?.sourceOfFunds;
165
+ return normalizeOptions(raw, defaultSourceOfFundOptions);
166
+ }, [md]);
167
+
168
+ const pepOptions = React.useMemo(() => {
169
+ const raw = md?.pepOptions
170
+ || md?.politicallyExposedPersonOptions
171
+ || md?.pepList
172
+ || md?.politicallyExposedPersonList
173
+ || md?.politicallyExposedPerson // sometimes provided as CSV/string
174
+ || md?.pep; // alternate short key
175
+ return normalizeOptions(raw, defaultPepOptions);
176
+ }, [md]);
177
+
178
+ const [form, setForm] = useState<EmployeeData>({
179
+ occupation: initialData?.occupation || (occupationOptions[0] || 'Self Employed'),
180
+ natureOfBusiness: initialData?.natureOfBusiness || (natureOfBusinessOptions[0] || 'Other'),
181
+ customOccupation: initialData?.customOccupation || '',
182
+ customNatureOfBusiness: (initialData as any)?.customNatureOfBusiness || '',
183
+ annualIncome: initialData?.annualIncome || (annualIncomeOptions[0] || defaultAnnualIncomeOptions[0]),
184
+ sourceOfFund: initialData?.sourceOfFund || (sourceOfFundOptions[0] || defaultSourceOfFundOptions[0]),
185
+ customSourceOfFund: (initialData as any)?.customSourceOfFund || '',
186
+ politicallyExposedPerson: initialData?.politicallyExposedPerson || (pepOptions[0] || defaultPepOptions[0]),
187
+ });
188
+
189
+ // Keep selections valid if options change after load
190
+ React.useEffect(() => {
191
+ setForm(prev => ({
192
+ ...prev,
193
+ occupation: occupationOptions.includes(prev.occupation) ? prev.occupation : (occupationOptions[0] || prev.occupation),
194
+ natureOfBusiness: natureOfBusinessOptions.includes(prev.natureOfBusiness) ? prev.natureOfBusiness : (natureOfBusinessOptions[0] || prev.natureOfBusiness),
195
+ annualIncome: annualIncomeOptions.includes(prev.annualIncome) ? prev.annualIncome : (annualIncomeOptions[0] || prev.annualIncome),
196
+ sourceOfFund: sourceOfFundOptions.includes(prev.sourceOfFund) ? prev.sourceOfFund : (sourceOfFundOptions[0] || prev.sourceOfFund),
197
+ politicallyExposedPerson: pepOptions.includes(prev.politicallyExposedPerson) ? prev.politicallyExposedPerson : (pepOptions[0] || prev.politicallyExposedPerson),
198
+ }));
199
+ }, [occupationOptions, natureOfBusinessOptions, annualIncomeOptions, sourceOfFundOptions, pepOptions]);
200
+
201
+ const [isGoingBack, setIsGoingBack] = useState(false);
202
+ const [openMenus, setOpenMenus] = useState({
203
+ occupation: false,
204
+ natureOfBusiness: false,
205
+ annualIncome: false,
206
+ sourceOfFund: false,
207
+ pep: false,
208
+ });
209
+
210
+ // Error states for custom fields
211
+ const [fieldErrors, setFieldErrors] = useState<{ [key: string]: string }>({});
212
+ // Track if continue button was pressed to disable it immediately
213
+ const [isSubmitting, setIsSubmitting] = useState(false);
214
+ // Track if component is mounted to prevent API calls after unmount
215
+ const isMountedRef = React.useRef(true);
216
+ // Use ref for immediate synchronous check to prevent race conditions
217
+ const isApiCallInProgressRef = React.useRef(false);
218
+
219
+ // Validate custom fields when form data changes
220
+ React.useEffect(() => {
221
+ // Validate custom occupation
222
+ const occupationError = validateCustomField('customOccupation', form.customOccupation, 'occupation');
223
+ setFieldErrors(prev => ({ ...prev, customOccupation: occupationError }));
224
+
225
+ // Validate custom nature of business
226
+ const natureError = validateCustomField('customNatureOfBusiness', form.customNatureOfBusiness, 'natureOfBusiness');
227
+ setFieldErrors(prev => ({ ...prev, customNatureOfBusiness: natureError }));
228
+
229
+ // Validate custom source of fund
230
+ const sourceError = validateCustomField('customSourceOfFund', form.customSourceOfFund, 'sourceOfFund');
231
+ setFieldErrors(prev => ({ ...prev, customSourceOfFund: sourceError }));
232
+ }, [form.occupation, form.natureOfBusiness, form.sourceOfFund, form.customOccupation, form.customNatureOfBusiness, form.customSourceOfFund]);
233
+
234
+ // Validate custom field and set error
235
+ const validateCustomField = (field: keyof EmployeeData, value: string, parentField: keyof EmployeeData): string => {
236
+ const parentValue = String(form[parentField] || '').trim().toLowerCase();
237
+ const isOther = parentValue === 'other' || parentValue === 'others';
238
+
239
+ // Show error immediately when "Other" is selected, even if custom field is empty
240
+ if (isOther) {
241
+ if (!value || value.trim().length === 0) {
242
+
243
+ // Get user-friendly label based on parent field
244
+ let label = '';
245
+ switch (parentField) {
246
+ case 'occupation':
247
+ label = EMPLOYEE_STRINGS.OCCUPATION_LABEL;
248
+ break;
249
+ case 'natureOfBusiness':
250
+ label = EMPLOYEE_STRINGS.NATURE_OF_BUSINESS_LABEL;
251
+ break;
252
+ case 'sourceOfFund':
253
+ label = EMPLOYEE_STRINGS.SOURCE_OF_FUND_LABEL;
254
+ break;
255
+ default:
256
+ label = parentField;
257
+ }
258
+
259
+ return `${label} is required`;
260
+ }
261
+
262
+ // Validate minimum 2 characters
263
+ if (value && value.trim().length > 0 && value.trim().length < 2) {
264
+ // Get user-friendly label based on parent field
265
+ let label = '';
266
+ switch (parentField) {
267
+ case 'occupation':
268
+ label = EMPLOYEE_STRINGS.OCCUPATION_LABEL;
269
+ break;
270
+ case 'natureOfBusiness':
271
+ label = EMPLOYEE_STRINGS.NATURE_OF_BUSINESS_LABEL;
272
+ break;
273
+ case 'sourceOfFund':
274
+ label = EMPLOYEE_STRINGS.SOURCE_OF_FUND_LABEL;
275
+ break;
276
+ default:
277
+ label = parentField;
278
+ }
279
+
280
+ return `${label} must be at least 2 characters`;
281
+ }
282
+ }
283
+
284
+ return '';
285
+ };
286
+
287
+ // Render error message for a field
288
+ const renderFieldError = (field: keyof EmployeeData) => {
289
+ const error = fieldErrors[field];
290
+ if (!error) return null;
291
+
292
+ return (
293
+ <View style={styles.errorContainer}>
294
+ {Platform.OS === 'android' && (
295
+ <Icon name="warning" size={16} color={colors.error || '#FF0000'} style={styles.errorIcon} />
296
+ )}
297
+ <Text style={styles.errorText}>{error}</Text>
298
+ </View>
299
+ );
300
+ };
301
+
302
+ // Check if occupation is "Self Employed" to conditionally show nature of business
303
+ const isSelfEmployed = React.useMemo(() => {
304
+ const occupation = String(form.occupation || '').trim().toLowerCase();
305
+ return occupation === 'self employed';
306
+ }, [form.occupation]);
307
+
308
+ // Validate required fields; require custom fields when "Other" selected
309
+ const isFormValid = React.useMemo(() => {
310
+ const hasBasic = Boolean(
311
+ String(form.occupation || '').trim() &&
312
+ (isSelfEmployed ? String(form.natureOfBusiness || '').trim() : true) &&
313
+ String(form.annualIncome || '').trim() &&
314
+ String(form.sourceOfFund || '').trim() &&
315
+ String(form.politicallyExposedPerson || '').trim()
316
+ );
317
+
318
+ const occ = String(form.occupation || '').trim().toLowerCase();
319
+ const nbf = String(form.natureOfBusiness || '').trim().toLowerCase();
320
+ const sof = String(form.sourceOfFund || '').trim().toLowerCase();
321
+
322
+ const needsCustomOcc = occ === 'other' || occ === 'others';
323
+ const needsCustomNob = isSelfEmployed && (nbf === 'other' || nbf === 'others');
324
+ const needsCustomSof = sof === 'other' || sof === 'others';
325
+
326
+ const customOk = (
327
+ (!needsCustomOcc || String(form.customOccupation || '').trim().length >= 2) &&
328
+ (!needsCustomNob || String(form.customNatureOfBusiness || '').trim().length >= 2) &&
329
+ (!needsCustomSof || String(form.customSourceOfFund || '').trim().length >= 2)
330
+ );
331
+
332
+ return hasBasic && customOk;
333
+ }, [form, isSelfEmployed]);
334
+
335
+ const updateField = (field: keyof EmployeeData, value: string) => {
336
+ setForm(prev => {
337
+ const newForm = { ...prev, [field]: value };
338
+
339
+ // Clear Nature of Business fields when occupation changes to non-self-employed
340
+ if (field === 'occupation') {
341
+ const occupation = String(value || '').trim().toLowerCase();
342
+ const isSelfEmployed = occupation === 'self employed';
343
+
344
+ if (!isSelfEmployed) {
345
+ newForm.natureOfBusiness = '';
346
+ newForm.customNatureOfBusiness = '';
347
+
348
+ // Also clear any field errors for Nature of Business
349
+ setFieldErrors(prev => ({
350
+ ...prev,
351
+ natureOfBusiness: '',
352
+ customNatureOfBusiness: ''
353
+ }));
354
+ }
355
+
356
+ // If occupation is not Other, clear customOccupation
357
+ const isOccOther = occupation === 'other' || occupation === 'others';
358
+ if (!isOccOther) {
359
+ newForm.customOccupation = '';
360
+ setFieldErrors(prev => ({
361
+ ...prev,
362
+ customOccupation: ''
363
+ }));
364
+ }
365
+ }
366
+
367
+ // If Nature of Business changes away from Other, clear its custom field
368
+ if (field === 'natureOfBusiness') {
369
+ const nob = String(value || '').trim().toLowerCase();
370
+ const isOther = nob === 'other' || nob === 'others';
371
+ if (!isOther) {
372
+ newForm.customNatureOfBusiness = '';
373
+ setFieldErrors(prev => ({
374
+ ...prev,
375
+ customNatureOfBusiness: ''
376
+ }));
377
+ }
378
+ }
379
+
380
+ // If Source of Fund changes away from Other, clear its custom field
381
+ if (field === 'sourceOfFund') {
382
+ const sof = String(value || '').trim().toLowerCase();
383
+ const isOther = sof === 'other' || sof === 'others';
384
+ if (!isOther) {
385
+ newForm.customSourceOfFund = '';
386
+ setFieldErrors(prev => ({
387
+ ...prev,
388
+ customSourceOfFund: ''
389
+ }));
390
+ }
391
+ }
392
+
393
+ return newForm;
394
+ });
395
+
396
+ // Validate custom fields when they are updated
397
+ if (field === 'customOccupation') {
398
+ const error = validateCustomField('customOccupation', value, 'occupation');
399
+ setFieldErrors(prev => ({ ...prev, customOccupation: error }));
400
+ } else if (field === 'customNatureOfBusiness') {
401
+ const error = validateCustomField('customNatureOfBusiness', value, 'natureOfBusiness');
402
+ setFieldErrors(prev => ({ ...prev, customNatureOfBusiness: error }));
403
+ } else if (field === 'customSourceOfFund') {
404
+ const error = validateCustomField('customSourceOfFund', value, 'sourceOfFund');
405
+ setFieldErrors(prev => ({ ...prev, customSourceOfFund: error }));
406
+ }
407
+
408
+ // Also validate custom fields when parent fields change
409
+ if (field === 'occupation') {
410
+ const error = validateCustomField('customOccupation', form.customOccupation, 'occupation');
411
+ setFieldErrors(prev => ({ ...prev, customOccupation: error }));
412
+ } else if (field === 'natureOfBusiness') {
413
+ const error = validateCustomField('customNatureOfBusiness', form.customNatureOfBusiness, 'natureOfBusiness');
414
+ setFieldErrors(prev => ({ ...prev, customNatureOfBusiness: error }));
415
+ } else if (field === 'sourceOfFund') {
416
+ const error = validateCustomField('customSourceOfFund', form.customSourceOfFund, 'sourceOfFund');
417
+ setFieldErrors(prev => ({ ...prev, customSourceOfFund: error }));
418
+ }
419
+ };
420
+
421
+ const toggleMenu = (key: keyof typeof openMenus) => {
422
+ setOpenMenus(prev => ({
423
+ occupation: false,
424
+ natureOfBusiness: false,
425
+ annualIncome: false,
426
+ sourceOfFund: false,
427
+ pep: false,
428
+ [key]: !prev[key],
429
+ }));
430
+ };
431
+
432
+ const closeAllMenus = () => {
433
+ setOpenMenus({
434
+ occupation: false,
435
+ natureOfBusiness: false,
436
+ annualIncome: false,
437
+ sourceOfFund: false,
438
+ pep: false,
439
+ });
440
+ };
441
+
442
+ const renderDropdown = (
443
+ label: string,
444
+ value: string,
445
+ field: keyof EmployeeData,
446
+ options: string[],
447
+ menuKey: keyof typeof openMenus,
448
+ ) => (
449
+ <View>
450
+ <TextFieldWithLabel
451
+ label={label}
452
+ value={value || ''}
453
+ onChangeText={(text) => updateField(field, text)}
454
+ variant="dropdown"
455
+ options={options}
456
+ isDropdownOpen={openMenus[menuKey]}
457
+ onDropdownToggle={() => toggleMenu(menuKey)}
458
+ onDropdownSelect={(option) => {
459
+ closeAllMenus();
460
+ updateField(field, option);
461
+ setOpenMenus(prev => ({ ...prev, [menuKey]: false }));
462
+ }}
463
+ placeholder={EMPLOYEE_STRINGS.OCCUPATION_PLACEHOLDER}
464
+ placeholderColor={themeName === 'dark' ? colors.placeholderColor : 'rgba(0,0,0,0.2)'}
465
+ />
466
+ {openMenus[menuKey] && (
467
+ <View style={styles.inlineMenu}>
468
+ {options.map((option) => (
469
+ <TouchableOpacity
470
+ key={option}
471
+ style={styles.modalOption}
472
+ onPress={() => {
473
+ closeAllMenus();
474
+ updateField(field, option);
475
+ setOpenMenus(prev => ({ ...prev, [menuKey]: false }));
476
+ }}
477
+ >
478
+ <Text style={styles.modalOptionText}>{option}</Text>
479
+ </TouchableOpacity>
480
+ ))}
481
+ </View>
482
+ )}
483
+ </View>
484
+ );
485
+
486
+ // Reset button states when component mounts (in case user navigates back)
487
+ React.useEffect(() => {
488
+ isApiCallInProgressRef.current = false;
489
+ setIsSubmitting(false);
490
+ }, []);
491
+
492
+ // Cleanup on unmount
493
+ React.useEffect(() => {
494
+ return () => {
495
+ isMountedRef.current = false;
496
+ };
497
+ }, []);
498
+
499
+ // customer/occupation API is ONLY called when user clicks Continue button on Employee screen
500
+ const handleContinue = async () => {
501
+ // Immediately disable button for double-click prevention
502
+ if (isApiCallInProgressRef.current || isSubmitting || isLoadingCustomerOccupation) {
503
+ return;
504
+ }
505
+
506
+ // Set lock synchronously
507
+ isApiCallInProgressRef.current = true;
508
+ setIsSubmitting(true);
509
+
510
+ try {
511
+ // Get user info from app data
512
+ const userInfo = getUserInfoForAPI();
513
+
514
+ // Prepare customer occupation request data
515
+ const customerOccupationRequest = {
516
+ providerId: defaultProviderId,
517
+ workflowInstanceId,
518
+ userreferenceid: userInfo.userReferenceId,
519
+ applicationid: applicationId,
520
+ customerId: customerId,
521
+ name: form.occupation,
522
+ natureOfBusiness: isSelfEmployed ? form.natureOfBusiness : '',
523
+ income: form.annualIncome,
524
+ sourceOfFund: form.sourceOfFund,
525
+ politicallyExposedPerson: form.politicallyExposedPerson,
526
+ customSourceOfFund: form.customSourceOfFund,
527
+ customNatureOfBusiness: isSelfEmployed ? form.customNatureOfBusiness : '',
528
+ customName: form.customOccupation,
529
+ };
530
+
531
+ const response = await customerOccupation(customerOccupationRequest as any).unwrap();
532
+
533
+ // Reset states before navigation to ensure button is enabled if user comes back
534
+ isApiCallInProgressRef.current = false;
535
+ setIsSubmitting(false);
536
+
537
+ if (!isMountedRef.current) return;
538
+ onContinue?.(form);
539
+ } catch (error) {
540
+ // Reset states on error so user can retry
541
+ isApiCallInProgressRef.current = false;
542
+ setIsSubmitting(false);
543
+
544
+ if (!isMountedRef.current) return;
545
+
546
+ const apiMessage =
547
+ (error && (error as any).data && ((error as any).data.message || (error as any).data.error || (error as any).data.detail)) ||
548
+ ((error as any)?.error) ||
549
+ ((error as any)?.message) ||
550
+ '';
551
+
552
+ const fieldErrorsArr = (error as any)?.data?.errors;
553
+ const fieldErrorsText = Array.isArray(fieldErrorsArr)
554
+ ? fieldErrorsArr.map((e: any) => (typeof e === 'string' ? e : e?.message)).filter(Boolean).join('\n')
555
+ : '';
556
+
557
+ const message = [apiMessage, fieldErrorsText]
558
+ .filter((s) => typeof s === 'string' && s.trim().length)
559
+ .join('\n') || 'Unable to save your occupation details. Please check your information and try again.';
560
+
561
+ Alert.alert('Occupation Details Save Failed', message, [{ text: COMMON_STRINGS.OK }]);
562
+ }
563
+ };
564
+
565
+ // Handler for back button (used by both header and hardware back button)
566
+ const handleBackPress = async () => {
567
+ setIsGoingBack(true);
568
+ try {
569
+ const userInfo = getUserInfoForAPI();
570
+ await previousState({
571
+ providerId: defaultProviderId,
572
+ workflowInstanceId,
573
+ userreferenceid: userInfo.userReferenceId,
574
+ applicationid: applicationId,
575
+ entityid: '',
576
+ });
577
+ } catch (e) {
578
+ // Handle error silently
579
+ } finally {
580
+ setIsGoingBack(false);
581
+ // Navigate to ReviewKYC screen instead of just going back
582
+ navigate('ReviewKYC');
583
+ }
584
+ };
585
+
586
+ // Handle Android hardware back button
587
+ useEffect(() => {
588
+ if (Platform.OS !== 'android') return;
589
+
590
+ const onHardwareBackPress = () => {
591
+ handleBackPress();
592
+ return true; // Prevent default behavior
593
+ };
594
+
595
+ const backHandler = BackHandler.addEventListener(
596
+ 'hardwareBackPress',
597
+ onHardwareBackPress
598
+ );
599
+
600
+ return () => backHandler.remove();
601
+ }, [defaultProviderId, workflowInstanceId, applicationId]);
602
+
603
+ return (
604
+ <SafeAreaWrapper
605
+ includeTop={false}
606
+ bottomPadding={25}
607
+ statusBarColor="#000000"
608
+ statusBarStyle="light-content"
609
+ >
610
+ <Header
611
+ title={EMPLOYEE_STRINGS.OCCUPATION_DETAILS_TITLE}
612
+ onBackPress={handleBackPress}
613
+ backgroundColor={colors.primary}
614
+ />
615
+
616
+ <KeyboardAvoidingView
617
+ behavior={Platform.OS === 'ios' ? 'padding' : undefined}
618
+ style={styles.keyboardAvoidingView}
619
+ keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : 0}
620
+ >
621
+ <ScrollView
622
+ style={styles.container}
623
+ showsVerticalScrollIndicator={false}
624
+ scrollEnabled={!(isLoadingCustomerOccupation || isSubmitting)}
625
+ keyboardShouldPersistTaps="handled"
626
+ contentContainerStyle={styles.scrollContent}
627
+ >
628
+ <TouchableWithoutFeedback onPress={isLoadingCustomerOccupation || isSubmitting ? undefined : closeAllMenus}>
629
+ <View>
630
+ {renderDropdown(EMPLOYEE_STRINGS.OCCUPATION_LABEL, form.occupation, 'occupation', occupationOptions, 'occupation')}
631
+
632
+ {(() => {
633
+ const val = String(form.occupation || '').trim().toLowerCase();
634
+ const isOther = val === 'other' || val === 'others';
635
+ return isOther ? (
636
+ <View>
637
+ <TextFieldWithLabel
638
+ label=""
639
+ containerStyle={{ marginTop: -40 }}
640
+ value={form.customOccupation || ''}
641
+ onChangeText={(text) => {
642
+ const lettersOnly = text.replace(/[^A-Za-z ]/g, '');
643
+ closeAllMenus();
644
+ updateField('customOccupation', lettersOnly);
645
+ }}
646
+ placeholder={EMPLOYEE_STRINGS.CUSTOM_OCCUPATION_PLACEHOLDER}
647
+ placeholderColor={themeName === 'dark' ? colors.placeholderColor : 'rgba(0,0,0,0.2)'}
648
+ variant="text"
649
+ />
650
+ {renderFieldError('customOccupation')}
651
+ </View>
652
+ ) : null;
653
+ })()}
654
+
655
+ {/* Show Nature of Business only when occupation is "Self Employed" */}
656
+ {isSelfEmployed && (
657
+ <>
658
+ {renderDropdown(EMPLOYEE_STRINGS.NATURE_OF_BUSINESS_LABEL, form.natureOfBusiness, 'natureOfBusiness', natureOfBusinessOptions, 'natureOfBusiness')}
659
+
660
+ {(() => {
661
+ const val = String(form.natureOfBusiness || '').trim().toLowerCase();
662
+ const isOther = val === 'other' || val === 'others';
663
+ return isOther ? (
664
+ <View>
665
+ <TextFieldWithLabel
666
+ label=""
667
+ containerStyle={{ marginTop: -40 }}
668
+ value={form.customNatureOfBusiness || ''}
669
+ onChangeText={(text) => {
670
+ const lettersOnly = text.replace(/[^A-Za-z ]/g, '');
671
+ closeAllMenus();
672
+ updateField('customNatureOfBusiness' as any, lettersOnly);
673
+ }}
674
+ placeholder={EMPLOYEE_STRINGS.CUSTOM_NATURE_OF_BUSINESS_PLACEHOLDER}
675
+ placeholderColor={themeName === 'dark' ? colors.placeholderColor : 'rgba(0,0,0,0.2)'}
676
+ variant="text"
677
+ />
678
+ {renderFieldError('customNatureOfBusiness')}
679
+ </View>
680
+ ) : null;
681
+ })()}
682
+ </>
683
+ )}
684
+
685
+ {renderDropdown(EMPLOYEE_STRINGS.ANNUAL_INCOME_LABEL, form.annualIncome, 'annualIncome', annualIncomeOptions, 'annualIncome')}
686
+
687
+ {renderDropdown(EMPLOYEE_STRINGS.SOURCE_OF_FUND_LABEL, form.sourceOfFund, 'sourceOfFund', sourceOfFundOptions, 'sourceOfFund')}
688
+
689
+ {(() => {
690
+ const val = String(form.sourceOfFund || '').trim().toLowerCase();
691
+ const isOther = val === 'other' || val === 'others';
692
+ return isOther ? (
693
+ <View>
694
+ <TextFieldWithLabel
695
+ label=""
696
+ containerStyle={{ marginTop: -40 }}
697
+ value={form.customSourceOfFund || ''}
698
+ onChangeText={(text) => {
699
+ const lettersOnly = text.replace(/[^A-Za-z ]/g, '');
700
+ closeAllMenus();
701
+ updateField('customSourceOfFund' as any, lettersOnly);
702
+ }}
703
+ placeholder={EMPLOYEE_STRINGS.CUSTOM_SOURCE_OF_FUND_PLACEHOLDER}
704
+ placeholderColor={themeName === 'dark' ? colors.placeholderColor : 'rgba(0,0,0,0.2)'}
705
+ variant="text"
706
+ />
707
+ {renderFieldError('customSourceOfFund')}
708
+ </View>
709
+ ) : null;
710
+ })()}
711
+
712
+ {renderDropdown(EMPLOYEE_STRINGS.POLITICALLY_EXPOSED_PERSON_LABEL, form.politicallyExposedPerson, 'politicallyExposedPerson', pepOptions, 'pep')}
713
+
714
+ {/* add bottom spacer so content not hidden behind footer */}
715
+ <View style={{ height: 120 }} />
716
+ </View>
717
+ </TouchableWithoutFeedback>
718
+ </ScrollView>
719
+ </KeyboardAvoidingView>
720
+ <View style={styles.footer}>
721
+ <ActionButton
722
+ title={COMMON_STRINGS.CONTINUE}
723
+ onPress={handleContinue}
724
+ disabled={isLoadingCustomerOccupation || isSubmitting || !isFormValid}
725
+ loading={isLoadingCustomerOccupation || isSubmitting}
726
+ />
727
+ </View>
728
+ {/* Overlay to disable screen interactions during API call */}
729
+ {(isLoadingCustomerOccupation || isSubmitting) && (
730
+ <View style={styles.loadingOverlay} pointerEvents="auto">
731
+ </View>
732
+ )}
733
+ {/* Loading overlay for back navigation */}
734
+ {isGoingBack && (
735
+ <View style={styles.loadingOverlay} pointerEvents="auto">
736
+ <ActivityIndicator size="large" color={colors.primary} />
737
+ </View>
738
+ )}
739
+ </SafeAreaWrapper>
740
+ );
741
+ };
742
+
743
+ const createStyles = (colors: any, typography: any, themeName: string) => StyleSheet.create({
744
+ keyboardAvoidingView: {
745
+ flex: 1,
746
+ },
747
+ container: {
748
+ flex: 1,
749
+ paddingHorizontal: 16,
750
+ paddingTop: 20,
751
+ },
752
+ scrollContent: {
753
+ flexGrow: 1,
754
+ },
755
+ footer: {
756
+ position: 'absolute',
757
+ left: 0,
758
+ right: 0,
759
+ bottom: 0,
760
+ backgroundColor: colors.background,
761
+ paddingTop: 8,
762
+ paddingBottom: 16,
763
+ },
764
+ inlineMenu: {
765
+ backgroundColor: themeName === 'dark' ? colors.inputBackground : colors.background,
766
+ borderWidth: themeName === 'dark' ? 1 : 0.5,
767
+ borderColor: themeName === 'dark' ? '#ffffff' : 'rgba(0,0,0,0.2)',
768
+ borderRadius: 8,
769
+ marginTop: -20,
770
+ marginBottom: 10,
771
+ paddingHorizontal: 12,
772
+ paddingVertical: 6,
773
+ },
774
+ modalOption: {
775
+ paddingVertical: 14,
776
+ },
777
+ modalOptionText: {
778
+ ...typography.styles.bodyLarge,
779
+ color: colors.text,
780
+ },
781
+ debugButton: {
782
+ paddingHorizontal: 16,
783
+ paddingVertical: 12,
784
+ borderRadius: 8,
785
+ marginBottom: 16,
786
+ alignItems: 'center',
787
+ },
788
+ debugButtonText: {
789
+ ...typography.styles.bodyMedium,
790
+ fontWeight: '600',
791
+ },
792
+ errorContainer: {
793
+ flexDirection: 'row',
794
+ alignItems: 'center',
795
+ marginTop: -20,
796
+ marginBottom: 20,
797
+ },
798
+ errorIcon: {
799
+ marginRight: 6,
800
+ },
801
+ errorText: {
802
+ fontSize: 12,
803
+ color: colors.error || '#FF0000',
804
+ fontWeight: '500',
805
+ flex: 1,
806
+ },
807
+ loadingOverlay: {
808
+ position: 'absolute',
809
+ top: 0,
810
+ left: 0,
811
+ right: 0,
812
+ bottom: 0,
813
+ backgroundColor: 'rgba(0, 0, 0, 0.3)',
814
+ justifyContent: 'center',
815
+ alignItems: 'center',
816
+ zIndex: 1000,
817
+ },
818
+ });
819
+
820
+ export default Employee;
821
+
822
+