@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.
- package/README.md +184 -0
- package/lib/api/applicationApi.d.ts +1 -0
- package/lib/api/applicationApi.js +11 -0
- package/lib/api/bankApi.d.ts +352 -0
- package/lib/api/bankApi.js +54 -0
- package/lib/api/baseApi.d.ts +8 -0
- package/lib/api/baseApi.js +456 -0
- package/lib/api/customerApi.d.ts +855 -0
- package/lib/api/customerApi.js +213 -0
- package/lib/api/fdApi.d.ts +979 -0
- package/lib/api/fdApi.js +112 -0
- package/lib/api/fdCalculatorApi.d.ts +179 -0
- package/lib/api/fdCalculatorApi.js +36 -0
- package/lib/api/index.d.ts +14 -0
- package/lib/api/index.js +45 -0
- package/lib/api/interestRateApi.d.ts +585 -0
- package/lib/api/interestRateApi.js +101 -0
- package/lib/api/kycApi.d.ts +486 -0
- package/lib/api/kycApi.js +71 -0
- package/lib/api/masterDataApi.d.ts +158 -0
- package/lib/api/masterDataApi.js +32 -0
- package/lib/api/nomineeApi.d.ts +325 -0
- package/lib/api/nomineeApi.js +46 -0
- package/lib/api/onboardingApi.d.ts +192 -0
- package/lib/api/onboardingApi.js +41 -0
- package/lib/api/panApi.d.ts +0 -0
- package/lib/api/panApi.js +23 -0
- package/lib/api/paymentApi.d.ts +325 -0
- package/lib/api/paymentApi.js +46 -0
- package/lib/api/workflowApi.d.ts +654 -0
- package/lib/api/workflowApi.js +90 -0
- package/lib/assets/images/images.d.ts +4 -0
- package/lib/assets/images/images.js +10 -0
- package/lib/components/AadhaarInput.d.ts +13 -0
- package/lib/components/AadhaarInput.js +47 -0
- package/lib/components/ActionButton.d.ts +12 -0
- package/lib/components/ActionButton.js +87 -0
- package/lib/components/ActiveFDCard.d.ts +16 -0
- package/lib/components/ActiveFDCard.js +95 -0
- package/lib/components/AmountInput.d.ts +20 -0
- package/lib/components/AmountInput.js +144 -0
- package/lib/components/CheckboxOption.d.ts +11 -0
- package/lib/components/CheckboxOption.js +41 -0
- package/lib/components/CompanyHeader.d.ts +7 -0
- package/lib/components/CompanyHeader.js +57 -0
- package/lib/components/DropdownSelector.d.ts +9 -0
- package/lib/components/DropdownSelector.js +49 -0
- package/lib/components/EmptyState.d.ts +17 -0
- package/lib/components/EmptyState.js +44 -0
- package/lib/components/ErrorDisplay.d.ts +17 -0
- package/lib/components/ErrorDisplay.js +69 -0
- package/lib/components/FAQItem.d.ts +9 -0
- package/lib/components/FAQItem.js +52 -0
- package/lib/components/FDCard.d.ts +21 -0
- package/lib/components/FDCard.js +96 -0
- package/lib/components/FormDropdown.d.ts +18 -0
- package/lib/components/FormDropdown.js +155 -0
- package/lib/components/FormSection.d.ts +14 -0
- package/lib/components/FormSection.js +38 -0
- package/lib/components/Header.d.ts +14 -0
- package/lib/components/Header.js +52 -0
- package/lib/components/IFSCSearchResultCard.d.ts +13 -0
- package/lib/components/IFSCSearchResultCard.js +70 -0
- package/lib/components/InfoBox.d.ts +8 -0
- package/lib/components/InfoBox.js +39 -0
- package/lib/components/InterestRateCard.d.ts +8 -0
- package/lib/components/InterestRateCard.js +46 -0
- package/lib/components/LoadingIndicator.d.ts +12 -0
- package/lib/components/LoadingIndicator.js +30 -0
- package/lib/components/OTPInput.d.ts +17 -0
- package/lib/components/OTPInput.js +144 -0
- package/lib/components/PaymentDetailsCard.d.ts +20 -0
- package/lib/components/PaymentDetailsCard.js +68 -0
- package/lib/components/PendingFDBottomSheet.d.ts +18 -0
- package/lib/components/PendingFDBottomSheet.js +122 -0
- package/lib/components/SafeAreaWrapper.d.ts +13 -0
- package/lib/components/SafeAreaWrapper.js +41 -0
- package/lib/components/ScreenHeader.d.ts +11 -0
- package/lib/components/ScreenHeader.js +46 -0
- package/lib/components/StatusDisplay.d.ts +15 -0
- package/lib/components/StatusDisplay.js +88 -0
- package/lib/components/TextFieldWithLabel.d.ts +46 -0
- package/lib/components/TextFieldWithLabel.js +326 -0
- package/lib/components/TrustBox.d.ts +8 -0
- package/lib/components/TrustBox.js +45 -0
- package/lib/components/ValidationErrorAlert.d.ts +23 -0
- package/lib/components/ValidationErrorAlert.js +39 -0
- package/lib/components/ValidationMessage.d.ts +9 -0
- package/lib/components/ValidationMessage.js +98 -0
- package/lib/components/index.d.ts +35 -0
- package/lib/components/index.js +64 -0
- package/lib/config/apiConfig.d.ts +34 -0
- package/lib/config/apiConfig.js +158 -0
- package/lib/config/appDataConfig.d.ts +114 -0
- package/lib/config/appDataConfig.js +264 -0
- package/lib/config/encryptionConfig.d.ts +21 -0
- package/lib/config/encryptionConfig.js +61 -0
- package/lib/config/workflowConstants.d.ts +37 -0
- package/lib/config/workflowConstants.js +38 -0
- package/lib/constants/strings/bank.d.ts +72 -0
- package/lib/constants/strings/bank.js +86 -0
- package/lib/constants/strings/base64Images.d.ts +25 -0
- package/lib/constants/strings/base64Images.js +28 -0
- package/lib/constants/strings/common.d.ts +53 -0
- package/lib/constants/strings/common.js +62 -0
- package/lib/constants/strings/employee.d.ts +61 -0
- package/lib/constants/strings/employee.js +77 -0
- package/lib/constants/strings/faq.d.ts +14 -0
- package/lib/constants/strings/faq.js +20 -0
- package/lib/constants/strings/fd.d.ts +122 -0
- package/lib/constants/strings/fd.js +151 -0
- package/lib/constants/strings/home.d.ts +49 -0
- package/lib/constants/strings/home.js +62 -0
- package/lib/constants/strings/index.d.ts +16 -0
- package/lib/constants/strings/index.js +44 -0
- package/lib/constants/strings/kyc.d.ts +80 -0
- package/lib/constants/strings/kyc.js +94 -0
- package/lib/constants/strings/nominee.d.ts +64 -0
- package/lib/constants/strings/nominee.js +81 -0
- package/lib/hooks/useAuth.d.ts +25 -0
- package/lib/hooks/useAuth.js +39 -0
- package/lib/hooks/useFDData.d.ts +11 -0
- package/lib/hooks/useFDData.js +40 -0
- package/lib/index.d.ts +69 -0
- package/lib/index.js +182 -0
- package/lib/navigation/RootNavigator.d.ts +8 -0
- package/lib/navigation/RootNavigator.js +205 -0
- package/lib/navigation/SimpleNavigator.d.ts +11 -0
- package/lib/navigation/SimpleNavigator.js +107 -0
- package/lib/navigation/helpers.d.ts +11 -0
- package/lib/navigation/helpers.js +83 -0
- package/lib/navigation/index.d.ts +15 -0
- package/lib/navigation/index.js +42 -0
- package/lib/navigation/types.d.ts +113 -0
- package/lib/navigation/types.js +2 -0
- package/lib/navigation/workflowNavigator.d.ts +22 -0
- package/lib/navigation/workflowNavigator.js +104 -0
- package/lib/providers/ApiProvider.d.ts +7 -0
- package/lib/providers/ApiProvider.js +34 -0
- package/lib/providers/MasterDataProvider.d.ts +10 -0
- package/lib/providers/MasterDataProvider.js +54 -0
- package/lib/screens/AadhaarVerification.d.ts +7 -0
- package/lib/screens/AadhaarVerification.js +627 -0
- package/lib/screens/AddBankAccount.d.ts +22 -0
- package/lib/screens/AddBankAccount.js +381 -0
- package/lib/screens/BankDetail.d.ts +16 -0
- package/lib/screens/BankDetail.js +596 -0
- package/lib/screens/BookFD.d.ts +0 -0
- package/lib/screens/BookFD.js +315 -0
- package/lib/screens/Employee.d.ts +18 -0
- package/lib/screens/Employee.js +594 -0
- package/lib/screens/FDCalculator.d.ts +18 -0
- package/lib/screens/FDCalculator.js +759 -0
- package/lib/screens/FDList.d.ts +27 -0
- package/lib/screens/FDList.js +1008 -0
- package/lib/screens/FindIFSC.d.ts +16 -0
- package/lib/screens/FindIFSC.js +248 -0
- package/lib/screens/Home.d.ts +0 -0
- package/lib/screens/Home.js +143 -0
- package/lib/screens/NomineeDetail.d.ts +17 -0
- package/lib/screens/NomineeDetail.js +592 -0
- package/lib/screens/PayNow.d.ts +14 -0
- package/lib/screens/PayNow.js +230 -0
- package/lib/screens/Payment.d.ts +11 -0
- package/lib/screens/Payment.js +191 -0
- package/lib/screens/PaymentStatus.d.ts +16 -0
- package/lib/screens/PaymentStatus.js +397 -0
- package/lib/screens/ReviewKYC.d.ts +21 -0
- package/lib/screens/ReviewKYC.js +660 -0
- package/lib/state/paymentSession.d.ts +8 -0
- package/lib/state/paymentSession.js +13 -0
- package/lib/store/fdListSelectedSlice.d.ts +21 -0
- package/lib/store/fdListSelectedSlice.js +26 -0
- package/lib/store/hooks.d.ts +8 -0
- package/lib/store/hooks.js +31 -0
- package/lib/store/index.d.ts +3 -0
- package/lib/store/index.js +8 -0
- package/lib/store/onboardingSlice.d.ts +12 -0
- package/lib/store/onboardingSlice.js +32 -0
- package/lib/store/store.d.ts +13 -0
- package/lib/store/store.js +33 -0
- package/lib/theme/ThemeContext.d.ts +210 -0
- package/lib/theme/ThemeContext.js +90 -0
- package/lib/theme/colors.d.ts +80 -0
- package/lib/theme/colors.js +85 -0
- package/lib/theme/index.d.ts +34 -0
- package/lib/theme/index.js +69 -0
- package/lib/theme/shadows.d.ts +53 -0
- package/lib/theme/shadows.js +58 -0
- package/lib/theme/typography.d.ts +134 -0
- package/lib/theme/typography.js +143 -0
- package/lib/types/dataTypes.d.ts +34 -0
- package/lib/types/dataTypes.js +2 -0
- package/lib/types/workflowTypes.d.ts +2 -0
- package/lib/types/workflowTypes.js +2 -0
- package/lib/utils/apiLogger.d.ts +48 -0
- package/lib/utils/apiLogger.js +105 -0
- package/lib/utils/encryption.d.ts +28 -0
- package/lib/utils/encryption.js +113 -0
- package/lib/utils/getFDData.d.ts +48 -0
- package/lib/utils/getFDData.js +154 -0
- package/lib/utils/globalData.d.ts +2 -0
- package/lib/utils/globalData.js +10 -0
- package/package.json +76 -0
- package/src/api/applicationApi.ts +12 -0
- package/src/api/bankApi.ts +42 -0
- package/src/api/baseApi.ts +513 -0
- package/src/api/customerApi.ts +291 -0
- package/src/api/fdApi.ts +150 -0
- package/src/api/fdCalculatorApi.ts +41 -0
- package/src/api/index.ts +29 -0
- package/src/api/interestRateApi.ts +143 -0
- package/src/api/kycApi.ts +63 -0
- package/src/api/masterDataApi.ts +34 -0
- package/src/api/nomineeApi.ts +34 -0
- package/src/api/onboardingApi.ts +64 -0
- package/src/api/panApi.ts +25 -0
- package/src/api/paymentApi.ts +34 -0
- package/src/api/workflowApi.ts +94 -0
- package/src/assets/images/arrow-filled.png +0 -0
- package/src/assets/images/arrow-left.png +0 -0
- package/src/assets/images/backicon.png +0 -0
- package/src/assets/images/calendar.png +0 -0
- package/src/assets/images/chevron-down.png +0 -0
- package/src/assets/images/chevron-down@2x.png +0 -0
- package/src/assets/images/chevron-down@3x.png +0 -0
- package/src/assets/images/images.js +8 -0
- package/src/components/AadhaarInput.tsx +91 -0
- package/src/components/ActionButton.tsx +129 -0
- package/src/components/ActiveFDCard.tsx +158 -0
- package/src/components/AmountInput.tsx +217 -0
- package/src/components/CheckboxOption.tsx +93 -0
- package/src/components/CompanyHeader.tsx +78 -0
- package/src/components/DropdownSelector.tsx +77 -0
- package/src/components/EmptyState.tsx +109 -0
- package/src/components/ErrorDisplay.tsx +135 -0
- package/src/components/FAQItem.tsx +90 -0
- package/src/components/FDCard.tsx +165 -0
- package/src/components/FormDropdown.tsx +214 -0
- package/src/components/FormSection.tsx +86 -0
- package/src/components/Header.tsx +110 -0
- package/src/components/IFSCSearchResultCard.tsx +139 -0
- package/src/components/InfoBox.tsx +55 -0
- package/src/components/InterestRateCard.tsx +77 -0
- package/src/components/LoadingIndicator.tsx +63 -0
- package/src/components/OTPInput.tsx +213 -0
- package/src/components/PaymentDetailsCard.tsx +120 -0
- package/src/components/PendingFDBottomSheet.tsx +235 -0
- package/src/components/README.md +210 -0
- package/src/components/SafeAreaWrapper.tsx +68 -0
- package/src/components/ScreenHeader.tsx +83 -0
- package/src/components/StatusDisplay.tsx +139 -0
- package/src/components/TextFieldWithLabel.tsx +502 -0
- package/src/components/TrustBox.tsx +63 -0
- package/src/components/ValidationErrorAlert.tsx +57 -0
- package/src/components/ValidationMessage.tsx +134 -0
- package/src/components/index.tsx +47 -0
- package/src/config/apiConfig.ts +217 -0
- package/src/config/appDataConfig.ts +279 -0
- package/src/config/encryptionConfig.ts +65 -0
- package/src/config/workflowConstants.ts +43 -0
- package/src/constants/strings/README.md +146 -0
- package/src/constants/strings/bank.ts +92 -0
- package/src/constants/strings/base64Images.ts +29 -0
- package/src/constants/strings/common.ts +63 -0
- package/src/constants/strings/employee.ts +85 -0
- package/src/constants/strings/faq.ts +23 -0
- package/src/constants/strings/fd.ts +172 -0
- package/src/constants/strings/home.ts +67 -0
- package/src/constants/strings/index.ts +21 -0
- package/src/constants/strings/kyc.ts +100 -0
- package/src/constants/strings/nominee.ts +90 -0
- package/src/hooks/useAuth.ts +42 -0
- package/src/hooks/useFDData.ts +48 -0
- package/src/index.tsx +173 -0
- package/src/navigation/RootNavigator.tsx +352 -0
- package/src/navigation/SimpleNavigator.tsx +107 -0
- package/src/navigation/helpers.ts +85 -0
- package/src/navigation/index.tsx +81 -0
- package/src/navigation/types.ts +124 -0
- package/src/navigation/workflowNavigator.ts +131 -0
- package/src/providers/ApiProvider.tsx +43 -0
- package/src/providers/MasterDataProvider.tsx +30 -0
- package/src/screens/AadhaarVerification.tsx +809 -0
- package/src/screens/AddBankAccount.tsx +541 -0
- package/src/screens/BankDetail.tsx +826 -0
- package/src/screens/BookFD.tsx +330 -0
- package/src/screens/Employee.tsx +822 -0
- package/src/screens/FDCalculator.tsx +987 -0
- package/src/screens/FDList.tsx +1284 -0
- package/src/screens/FindIFSC.tsx +332 -0
- package/src/screens/Home.tsx +152 -0
- package/src/screens/NomineeDetail.tsx +800 -0
- package/src/screens/PayNow.tsx +282 -0
- package/src/screens/Payment.tsx +224 -0
- package/src/screens/PaymentStatus.tsx +561 -0
- package/src/screens/ReviewKYC.tsx +956 -0
- package/src/state/paymentSession.ts +13 -0
- package/src/store/fdListSelectedSlice.ts +42 -0
- package/src/store/hooks.ts +27 -0
- package/src/store/index.ts +3 -0
- package/src/store/onboardingSlice.ts +37 -0
- package/src/store/store.ts +35 -0
- package/src/theme/ThemeContext.tsx +82 -0
- package/src/theme/colors.ts +90 -0
- package/src/theme/index.ts +64 -0
- package/src/theme/shadows.ts +61 -0
- package/src/theme/typography.ts +151 -0
- package/src/types/dataTypes.ts +37 -0
- package/src/types/env.d.ts +93 -0
- package/src/types/workflowTypes.ts +12 -0
- package/src/utils/apiLogger.ts +166 -0
- package/src/utils/encryption.ts +159 -0
- package/src/utils/getFDData.ts +175 -0
- 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
|
+
|