onOptionClick(option)}>
{isMultiSelect ? (
diff --git a/src/components/AutocompleteDropdown/SingleAutocompleteDropdown.tsx b/src/components/AutocompleteDropdown/SingleAutocompleteDropdown.tsx
index 0a48015a..e3b59178 100644
--- a/src/components/AutocompleteDropdown/SingleAutocompleteDropdown.tsx
+++ b/src/components/AutocompleteDropdown/SingleAutocompleteDropdown.tsx
@@ -31,6 +31,8 @@ const SingleAutocompleteDropdown = forwardRef
void;
onSelectionChange: (selectedOptions: IOption | null) => void;
customOptionTemplate?: (option: IOption) => React.ReactNode;
@@ -49,6 +51,8 @@ export interface IOptionsList {
onOptionClick: (option: IOption) => void;
isOptionSelected: (option: IOption) => boolean;
optionTemplate?: (option: IOption) => React.ReactNode;
+ notFoundCopy?: string;
+ customOptionsContainer?: string;
}
export interface ISelectedOptions {
diff --git a/src/components/Popper/Popper.tsx b/src/components/Popper/Popper.tsx
index 69adbf5d..d43c2659 100644
--- a/src/components/Popper/Popper.tsx
+++ b/src/components/Popper/Popper.tsx
@@ -34,6 +34,7 @@ interface PopperOptions {
crossAxisOffset?: number;
disableCloseOnOutsideClick?: boolean;
transform?: boolean;
+ hideStrategy?: 'escaped' | 'referenceHidden' | undefined;
}
interface PopperContentProps {
@@ -67,7 +68,8 @@ export function usePopper({
mainAxisOffset = 9,
crossAxisOffset = 9,
disableCloseOnOutsideClick = false,
- transform = true
+ transform = true,
+ hideStrategy = 'escaped'
}: PopperOptions = {}) {
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(initialOpen);
@@ -94,7 +96,7 @@ export function usePopper({
arrow({ element: arrowRef?.current }),
hide({
padding: hiddenPadding,
- strategy: 'escaped'
+ strategy: hideStrategy
})
]
});
diff --git a/src/pages/CaseDetails/actions/moreDetailsActions.ts b/src/pages/CaseDetails/actions/moreDetailsActions.ts
index 150b2a7f..a0a73a78 100644
--- a/src/pages/CaseDetails/actions/moreDetailsActions.ts
+++ b/src/pages/CaseDetails/actions/moreDetailsActions.ts
@@ -4,8 +4,10 @@ import { STORE_KEYS, ToastMessage } from '../constants';
import {
setCaseDetail,
setCaseDetailError,
- setCaseDetailLoading
+ setCaseDetailLoading,
+ setSecondarySources
} from '../reducers/caseDetailSlice';
+import { Dispatch } from '@reduxjs/toolkit';
interface dispatchProps {
payload: any;
@@ -51,9 +53,11 @@ export const postAlternateNumber =
(
loanaccountnumber: string,
customerreferenceid: string,
- alternateNumber: string,
successCallback: () => void,
- failureCallback: () => void
+ cb: (isModalOpen: boolean) => void,
+ alternateNumber: string,
+ source: string,
+ otherSource?: string
) =>
() => {
axiosInstance
@@ -63,6 +67,8 @@ export const postAlternateNumber =
{
number: alternateNumber,
source: 'LONGHORN',
+ secondarySource: source,
+ remarkForSecondarySource: otherSource,
type: 'Alternate Contact'
}
],
@@ -70,16 +76,36 @@ export const postAlternateNumber =
headers: { customerreferenceid, loanaccountnumber }
}
)
- .then(() => {
- successCallback();
+ .then(response => {
toast(ToastMessage.NUMBER_ADDED_SUCCESSFULLY, { type: 'success' });
+ cb(false);
+ successCallback();
})
- .catch(() => {
- failureCallback();
+ .catch(err => {
toast(ToastMessage.NUMBER_ADDITION_FAILED, { type: 'error' });
});
};
+export const getSecondarySources = () => (dispatch: Dispatch) => {
+ const url = getApiUrl(ApiKeys.PHONENUMBER_SECONDARY_SOURCES);
+
+ axiosInstance.get(url).then(response => {
+ dispatch(setSecondarySources(response.data));
+ });
+};
+
+export const getValidateNumber = async (number: string, customerRefId: string) => {
+ const url = getApiUrl(ApiKeys.VALIDATE_PHONE_NUMBER, { number });
+ try {
+ const response = await axiosInstance.get(url, {
+ headers: { customerReferenceId: customerRefId }
+ });
+ return response.data;
+ } catch (error) {
+ return 'Error';
+ }
+};
+
export const postAlternateEmail = (
loanaccountnumber: string,
customerreferenceid: string,
diff --git a/src/pages/CaseDetails/components/MoreDetails/AddNewNumberOverlay.tsx b/src/pages/CaseDetails/components/MoreDetails/AddNewNumberOverlay.tsx
index 1a851de3..1f1929b1 100644
--- a/src/pages/CaseDetails/components/MoreDetails/AddNewNumberOverlay.tsx
+++ b/src/pages/CaseDetails/components/MoreDetails/AddNewNumberOverlay.tsx
@@ -4,60 +4,38 @@ import AddIcon from '@icons/AddIcon';
import styles from './index.module.scss';
import { useParams } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
-import { useCallback, useReducer } from 'react';
-import { IAddNewNumber, IAddNewNumberAction } from '../../interfaces/MoreDetails.type';
-import { AddNewNumberKind } from '../../constants/MoreDetails.constant';
+import { useCallback } from 'react';
import Typography from '@navi/web-ui/lib/primitives/Typography/Typography';
-import { postAlternateNumber } from '../../actions/moreDetailsActions';
-import { getTelephonesV2 } from '../../actions/casesDetailsActions';
-import { createKey, isNumericLength } from 'src/utils/CaseDetail.utils';
+import {
+ getSecondarySources,
+ getValidateNumber,
+ postAlternateNumber
+} from '../../actions/moreDetailsActions';
+import { createKey } from 'src/utils/CaseDetail.utils';
import { addClickstreamEvent } from '../../../../service/clickStreamEventService';
import { CLICKSTREAM_EVENT_NAMES } from '../../../../service/clickStream.constant';
import Modal from '@cp/src/components/Modal/Modal';
import { isValidPhoneNumber } from '@cp/src/utils/commonUtils';
import { RootState } from '@cp/src/store';
import { Roles } from '@cp/pages/auth/constants/AuthConstants';
+import { useForm } from 'react-hook-form';
+import cx from 'classnames';
+import BorderedInputTextBox from './BorderedInputTextBox';
+import { getTelephonesV2 } from '../../actions/casesDetailsActions';
+import SingleAutoDropdown from './SingleAutoDropdown';
+import {
+ OTHER_SOURCE,
+ OtherSourceValidations,
+ PHONE_NUMBER,
+ PhoneNumberValidations,
+ SOURCE
+} from '../../constants/MoreDetails.constant';
-const addNewNumberReducer = (state: IAddNewNumber, action: IAddNewNumberAction) => {
- const { type, payload } = action;
- switch (type) {
- case AddNewNumberKind.INITIAL_STATE:
- return {
- ...state,
- open: true
- };
- case AddNewNumberKind.CLOSE:
- return {
- ...state,
- open: false
- };
- case AddNewNumberKind.BEFORE_API_CALL:
- return {
- ...state,
- loading: true
- };
- case AddNewNumberKind.API_CALL_SUCCESS:
- return {
- ...state,
- loading: false,
- open: false,
- newNumber: ''
- };
- case AddNewNumberKind.API_ERROR:
- return {
- ...state,
- loading: false
- };
- case AddNewNumberKind.SET_NUMBER: {
- return {
- ...state,
- newNumber: payload?.newNumber || ''
- };
- }
- default:
- return state;
- }
-};
+export interface AddNumberProps {
+ phoneNumber: string;
+ source: string;
+ otherSource: string;
+}
const AddNewNumberOverlay: React.FC = () => {
const { loanId = '', customerId = '' } = useParams();
@@ -68,64 +46,98 @@ const AddNewNumberOverlay: React.FC = () => {
}));
const isGlobalAccessRoleGiven = user?.roles?.includes(Roles.ROLE_GLOBAL_ACCESS);
const disableCTAs = isGlobalAccessRoleGiven ? !editAccessFlag : false;
+ const { telephones } = useSelector((state: RootState) => ({
+ telephones: state.caseDetail.pageData?.[createKey(loanId, customerId)]?.telephonesv2?.data
+ }));
+ const secondarySources = useSelector((state: RootState) => state.caseDetail.secondarySources);
+ const optionsList =
+ secondarySources?.map(source => ({
+ label: source.label,
+ value: source.value
+ })) || [];
+
+ const [isModalOpen, setIsModalOpen] = React.useState(false);
+
+ const {
+ handleSubmit,
+ watch,
+ setValue,
+ formState: { errors, isValid, isDirty },
+ register,
+ control,
+ getValues,
+ reset
+ } = useForm({ mode: 'onChange' });
const dispatch = useDispatch();
- const [addNewNumber, dispatchAddNewNumber] = useReducer(addNewNumberReducer, {
- open: false,
- loading: false,
- newNumber: ''
- });
const openModal = () => {
- dispatchAddNewNumber({
- type: AddNewNumberKind.INITIAL_STATE
- });
+ setIsModalOpen(true);
+ dispatch(getSecondarySources());
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.LH_Phone_AddNewNumberCTA);
};
const closeModal = () => {
- dispatchAddNewNumber({
- type: AddNewNumberKind.CLOSE
- });
- addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.LH_Phone_AddNewNumberCross);
+ setIsModalOpen(false);
};
- const handleAddNumber = () => {
- dispatchAddNewNumber({ type: AddNewNumberKind.BEFORE_API_CALL });
- dispatch(
- postAlternateNumber(
- loanId,
- customerId,
- addNewNumber.newNumber,
- successCallback,
- failureCallback
- )
- );
+ const handleFormSubmit = data => {};
+ const handleAddNumber = async () => {
+ if (!(!isDirty || !isValid)) {
+ dispatch(
+ postAlternateNumber(
+ loanId,
+ customerId,
+ successCallback,
+ setIsModalOpen,
+ getValues(PHONE_NUMBER),
+ getValues(SOURCE).value,
+ getValues(OTHER_SOURCE)
+ )
+ );
+ }
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.LH_Phone_AddNewNumberSubmit, {
- newPhoneNumber: addNewNumber.newNumber
+ newPhoneNumber: getValues(PHONE_NUMBER)
});
};
const successCallback = useCallback(() => {
- dispatchAddNewNumber({ type: AddNewNumberKind.API_CALL_SUCCESS });
dispatch(getTelephonesV2(loanId, customerId));
- }, [addNewNumber.newNumber]);
-
- const failureCallback = useCallback(() => {
- dispatchAddNewNumber({ type: AddNewNumberKind.API_ERROR });
- }, [addNewNumber.newNumber]);
+ reset();
+ }, []);
const handleNumberChange = useCallback((event: React.ChangeEvent) => {
event.preventDefault();
- dispatchAddNewNumber({
- type: AddNewNumberKind.SET_NUMBER,
- payload: { newNumber: event.target.value }
- });
}, []);
- const handleKeyPress = event => {
- if (event.key === 'Enter' && isValidPhoneNumber(addNewNumber.newNumber)) {
- handleAddNumber();
+
+ const selectedSource = watch(SOURCE, '');
+ const enteredOtherSource = watch(OTHER_SOURCE, '');
+
+ const validateNumber = async (value: string) => {
+ if (value.length == 10) {
+ if (!isValidPhoneNumber(value)) return PhoneNumberValidations.VALID_PHONE_NUMBER;
+ const errorMessage = await getValidateNumber(value, customerId);
+ if (errorMessage === 'Error') {
+ return PhoneNumberValidations.ERROR;
+ }
+ if (errorMessage?.isPresent) {
+ if (errorMessage?.isValid) {
+ return PhoneNumberValidations.PHONE_NUMBER_EXISTS;
+ } else return PhoneNumberValidations.NUMBER_MARKED_INVALID;
+ }
+ return true;
}
+ return PhoneNumberValidations.VALID_PHONE_NUMBER;
+ };
+ const validateOtherSource = (value: string) => {
+ if (value?.length < 3) return OtherSourceValidations.MIN_SOURCE_LENGTH;
+ const areAllCharactersSame = value.split('').every(char => char === value[0]);
+ if (areAllCharactersSame) return OtherSourceValidations.SAME_CHARACTERS;
+ return /^(?=.*[a-zA-Z0-9]).+$/i.test(value) || OtherSourceValidations.ONLY_SPECIAL_CHARACTERS;
+ };
+
+ const handleSelectionChange = selectedOption => {
+ setValue(SOURCE, selectedOption.value);
};
return (
@@ -141,28 +153,87 @@ const AddNewNumberOverlay: React.FC = () => {
>
Add new number
-
-
-
- Mobile number
-
-
-
-
+
+
+
);
diff --git a/src/pages/CaseDetails/components/MoreDetails/BorderedInputTextBox.tsx b/src/pages/CaseDetails/components/MoreDetails/BorderedInputTextBox.tsx
new file mode 100644
index 00000000..cab8123e
--- /dev/null
+++ b/src/pages/CaseDetails/components/MoreDetails/BorderedInputTextBox.tsx
@@ -0,0 +1,67 @@
+import { BorderedInput } from '@navi/web-ui/lib/primitives';
+import { AddNumberProps } from './AddNewNumberOverlay';
+import {
+ Control,
+ Controller,
+ FieldErrorsImpl,
+ RegisterOptions,
+ UseFormGetValues
+} from 'react-hook-form';
+
+type IBorderedInputTextBoxProps = {
+ placeholder: string;
+ type: string;
+ errors: Partial