@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,800 @@
|
|
|
1
|
+
|
|
2
|
+
import React, { useState, useEffect, useRef } from 'react';
|
|
3
|
+
import { View, Text, StyleSheet, ScrollView, TouchableOpacity, Alert, Platform, Image, TextInput, TouchableWithoutFeedback, BackHandler, ActivityIndicator, KeyboardAvoidingView } from 'react-native';
|
|
4
|
+
import Icon from 'react-native-vector-icons/Ionicons';
|
|
5
|
+
import DateTimePicker from '@react-native-community/datetimepicker';
|
|
6
|
+
// import DatePicker from 'react-native-date-picker'; // Commented out old date picker
|
|
7
|
+
import SafeAreaWrapper from '../components/SafeAreaWrapper';
|
|
8
|
+
import { Header } from '../components';
|
|
9
|
+
import ActionButton from '../components/ActionButton';
|
|
10
|
+
import TextFieldWithLabel from '../components/TextFieldWithLabel';
|
|
11
|
+
import { useColors, useTypography, useTheme } from '../theme/ThemeContext';
|
|
12
|
+
import { useCustomerNomineeMutation, useGetCustomerApplicationDetailsMutation } from '../api/customerApi';
|
|
13
|
+
import { useMasterData } from '../providers/MasterDataProvider';
|
|
14
|
+
import { useAppSelector } from '../store';
|
|
15
|
+
import { getUserInfoForAPI } from '../config/appDataConfig';
|
|
16
|
+
import { usePreviousStateMutation } from '../api/workflowApi';
|
|
17
|
+
import { useFocusEffect } from '@react-navigation/native';
|
|
18
|
+
import { navigate } from '../navigation/helpers';
|
|
19
|
+
import { base64Images } from '../constants/strings/base64Images';
|
|
20
|
+
import { NOMINEE_STRINGS } from '../constants/strings/nominee';
|
|
21
|
+
import { COMMON_STRINGS } from '../constants/strings/common';
|
|
22
|
+
|
|
23
|
+
export interface NomineeDetailProps {
|
|
24
|
+
onGoBack?: () => void;
|
|
25
|
+
onSave?: (data: NomineeData) => void;
|
|
26
|
+
initialData?: Partial<NomineeData>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface NomineeData {
|
|
30
|
+
makeNomination: string;
|
|
31
|
+
nomineeTitle: string;
|
|
32
|
+
nomineeName: string;
|
|
33
|
+
relationship: string;
|
|
34
|
+
customRelationship?: string;
|
|
35
|
+
dateOfBirth: string;
|
|
36
|
+
printNameOnCertificate: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const makeNominationOptions = ['Yes', 'No'];
|
|
40
|
+
|
|
41
|
+
const NomineeDetail: React.FC<NomineeDetailProps> = ({ onGoBack, onSave, initialData }) => {
|
|
42
|
+
const colors = useColors();
|
|
43
|
+
const typography = useTypography();
|
|
44
|
+
const { themeName } = useTheme();
|
|
45
|
+
const styles = createStyles(colors, typography, themeName);
|
|
46
|
+
|
|
47
|
+
const { masterData, setMasterData } = useMasterData();
|
|
48
|
+
const workflowInstanceId = useAppSelector((state: any) => state?.onboarding?.workflowInstanceId);
|
|
49
|
+
const applicationId = useAppSelector((state: any) => state?.onboarding?.applicationId);
|
|
50
|
+
const customerId = useAppSelector((state: any) => state?.onboarding?.customerId);
|
|
51
|
+
const defaultProviderId = useAppSelector((state: any) => state?.onboarding?.providerId);// Default Shriram provider ID
|
|
52
|
+
|
|
53
|
+
const [customerNominee, {
|
|
54
|
+
data: customerNomineeResponse,
|
|
55
|
+
error: customerNomineeError,
|
|
56
|
+
isLoading: isLoadingCustomerNominee,
|
|
57
|
+
}] = useCustomerNomineeMutation();
|
|
58
|
+
const [previousState] = usePreviousStateMutation();
|
|
59
|
+
const [getCustomerApplicationDetails] = useGetCustomerApplicationDetailsMutation();
|
|
60
|
+
|
|
61
|
+
// Master data state
|
|
62
|
+
// const [masterData, setMasterData] = useState<{
|
|
63
|
+
// nomineePrefix: string[];
|
|
64
|
+
// nomineeRelation: string[];
|
|
65
|
+
// }>({
|
|
66
|
+
// nomineePrefix: [],
|
|
67
|
+
// nomineeRelation: [],
|
|
68
|
+
// });
|
|
69
|
+
|
|
70
|
+
// Loading state for back navigation
|
|
71
|
+
const [isGoingBack, setIsGoingBack] = useState(false);
|
|
72
|
+
|
|
73
|
+
// Form state
|
|
74
|
+
const [form, setForm] = useState<NomineeData>({
|
|
75
|
+
makeNomination: initialData?.makeNomination || 'Yes',
|
|
76
|
+
nomineeTitle: '', // will be set after master data loads
|
|
77
|
+
nomineeName: initialData?.nomineeName || '',
|
|
78
|
+
relationship: '', // will be set after master data loads
|
|
79
|
+
customRelationship: (initialData as any)?.customRelationship || '',
|
|
80
|
+
dateOfBirth: initialData?.dateOfBirth || '',
|
|
81
|
+
printNameOnCertificate: initialData?.printNameOnCertificate ?? false,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
// Dropdown menu state
|
|
86
|
+
const [openMenus, setOpenMenus] = useState({
|
|
87
|
+
makeNomination: false,
|
|
88
|
+
title: false,
|
|
89
|
+
relationship: false,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Error state for custom relationship
|
|
93
|
+
const [fieldErrors, setFieldErrors] = useState<{ [key: string]: string }>({});
|
|
94
|
+
|
|
95
|
+
// Date picker state
|
|
96
|
+
const [showDatePicker, setShowDatePicker] = useState(false);
|
|
97
|
+
const datePickerLockRef = useRef(false);
|
|
98
|
+
const [selectedDate, setSelectedDate] = useState(new Date());
|
|
99
|
+
|
|
100
|
+
// ScrollView ref for auto-scrolling
|
|
101
|
+
const scrollViewRef = useRef<ScrollView>(null);
|
|
102
|
+
|
|
103
|
+
// Ref for nominee name text field
|
|
104
|
+
const nomineeNameRef = useRef<TextInput>(null);
|
|
105
|
+
|
|
106
|
+
// Derive nominee prefix and relationship options from master data
|
|
107
|
+
const normalizeOptions = (raw: any): string[] => {
|
|
108
|
+
if (Array.isArray(raw)) return raw.map((v) => String(v)).filter(Boolean);
|
|
109
|
+
if (typeof raw === 'string' && raw.trim().length) return raw.split(',').map((v) => v.trim()).filter(Boolean);
|
|
110
|
+
return [];
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const mdRoot: any = (masterData as any)?.data || masterData || {};
|
|
114
|
+
|
|
115
|
+
const nomineePrefixOptions: string[] = React.useMemo(() => {
|
|
116
|
+
const raw = mdRoot?.nomineePrefix || mdRoot?.titleOptions || mdRoot?.prefixOptions;
|
|
117
|
+
const opts = normalizeOptions(raw);
|
|
118
|
+
return opts.length ? opts : ['Mr', 'Ms', 'Mrsww'];
|
|
119
|
+
}, [mdRoot]);
|
|
120
|
+
|
|
121
|
+
const nomineeRelationOptions: string[] = React.useMemo(() => {
|
|
122
|
+
const raw = mdRoot?.nomineeRelation || mdRoot?.relationshipOptions || mdRoot?.relations || mdRoot?.relationOptions;
|
|
123
|
+
const opts = normalizeOptions(raw);
|
|
124
|
+
return opts.length ? opts : ['Father', 'Mother', 'Spouse', 'Son', 'Daughter', 'Others'];
|
|
125
|
+
}, [mdRoot]);
|
|
126
|
+
|
|
127
|
+
// Do not auto-select defaults; placeholders should remain if API does not provide data
|
|
128
|
+
// Intentionally no-op here to avoid preselecting values
|
|
129
|
+
useEffect(() => {
|
|
130
|
+
// no-op to preserve empty state when no API data
|
|
131
|
+
}, [nomineePrefixOptions, nomineeRelationOptions]);
|
|
132
|
+
|
|
133
|
+
// Sync selectedDate with form.dateOfBirth when form data changes
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
if (form.dateOfBirth && form.dateOfBirth.trim() !== '') {
|
|
136
|
+
const dateParts = form.dateOfBirth.split('/');
|
|
137
|
+
if (dateParts.length === 3) {
|
|
138
|
+
const day = parseInt(dateParts[0], 10);
|
|
139
|
+
const month = parseInt(dateParts[1], 10);
|
|
140
|
+
const year = parseInt(dateParts[2], 10);
|
|
141
|
+
|
|
142
|
+
if (day >= 1 && day <= 31 && month >= 1 && month <= 12 && year >= 1900 && year <= new Date().getFullYear()) {
|
|
143
|
+
const parsedDate = new Date(year, month - 1, day);
|
|
144
|
+
if (!isNaN(parsedDate.getTime()) && parsedDate.getDate() === day && parsedDate.getMonth() === month - 1 && parsedDate.getFullYear() === year) {
|
|
145
|
+
setSelectedDate(parsedDate);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}, [form.dateOfBirth]);
|
|
151
|
+
|
|
152
|
+
// Fetch customer application details on focus and set form from nominee data
|
|
153
|
+
useFocusEffect(
|
|
154
|
+
React.useCallback(() => {
|
|
155
|
+
let isActive = true;
|
|
156
|
+
(async () => {
|
|
157
|
+
try {
|
|
158
|
+
const userInfo = getUserInfoForAPI();
|
|
159
|
+
const req = {
|
|
160
|
+
providerId: defaultProviderId,
|
|
161
|
+
workflowInstanceId,
|
|
162
|
+
userreferenceid: userInfo.userReferenceId,
|
|
163
|
+
applicationid: applicationId,
|
|
164
|
+
entityid: '',
|
|
165
|
+
applicationId: applicationId,
|
|
166
|
+
customerId: customerId,
|
|
167
|
+
} as any;
|
|
168
|
+
const res = await getCustomerApplicationDetails(req).unwrap();
|
|
169
|
+
if (!isActive) return;
|
|
170
|
+
const nominee = res?.data?.nominee;
|
|
171
|
+
if (nominee && Object.keys(nominee).length > 0) {
|
|
172
|
+
// ✅ Prefill only if nominee has valid data
|
|
173
|
+
const makeNomination = nominee?.intend_to_nominate ? 'Yes' : 'No';
|
|
174
|
+
const convertFromISO = (iso: string): string => {
|
|
175
|
+
if (!iso) return '';
|
|
176
|
+
const [y, m, d] = iso.split('-');
|
|
177
|
+
if (y && m && d) return `${d.padStart(2, '0')}/${m.padStart(2, '0')}/${y}`;
|
|
178
|
+
return '';
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
setForm(prev => ({
|
|
182
|
+
...prev,
|
|
183
|
+
makeNomination,
|
|
184
|
+
nomineeTitle: nominee?.prefix || '',
|
|
185
|
+
nomineeName: nominee?.full_name || '',
|
|
186
|
+
relationship: nominee?.relation || '',
|
|
187
|
+
dateOfBirth: convertFromISO((nominee as any)?.dob || ''),
|
|
188
|
+
printNameOnCertificate: !!nominee?.print_name_on_certificate,
|
|
189
|
+
}));
|
|
190
|
+
} else {
|
|
191
|
+
// 🧹 No nominee data — leave dropdowns empty
|
|
192
|
+
setForm({
|
|
193
|
+
makeNomination: '',
|
|
194
|
+
nomineeTitle: '',
|
|
195
|
+
nomineeName: '',
|
|
196
|
+
relationship: '',
|
|
197
|
+
dateOfBirth: '',
|
|
198
|
+
printNameOnCertificate: false,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
} catch (e) {
|
|
202
|
+
// On error, do not prefill anything
|
|
203
|
+
setForm({
|
|
204
|
+
makeNomination: '',
|
|
205
|
+
nomineeTitle: '', // Empty to show placeholder "No"
|
|
206
|
+
nomineeName: '',
|
|
207
|
+
relationship: '', // Empty to show placeholder "No"
|
|
208
|
+
dateOfBirth: '',
|
|
209
|
+
printNameOnCertificate: false,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
})();
|
|
213
|
+
return () => { isActive = false; };
|
|
214
|
+
}, [defaultProviderId, workflowInstanceId, applicationId, customerId, getCustomerApplicationDetails])
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
const closeAllMenus = () => {
|
|
218
|
+
setOpenMenus({ makeNomination: false, title: false, relationship: false });
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const updateField = (field: keyof NomineeData, value: string | boolean) => {
|
|
222
|
+
closeAllMenus();
|
|
223
|
+
setForm(prev => {
|
|
224
|
+
// Handle dependent clearing when user selects No for nomination
|
|
225
|
+
if (field === 'makeNomination') {
|
|
226
|
+
const isYes = String(value).toLowerCase() === 'yes';
|
|
227
|
+
if (!isYes) {
|
|
228
|
+
return {
|
|
229
|
+
...prev,
|
|
230
|
+
makeNomination: String(value),
|
|
231
|
+
nomineeTitle: '',
|
|
232
|
+
nomineeName: '',
|
|
233
|
+
relationship: '',
|
|
234
|
+
customRelationship: '',
|
|
235
|
+
dateOfBirth: '',
|
|
236
|
+
printNameOnCertificate: false,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
// If switching to Yes and fields are empty, prefill with first master data options
|
|
240
|
+
return {
|
|
241
|
+
...prev,
|
|
242
|
+
makeNomination: String(value),
|
|
243
|
+
nomineeTitle: prev.nomineeTitle || "",
|
|
244
|
+
relationship: prev.relationship || "",
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
const next = { ...prev, [field]: value } as NomineeData;
|
|
248
|
+
if (field === 'relationship') {
|
|
249
|
+
const rel = String(value || '').trim().toLowerCase();
|
|
250
|
+
const isOther = rel === 'other' || rel === 'others';
|
|
251
|
+
if (!isOther) {
|
|
252
|
+
(next as any).customRelationship = '';
|
|
253
|
+
setFieldErrors(prevErr => ({ ...prevErr, customRelationship: '' }));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return next;
|
|
257
|
+
});
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const toggleMenu = (key: keyof typeof openMenus) => {
|
|
261
|
+
// Remove focus from nominee name field when any dropdown is clicked
|
|
262
|
+
nomineeNameRef.current?.blur();
|
|
263
|
+
|
|
264
|
+
setOpenMenus(prev => ({
|
|
265
|
+
makeNomination: key === 'makeNomination' ? !prev.makeNomination : false,
|
|
266
|
+
title: key === 'title' ? !prev.title : false,
|
|
267
|
+
relationship: key === 'relationship' ? !prev.relationship : false,
|
|
268
|
+
}));
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
// Date picker functions
|
|
272
|
+
const openDatePicker = () => {
|
|
273
|
+
if (datePickerLockRef.current || showDatePicker) return;
|
|
274
|
+
datePickerLockRef.current = true;
|
|
275
|
+
closeAllMenus();
|
|
276
|
+
// Remove focus from nominee name field when date picker is opened
|
|
277
|
+
nomineeNameRef.current?.blur();
|
|
278
|
+
|
|
279
|
+
// Parse existing date from field
|
|
280
|
+
if (form.dateOfBirth && form.dateOfBirth.trim() !== '') {
|
|
281
|
+
const dateParts = form.dateOfBirth.split('/');
|
|
282
|
+
if (dateParts.length === 3) {
|
|
283
|
+
const day = parseInt(dateParts[0], 10);
|
|
284
|
+
const month = parseInt(dateParts[1], 10);
|
|
285
|
+
const year = parseInt(dateParts[2], 10);
|
|
286
|
+
|
|
287
|
+
// Validate the parsed values
|
|
288
|
+
if (day >= 1 && day <= 31 && month >= 1 && month <= 12 && year >= 1900 && year <= new Date().getFullYear()) {
|
|
289
|
+
const parsedDate = new Date(year, month - 1, day);
|
|
290
|
+
if (!isNaN(parsedDate.getTime()) && parsedDate.getDate() === day && parsedDate.getMonth() === month - 1 && parsedDate.getFullYear() === year) {
|
|
291
|
+
setSelectedDate(parsedDate);
|
|
292
|
+
} else {
|
|
293
|
+
// If parsing fails, use current date
|
|
294
|
+
setSelectedDate(new Date());
|
|
295
|
+
}
|
|
296
|
+
} else {
|
|
297
|
+
// If validation fails, use current date
|
|
298
|
+
setSelectedDate(new Date());
|
|
299
|
+
}
|
|
300
|
+
} else {
|
|
301
|
+
// If format is invalid, use current date
|
|
302
|
+
setSelectedDate(new Date());
|
|
303
|
+
}
|
|
304
|
+
} else {
|
|
305
|
+
// If no date in field, use current date
|
|
306
|
+
setSelectedDate(new Date());
|
|
307
|
+
}
|
|
308
|
+
setShowDatePicker(true);
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
const closeDatePicker = () => {
|
|
312
|
+
setShowDatePicker(false);
|
|
313
|
+
// Release the lock shortly after closing to avoid immediate re-open from the same tap
|
|
314
|
+
setTimeout(() => { datePickerLockRef.current = false; }, 500);
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const onDateChange = (event: any, selectedDate?: Date) => {
|
|
318
|
+
|
|
319
|
+
closeDatePicker();
|
|
320
|
+
if (Platform.OS === 'android') {
|
|
321
|
+
// Android fires with types: 'set' and 'dismissed'
|
|
322
|
+
if (event?.type === 'set' && selectedDate) {
|
|
323
|
+
setSelectedDate(selectedDate);
|
|
324
|
+
|
|
325
|
+
const today = new Date();
|
|
326
|
+
let age = today.getFullYear() - selectedDate.getFullYear();
|
|
327
|
+
const m = today.getMonth() - selectedDate.getMonth();
|
|
328
|
+
if (m < 0 || (m === 0 && today.getDate() < selectedDate.getDate())) {
|
|
329
|
+
age--;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (age < 18) {
|
|
333
|
+
updateField('dateOfBirth', '');
|
|
334
|
+
Alert.alert('Invalid Age', 'Nominee must be at least 18 years old.');
|
|
335
|
+
closeDatePicker();
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const day = selectedDate.getDate().toString().padStart(2, '0');
|
|
340
|
+
const month = (selectedDate.getMonth() + 1).toString().padStart(2, '0');
|
|
341
|
+
const year = selectedDate.getFullYear().toString();
|
|
342
|
+
const formattedDate = `${day}/${month}/${year}`;
|
|
343
|
+
updateField('dateOfBirth', formattedDate);
|
|
344
|
+
closeDatePicker();
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
if (event?.type === 'dismissed') {
|
|
348
|
+
closeDatePicker();
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// iOS inline/spinner handling
|
|
355
|
+
if (selectedDate) {
|
|
356
|
+
setSelectedDate(selectedDate);
|
|
357
|
+
const day = selectedDate.getDate().toString().padStart(2, '0');
|
|
358
|
+
const month = (selectedDate.getMonth() + 1).toString().padStart(2, '0');
|
|
359
|
+
const year = selectedDate.getFullYear().toString();
|
|
360
|
+
const formattedDate = `${day}/${month}/${year}`;
|
|
361
|
+
updateField('dateOfBirth', formattedDate);
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
const renderDropdown = (
|
|
366
|
+
label: string,
|
|
367
|
+
value: string,
|
|
368
|
+
field: keyof NomineeData,
|
|
369
|
+
options: string[],
|
|
370
|
+
menuKey: keyof typeof openMenus,
|
|
371
|
+
) => {
|
|
372
|
+
// Determine placeholder based on field
|
|
373
|
+
let placeholderText: string = NOMINEE_STRINGS.MAKE_NOMINATION_PLACEHOLDER;
|
|
374
|
+
if (field === 'relationship') {
|
|
375
|
+
placeholderText = NOMINEE_STRINGS.RELATIONSHIP_PLACEHOLDER;
|
|
376
|
+
} else if (field === 'nomineeTitle') {
|
|
377
|
+
placeholderText = NOMINEE_STRINGS.NOMINEE_TITLE_PLACEHOLDER;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return (
|
|
381
|
+
<View>
|
|
382
|
+
<TextFieldWithLabel
|
|
383
|
+
label={label}
|
|
384
|
+
value={value}
|
|
385
|
+
onChangeText={(text) => updateField(field, text)}
|
|
386
|
+
variant="dropdown"
|
|
387
|
+
options={options}
|
|
388
|
+
isDropdownOpen={openMenus[menuKey]}
|
|
389
|
+
onDropdownToggle={() => toggleMenu(menuKey)}
|
|
390
|
+
onDropdownSelect={(option) => {
|
|
391
|
+
closeAllMenus();
|
|
392
|
+
updateField(field, option);
|
|
393
|
+
setOpenMenus(prev => ({ ...prev, [menuKey]: false }));
|
|
394
|
+
}}
|
|
395
|
+
placeholder={placeholderText}
|
|
396
|
+
placeholderColor={themeName === 'dark' ? colors.placeholderColor : 'rgba(0,32,34,0.2)'}
|
|
397
|
+
/>
|
|
398
|
+
{openMenus[menuKey] && (
|
|
399
|
+
<View style={styles.inlineMenu}>
|
|
400
|
+
{options.map(option => (
|
|
401
|
+
<TouchableOpacity
|
|
402
|
+
key={option}
|
|
403
|
+
style={styles.modalOption}
|
|
404
|
+
onPress={() => {
|
|
405
|
+
closeAllMenus();
|
|
406
|
+
updateField(field, option);
|
|
407
|
+
setOpenMenus(prev => ({ ...prev, [menuKey]: false }));
|
|
408
|
+
}}
|
|
409
|
+
>
|
|
410
|
+
<Text style={styles.modalOptionText}>{option}</Text>
|
|
411
|
+
</TouchableOpacity>
|
|
412
|
+
))}
|
|
413
|
+
</View>
|
|
414
|
+
)}
|
|
415
|
+
{/* Show custom relationship field when 'Other' selected */}
|
|
416
|
+
{field === 'relationship' && ((String(value || '').trim().toLowerCase() === 'other') || (String(value || '').trim().toLowerCase() === 'others')) && (
|
|
417
|
+
<View>
|
|
418
|
+
<TextFieldWithLabel
|
|
419
|
+
label=""
|
|
420
|
+
containerStyle={{ marginTop: -40 }}
|
|
421
|
+
value={form.customRelationship || ''}
|
|
422
|
+
onChangeText={(text) => {
|
|
423
|
+
const lettersOnly = text.replace(/[^A-Za-z ]/g, '');
|
|
424
|
+
// Validate on change
|
|
425
|
+
const rel = String(form.relationship || '').trim().toLowerCase();
|
|
426
|
+
const isOther = rel === 'other' || rel === 'others';
|
|
427
|
+
if (isOther) {
|
|
428
|
+
if (!lettersOnly || lettersOnly.trim().length === 0) {
|
|
429
|
+
setFieldErrors(prev => ({ ...prev, customRelationship: 'Relationship is required' }));
|
|
430
|
+
} else if (lettersOnly.trim().length < 2) {
|
|
431
|
+
setFieldErrors(prev => ({ ...prev, customRelationship: 'Relationship must be at least 2 characters' }));
|
|
432
|
+
} else {
|
|
433
|
+
setFieldErrors(prev => ({ ...prev, customRelationship: '' }));
|
|
434
|
+
}
|
|
435
|
+
} else {
|
|
436
|
+
setFieldErrors(prev => ({ ...prev, customRelationship: '' }));
|
|
437
|
+
}
|
|
438
|
+
updateField('customRelationship' as any, lettersOnly);
|
|
439
|
+
}}
|
|
440
|
+
placeholder={'Enter Custom Relationship'}
|
|
441
|
+
placeholderColor={themeName === 'dark' ? colors.placeholderColor : 'rgba(0,0,0,0.2)'}
|
|
442
|
+
variant="text"
|
|
443
|
+
/>
|
|
444
|
+
{fieldErrors.customRelationship ? (
|
|
445
|
+
<View style={styles.errorContainer}>
|
|
446
|
+
{Platform.OS === 'android' && (
|
|
447
|
+
<Icon name="warning" size={16} color={colors.error || '#FF0000'} style={styles.errorIcon} />
|
|
448
|
+
)}
|
|
449
|
+
<Text style={styles.errorText}>{fieldErrors.customRelationship}</Text>
|
|
450
|
+
</View>
|
|
451
|
+
) : null}
|
|
452
|
+
</View>
|
|
453
|
+
)}
|
|
454
|
+
</View>
|
|
455
|
+
);
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
const renderNomineeNameFields = () => (
|
|
459
|
+
<View>
|
|
460
|
+
<Text style={styles.fieldLabel}>{NOMINEE_STRINGS.NOMINEE_NAME_LABEL}</Text>
|
|
461
|
+
<View style={styles.nameFieldsContainer}>
|
|
462
|
+
<View style={styles.titleField}>
|
|
463
|
+
{renderDropdown('', form.nomineeTitle, 'nomineeTitle', nomineePrefixOptions, 'title')}
|
|
464
|
+
</View>
|
|
465
|
+
<View style={styles.nameField}>
|
|
466
|
+
<TextFieldWithLabel
|
|
467
|
+
label=""
|
|
468
|
+
value={form.nomineeName}
|
|
469
|
+
onChangeText={(text) => { closeAllMenus(); updateField('nomineeName', text); }}
|
|
470
|
+
onFocus={() => { closeAllMenus(); }}
|
|
471
|
+
textInputRef={nomineeNameRef}
|
|
472
|
+
placeholder={NOMINEE_STRINGS.NOMINEE_NAME_PLACEHOLDER}
|
|
473
|
+
placeholderColor={themeName === 'dark' ? colors.placeholderColor : 'rgba(0,0,0,0.2)'}
|
|
474
|
+
variant="text"
|
|
475
|
+
/>
|
|
476
|
+
</View>
|
|
477
|
+
</View>
|
|
478
|
+
</View>
|
|
479
|
+
);
|
|
480
|
+
|
|
481
|
+
const renderDateField = (
|
|
482
|
+
label: string,
|
|
483
|
+
value: string,
|
|
484
|
+
field: keyof NomineeData,
|
|
485
|
+
) => (
|
|
486
|
+
<TextFieldWithLabel
|
|
487
|
+
label={label}
|
|
488
|
+
value={value}
|
|
489
|
+
onChangeText={(text) => updateField(field, text)}
|
|
490
|
+
variant="date"
|
|
491
|
+
placeholder="DD/MM/YYYY"
|
|
492
|
+
placeholderColor={themeName === 'dark' ? colors.placeholderColor : 'rgba(0,0,0,0.2)'}
|
|
493
|
+
maxLength={10}
|
|
494
|
+
dateFormat="DD/MM/YYYY"
|
|
495
|
+
onDatePress={() => {
|
|
496
|
+
if (datePickerLockRef.current || showDatePicker) return;
|
|
497
|
+
closeAllMenus();
|
|
498
|
+
openDatePicker();
|
|
499
|
+
}}
|
|
500
|
+
editable={false}
|
|
501
|
+
keepWhiteBackground={true}
|
|
502
|
+
inputStyle={{ color: 'white' }}
|
|
503
|
+
/>
|
|
504
|
+
);
|
|
505
|
+
|
|
506
|
+
// Validation function to check if all required fields are filled (pure function - no setState)
|
|
507
|
+
const validateForm = (): boolean => {
|
|
508
|
+
// If makeNomination is "No", no other fields are required
|
|
509
|
+
if (form.makeNomination.toLowerCase() === 'no') {
|
|
510
|
+
return true;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// If makeNomination is "Yes", all nominee fields are required
|
|
514
|
+
const requiredFields = [
|
|
515
|
+
form.nomineeTitle,
|
|
516
|
+
form.nomineeName,
|
|
517
|
+
form.relationship,
|
|
518
|
+
form.dateOfBirth,
|
|
519
|
+
];
|
|
520
|
+
|
|
521
|
+
const baseValid = requiredFields.every(field => field && field.trim().length > 0);
|
|
522
|
+
const rel = String(form.relationship || '').trim().toLowerCase();
|
|
523
|
+
const needsCustom = rel === 'other' || rel === 'others';
|
|
524
|
+
if (needsCustom) {
|
|
525
|
+
const customRel = String(form.customRelationship || '').trim();
|
|
526
|
+
if (!customRel || customRel.length < 2) {
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return baseValid;
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
const handleSave = async () => {
|
|
534
|
+
try {
|
|
535
|
+
// Clear previous errors
|
|
536
|
+
setFieldErrors({});
|
|
537
|
+
|
|
538
|
+
// Validate all required fields
|
|
539
|
+
const rel = String(form.relationship || '').trim().toLowerCase();
|
|
540
|
+
const needsCustom = rel === 'other' || rel === 'others';
|
|
541
|
+
if (needsCustom) {
|
|
542
|
+
const customRel = String(form.customRelationship || '').trim();
|
|
543
|
+
if (!customRel) {
|
|
544
|
+
setFieldErrors(prev => ({ ...prev, customRelationship: 'Relationship is required' }));
|
|
545
|
+
Alert.alert(COMMON_STRINGS.REQUIRED_FIELD, COMMON_STRINGS.FILL_REQUIRED_FIELDS);
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
if (customRel.length < 2) {
|
|
549
|
+
setFieldErrors(prev => ({ ...prev, customRelationship: 'Relationship must be at least 2 characters' }));
|
|
550
|
+
Alert.alert(COMMON_STRINGS.REQUIRED_FIELD, 'Relationship must be at least 2 characters');
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if (!validateForm()) {
|
|
556
|
+
Alert.alert(COMMON_STRINGS.REQUIRED_FIELD, COMMON_STRINGS.FILL_REQUIRED_FIELDS);
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Get user info from app data
|
|
561
|
+
const userInfo = getUserInfoForAPI();
|
|
562
|
+
|
|
563
|
+
// Convert date from DD/MM/YYYY to ISO 8601 format (YYYY-MM-DD)
|
|
564
|
+
const convertToISO8601 = (dateString: string): string => {
|
|
565
|
+
if (!dateString) return '';
|
|
566
|
+
const [day, month, year] = dateString.split('/');
|
|
567
|
+
if (day && month && year) {
|
|
568
|
+
const isoDate = `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`;
|
|
569
|
+
return isoDate;
|
|
570
|
+
}
|
|
571
|
+
return '';
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
const customerNomineeRequest = {
|
|
575
|
+
providerId: defaultProviderId,
|
|
576
|
+
workflowInstanceId, // from store
|
|
577
|
+
userreferenceid: userInfo.userReferenceId,
|
|
578
|
+
applicationid: applicationId,
|
|
579
|
+
// Only the specified parameters
|
|
580
|
+
customerId: customerId,
|
|
581
|
+
intentToNominate: form.makeNomination === 'Yes',
|
|
582
|
+
fullName: form.nomineeName,
|
|
583
|
+
nomineePrefix: form.nomineeTitle,
|
|
584
|
+
relation: form.relationship,
|
|
585
|
+
customRelation: form.customRelationship || '',
|
|
586
|
+
dob: convertToISO8601(form.dateOfBirth),
|
|
587
|
+
nomineeNameOnCertificate: form.printNameOnCertificate,
|
|
588
|
+
};
|
|
589
|
+
const response = await customerNominee(customerNomineeRequest).unwrap();
|
|
590
|
+
onSave?.(form);
|
|
591
|
+
} catch (error) {
|
|
592
|
+
|
|
593
|
+
// Show error message to user
|
|
594
|
+
Alert.alert(
|
|
595
|
+
NOMINEE_STRINGS.NOMINEE_SAVED_FAILED,
|
|
596
|
+
NOMINEE_STRINGS.NOMINEE_SAVE_ERROR,
|
|
597
|
+
[{ text: COMMON_STRINGS.OK }]
|
|
598
|
+
);
|
|
599
|
+
|
|
600
|
+
// Do not call onSave on failure - stay on current screen
|
|
601
|
+
// User can retry or correct their information
|
|
602
|
+
}
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
// Handler for back button (used by both header and hardware back button)
|
|
606
|
+
const handleBackPress = async () => {
|
|
607
|
+
setIsGoingBack(true);
|
|
608
|
+
try {
|
|
609
|
+
const userInfo = getUserInfoForAPI();
|
|
610
|
+
await previousState({
|
|
611
|
+
providerId: defaultProviderId,
|
|
612
|
+
workflowInstanceId,
|
|
613
|
+
userreferenceid: userInfo.userReferenceId,
|
|
614
|
+
applicationid: applicationId,
|
|
615
|
+
entityid: '',
|
|
616
|
+
});
|
|
617
|
+
} catch (e) {
|
|
618
|
+
// Handle error silently
|
|
619
|
+
} finally {
|
|
620
|
+
setIsGoingBack(false);
|
|
621
|
+
navigate('Employee');
|
|
622
|
+
}
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
// Handle Android hardware back button
|
|
626
|
+
useEffect(() => {
|
|
627
|
+
if (Platform.OS !== 'android') return;
|
|
628
|
+
|
|
629
|
+
const onHardwareBackPress = () => {
|
|
630
|
+
handleBackPress();
|
|
631
|
+
return true; // Prevent default behavior
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
const backHandler = BackHandler.addEventListener(
|
|
635
|
+
'hardwareBackPress',
|
|
636
|
+
onHardwareBackPress
|
|
637
|
+
);
|
|
638
|
+
|
|
639
|
+
return () => backHandler.remove();
|
|
640
|
+
}, [defaultProviderId, workflowInstanceId, applicationId]);
|
|
641
|
+
|
|
642
|
+
return (
|
|
643
|
+
<SafeAreaWrapper includeTop={false} bottomPadding={25} statusBarColor="#000000" statusBarStyle="light-content">
|
|
644
|
+
<Header title={NOMINEE_STRINGS.NOMINEE_DETAILS_TITLE} onBackPress={handleBackPress} backgroundColor={colors.primary} />
|
|
645
|
+
<KeyboardAvoidingView
|
|
646
|
+
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
|
647
|
+
style={styles.keyboardAvoidingView}
|
|
648
|
+
keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : 0}
|
|
649
|
+
>
|
|
650
|
+
<ScrollView
|
|
651
|
+
ref={scrollViewRef}
|
|
652
|
+
style={styles.container}
|
|
653
|
+
showsVerticalScrollIndicator={false}
|
|
654
|
+
contentContainerStyle={styles.scrollContent}
|
|
655
|
+
scrollEnabled={!isLoadingCustomerNominee}
|
|
656
|
+
keyboardShouldPersistTaps="handled"
|
|
657
|
+
>
|
|
658
|
+
<TouchableWithoutFeedback onPress={isLoadingCustomerNominee ? undefined : closeAllMenus}>
|
|
659
|
+
<View>
|
|
660
|
+
{renderDropdown(
|
|
661
|
+
NOMINEE_STRINGS.MAKE_NOMINATION_LABEL,
|
|
662
|
+
form.makeNomination,
|
|
663
|
+
'makeNomination',
|
|
664
|
+
makeNominationOptions,
|
|
665
|
+
'makeNomination'
|
|
666
|
+
)}
|
|
667
|
+
|
|
668
|
+
{String(form.makeNomination).toLowerCase() === 'yes' && (
|
|
669
|
+
<>
|
|
670
|
+
{renderNomineeNameFields()}
|
|
671
|
+
{renderDropdown(NOMINEE_STRINGS.RELATIONSHIP_LABEL, form.relationship, 'relationship', nomineeRelationOptions, 'relationship')}
|
|
672
|
+
{renderDateField(NOMINEE_STRINGS.DATE_OF_BIRTH_LABEL, form.dateOfBirth, 'dateOfBirth')}
|
|
673
|
+
|
|
674
|
+
{/* Date Picker Modal */}
|
|
675
|
+
{showDatePicker && (
|
|
676
|
+
<View style={styles.datePickerContainer}>
|
|
677
|
+
<DateTimePicker
|
|
678
|
+
value={selectedDate}
|
|
679
|
+
mode="date"
|
|
680
|
+
display={Platform.OS === 'ios' ? 'spinner' : 'default'}
|
|
681
|
+
onChange={onDateChange}
|
|
682
|
+
maximumDate={new Date(new Date().getFullYear() - 18, new Date().getMonth(), new Date().getDate())}
|
|
683
|
+
themeVariant="light"
|
|
684
|
+
/>
|
|
685
|
+
</View>
|
|
686
|
+
)}
|
|
687
|
+
|
|
688
|
+
<View style={styles.checkboxContainer}>
|
|
689
|
+
<TouchableOpacity
|
|
690
|
+
style={styles.checkbox}
|
|
691
|
+
onPress={() => updateField('printNameOnCertificate', !form.printNameOnCertificate)}
|
|
692
|
+
>
|
|
693
|
+
<View style={[styles.checkboxBox, form.printNameOnCertificate && styles.checkboxChecked]}>
|
|
694
|
+
{form.printNameOnCertificate ? (
|
|
695
|
+
<Image
|
|
696
|
+
source={{ uri: (useTheme().themeName === 'dark') ? base64Images.checkBoxDark : base64Images.filledCheckBox }}
|
|
697
|
+
style={{ width: 22, height: 22 }}
|
|
698
|
+
resizeMode="contain"
|
|
699
|
+
/>
|
|
700
|
+
) : (
|
|
701
|
+
(useTheme().themeName === 'dark') ? (
|
|
702
|
+
<Image
|
|
703
|
+
source={{ uri: base64Images.unCheckBoxDark }}
|
|
704
|
+
style={{ width: 22, height: 22 }}
|
|
705
|
+
resizeMode="contain"
|
|
706
|
+
/>
|
|
707
|
+
) : null
|
|
708
|
+
)}
|
|
709
|
+
</View>
|
|
710
|
+
</TouchableOpacity>
|
|
711
|
+
<Text style={styles.checkboxText}>
|
|
712
|
+
{NOMINEE_STRINGS.PRINT_NAME_INSTRUCTIONS}
|
|
713
|
+
</Text>
|
|
714
|
+
</View>
|
|
715
|
+
</>
|
|
716
|
+
)}
|
|
717
|
+
</View>
|
|
718
|
+
</TouchableWithoutFeedback>
|
|
719
|
+
</ScrollView>
|
|
720
|
+
</KeyboardAvoidingView>
|
|
721
|
+
<View style={styles.footer}>
|
|
722
|
+
<ActionButton
|
|
723
|
+
title={COMMON_STRINGS.SAVE}
|
|
724
|
+
onPress={() => { closeAllMenus(); handleSave(); }}
|
|
725
|
+
disabled={isLoadingCustomerNominee || !validateForm()}
|
|
726
|
+
loading={isLoadingCustomerNominee}
|
|
727
|
+
/>
|
|
728
|
+
</View>
|
|
729
|
+
{/* Overlay to disable screen interactions during API call */}
|
|
730
|
+
{isLoadingCustomerNominee && (
|
|
731
|
+
<View style={styles.loadingOverlay} pointerEvents="auto">
|
|
732
|
+
</View>
|
|
733
|
+
)}
|
|
734
|
+
{/* Loading overlay for back navigation */}
|
|
735
|
+
{isGoingBack && (
|
|
736
|
+
<View style={styles.loadingOverlay} pointerEvents="auto">
|
|
737
|
+
<ActivityIndicator size="large" color={colors.primary} />
|
|
738
|
+
</View>
|
|
739
|
+
)}
|
|
740
|
+
</SafeAreaWrapper>
|
|
741
|
+
);
|
|
742
|
+
};
|
|
743
|
+
|
|
744
|
+
const createStyles = (colors: any, typography: any, themeName: string) => StyleSheet.create({
|
|
745
|
+
keyboardAvoidingView: {
|
|
746
|
+
flex: 1,
|
|
747
|
+
},
|
|
748
|
+
container: { flex: 1, paddingHorizontal: 16, paddingTop: 20 },
|
|
749
|
+
scrollContent: {
|
|
750
|
+
flexGrow: 1,
|
|
751
|
+
paddingBottom: 120,
|
|
752
|
+
},
|
|
753
|
+
footer: {
|
|
754
|
+
position: 'absolute',
|
|
755
|
+
left: 0,
|
|
756
|
+
right: 0,
|
|
757
|
+
bottom: 0,
|
|
758
|
+
backgroundColor: colors.background,
|
|
759
|
+
paddingTop: 8,
|
|
760
|
+
paddingBottom: 16,
|
|
761
|
+
},
|
|
762
|
+
fieldLabel: { ...typography.styles.bodySmall, color: colors.textLight, marginBottom: -16 },
|
|
763
|
+
nameFieldsContainer: { flexDirection: 'row', gap: 12, marginBottom: 0 },
|
|
764
|
+
titleField: { width: 80 },
|
|
765
|
+
nameField: { flex: 1 },
|
|
766
|
+
datePickerContainer: {
|
|
767
|
+
backgroundColor: '#FFFFFF',
|
|
768
|
+
borderRadius: 12,
|
|
769
|
+
padding: 16,
|
|
770
|
+
marginBottom: 20,
|
|
771
|
+
marginTop: 10,
|
|
772
|
+
},
|
|
773
|
+
checkboxContainer: { flexDirection: 'row', alignItems: 'flex-start', marginBottom: 40, paddingHorizontal: 4, marginTop: 16 },
|
|
774
|
+
checkbox: { marginRight: 12, marginTop: 2 },
|
|
775
|
+
checkboxBox: { width: 20, height: 20, borderWidth: 2, borderColor: themeName === 'dark' ? colors.tabSelected : colors.primary, borderRadius: 4, alignItems: 'center', justifyContent: 'center', backgroundColor: 'white' },
|
|
776
|
+
checkboxChecked: {
|
|
777
|
+
// backgroundColor: colors.primary
|
|
778
|
+
},
|
|
779
|
+
checkboxText: { ...typography.styles.bodySmall, color: colors.textLight, flex: 1, lineHeight: 18 },
|
|
780
|
+
inlineMenu: { backgroundColor: themeName === 'dark' ? colors.inputBackground : colors.background, borderWidth: themeName === 'dark' ? 1 : 0.5, borderColor: themeName === 'dark' ? '#ffffff' : 'rgba(0,0,0,0.2)', borderRadius: 8, marginTop: -20, marginBottom: 10, paddingHorizontal: 12, paddingVertical: 6 },
|
|
781
|
+
modalOption: { paddingVertical: 14 },
|
|
782
|
+
modalOptionText: { ...typography.styles.bodyLarge, color: colors.text },
|
|
783
|
+
// Added to fix TS error where these styles are referenced above
|
|
784
|
+
errorContainer: { flexDirection: 'row', alignItems: 'center', marginTop: -20, marginBottom: 20 },
|
|
785
|
+
errorIcon: { marginRight: 6 },
|
|
786
|
+
errorText: { fontSize: 12, color: colors.error || '#FF0000', fontWeight: '500', flex: 1 },
|
|
787
|
+
loadingOverlay: {
|
|
788
|
+
position: 'absolute',
|
|
789
|
+
top: 0,
|
|
790
|
+
left: 0,
|
|
791
|
+
right: 0,
|
|
792
|
+
bottom: 0,
|
|
793
|
+
backgroundColor: 'rgba(0, 0, 0, 0.3)',
|
|
794
|
+
justifyContent: 'center',
|
|
795
|
+
alignItems: 'center',
|
|
796
|
+
zIndex: 1000,
|
|
797
|
+
},
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
export default NomineeDetail;
|