diff --git a/src/assets/icons/PersonWithSeprator.tsx b/src/assets/icons/PersonWithSeprator.tsx new file mode 100644 index 00000000..203ff843 --- /dev/null +++ b/src/assets/icons/PersonWithSeprator.tsx @@ -0,0 +1,34 @@ +import { IconProps } from './types'; + +const PersonWithSeperatorIcon = (props: IconProps) => { + const { width = '16', height = '16', fillColor = '#0276FE' } = props; + return ( + + + + + + + + + ); +}; + +export default PersonWithSeperatorIcon; diff --git a/src/assets/icons/ResizeIcon.tsx b/src/assets/icons/ResizeIcon.tsx new file mode 100644 index 00000000..7eb3c355 --- /dev/null +++ b/src/assets/icons/ResizeIcon.tsx @@ -0,0 +1,14 @@ +const ResizeIcon = () => { + return ( + + + + ); +}; + +export default ResizeIcon; diff --git a/src/components/AutocompleteDropdown/OptionsList.tsx b/src/components/AutocompleteDropdown/OptionsList.tsx index ae071187..b2ec82c1 100644 --- a/src/components/AutocompleteDropdown/OptionsList.tsx +++ b/src/components/AutocompleteDropdown/OptionsList.tsx @@ -11,7 +11,9 @@ const OptionsList: React.FC = ({ isMultiSelect, optionTemplate, onOptionClick, - isOptionSelected + isOptionSelected, + notFoundCopy, + customOptionsContainer }) => { if (showLoader) { return ( @@ -27,7 +29,7 @@ const OptionsList: React.FC = ({ return ( - No results found ! + {notFoundCopy ? notFoundCopy : 'No results found !'} ); @@ -38,7 +40,7 @@ const OptionsList: React.FC = ({ }; return ( - + {options.map(option => (
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 - - - -
+ +
+
+ + Mobile number + + + { + if (watch(PHONE_NUMBER)) { + return validateNumber(value); + } + } + }} + onChange={handleNumberChange} + placeholder="Enter phone number" + type="string" + errors={errors} + containerClassName="w-[100%]" + isRequired={true} + /> + + Source of number + + + {selectedSource?.value === 'OTHER_SOURCES' && ( + <> + + Enter other source + + + + + {enteredOtherSource?.length}/30 + +
+ } + control={control} + name="otherSource" + rules={{ + required: true, + validate: (value: string) => { + return validateOtherSource(value); + } + }} + /> + + )} +
+ +
+
+ ); 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>; + containerClassName?: string; + RightInputAdornment?: React.ReactNode; + control: Control; + name: keyof AddNumberProps; + rules: RegisterOptions; + isRequired?: boolean; + maxLength?: number; + onChange?: (e: React.ChangeEvent) => void; +}; +const BorderedInputTextBox = (props: IBorderedInputTextBoxProps) => { + const { + placeholder, + type, + errors, + containerClassName, + RightInputAdornment, + control, + name, + rules, + maxLength, + isRequired = true + } = props; + + return ( + <> + ( + { + onChange(e.target.value); + }} + maxLength={maxLength} + error={errors && errors[name] && errors[name].message} + RightInputAdornment={RightInputAdornment} + onFocus={e => e} + /> + )} + /> + + ); +}; + +export default BorderedInputTextBox; diff --git a/src/pages/CaseDetails/components/MoreDetails/SingleAutoDropdown.tsx b/src/pages/CaseDetails/components/MoreDetails/SingleAutoDropdown.tsx new file mode 100644 index 00000000..30e7a85d --- /dev/null +++ b/src/pages/CaseDetails/components/MoreDetails/SingleAutoDropdown.tsx @@ -0,0 +1,59 @@ +import { AddNumberProps } from './AddNewNumberOverlay'; +import { Control, Controller, FieldErrorsImpl, RegisterOptions } from 'react-hook-form'; +import SingleAutocompleteDropdown from '@cp/src/components/AutocompleteDropdown/SingleAutocompleteDropdown'; + +type ISingleAutoDropdownProps = { + placeholder: string; + errors: Partial>; + containerClassName?: string; + control: Control; + name: keyof AddNumberProps; + rules: RegisterOptions; + optionsList: { + label: string; + value: string; + }[]; + selectedSource: string; + handleSelectionChange: (selectedOption: any) => void; + onChange?: (e: React.ChangeEvent) => void; +}; +const SingleAutoDropdown = (props: ISingleAutoDropdownProps) => { + const { + placeholder, + errors, + control, + name, + rules, + selectedSource, + handleSelectionChange, + optionsList + } = props; + + return ( + <> + ( + 0 + ? 'No results found! Try adding other source.' + : 'No sources found' + } + customOptionsContainer="!max-h-[200px]" + onSelectionChange={onChange} + selectedOption={value?.label} + placeholder={placeholder} + /> + )} + /> + + ); +}; + +export default SingleAutoDropdown; diff --git a/src/pages/CaseDetails/components/MoreDetails/index.module.scss b/src/pages/CaseDetails/components/MoreDetails/index.module.scss index 3fce83d4..e6c6b1a4 100644 --- a/src/pages/CaseDetails/components/MoreDetails/index.module.scss +++ b/src/pages/CaseDetails/components/MoreDetails/index.module.scss @@ -87,7 +87,8 @@ .addNewNumberCard { box-sizing: border-box; width: 302px; - height: 138px; + display: flex; + flex-direction: column; border-radius: 8px; background-color: var(--bg-primary); transition: height 200ms; @@ -102,18 +103,7 @@ .enterPhoneNumber { color: var(--grayscale-2); } - input { - margin-top: 12px; - width: 100%; - border: 1px solid var(--border); - border-radius: 8px; - height: 36px; - padding: 12px; - } button { - position: absolute; - bottom: 0; - right: 0; width: 100px; } } diff --git a/src/pages/CaseDetails/components/PhoneNumber/PhoneNumberContactCard.tsx b/src/pages/CaseDetails/components/PhoneNumber/PhoneNumberContactCard.tsx index b7a44003..c7d07e15 100644 --- a/src/pages/CaseDetails/components/PhoneNumber/PhoneNumberContactCard.tsx +++ b/src/pages/CaseDetails/components/PhoneNumber/PhoneNumberContactCard.tsx @@ -13,12 +13,16 @@ import { ITelePhoneData, LIMIT_TYPE } from '@cp/pages/CaseDetails/interfaces/Cas import PocNumbersTag from '../PocTag/Index'; import dayjs from 'dayjs'; import InfoIconOutlined from '@cp/assets/images/icons/InfoIconOutlined'; +import PersonWithSeperatorIcon from '@cp/src/assets/icons/PersonWithSeprator'; import { useSelector } from 'react-redux'; import { RootState } from '@cp/src/store'; import { Roles } from '@cp/src/pages/auth/constants/AuthConstants'; +import { Popper, PopperContent, PopperTrigger } from '@cp/src/components/Popper/Popper'; +import CopyToClipboardIcon from '@cp/src/assets/icons/CopyToClipboardIcon'; +import Button from '@navi/web-ui/lib/primitives/Button/Button'; +import { copyToClipboard } from '@cp/src/utils/commonUtils'; import VerticalSeperator from '@cp/src/components/VerticalSeperator'; import DeleteIcon from '@cp/src/assets/icons/DeleteIcon'; -import Button from '@navi/web-ui/lib/primitives/Button/Button'; import RemoveNumberModal from './RemoveNumberModal'; const NO_OF_RECORDS = 5; @@ -27,6 +31,7 @@ const PhoneNumberContactCard = ({ callData }: { callData: ITelePhoneData }) => { const openTrueCallerTab = (number: string) => { window.open(`tel:${number}`, '_blank'); }; + const userRole = useSelector((state: RootState) => state?.common?.userData?.roles); const { dailyLimit, hourlyLimit, callRemainingPercentage } = useMemo(() => { const dailyLimit = callData?.limit?.find(limitRecord => limitRecord.type === LIMIT_TYPE.DAILY); const hourlyLimit = callData?.limit?.find( @@ -41,7 +46,8 @@ const PhoneNumberContactCard = ({ callData }: { callData: ITelePhoneData }) => { }; }, [callData]); - const userRole = useSelector((state: RootState) => state.common.userData?.roles); + const isAddedFromLonghorn = callData?.showCreatedBy || false; + const [isModalOpen, setIsModalOpen] = React.useState(false); const isPrimary = callData?.sources.includes('Primary'); @@ -167,6 +173,55 @@ const PhoneNumberContactCard = ({ callData }: { callData: ITelePhoneData }) => {
+ {userRole?.includes(Roles.ROLE_GLOBAL_ACCESS) && isAddedFromLonghorn ? ( + + + + + +
+
+ + Name + + + {callData?.createdByName || 'NA'} + +
+
+ + Email + +
+ + {callData?.createdByEmail || 'NA'} + + +
+
+
+
+
+ ) : ( + isAddedFromLonghorn && + )} {callData?.sources.slice(0, 2).map((source, index) => ( <> diff --git a/src/pages/CaseDetails/components/PhoneNumber/index.module.scss b/src/pages/CaseDetails/components/PhoneNumber/index.module.scss index 42091466..50327787 100644 --- a/src/pages/CaseDetails/components/PhoneNumber/index.module.scss +++ b/src/pages/CaseDetails/components/PhoneNumber/index.module.scss @@ -68,6 +68,8 @@ } .sourcesWrapper { + display: flex; + gap: 4px; background: transparent; padding: 9px 16px; font-weight: 500; diff --git a/src/pages/CaseDetails/constants/MoreDetails.constant.tsx b/src/pages/CaseDetails/constants/MoreDetails.constant.tsx index 1f1146d3..5759aa3c 100644 --- a/src/pages/CaseDetails/constants/MoreDetails.constant.tsx +++ b/src/pages/CaseDetails/constants/MoreDetails.constant.tsx @@ -157,3 +157,22 @@ export const tooltipArrowColor = 'var(--tooltip-background-color)'; export const gmailComposeLink = 'https://mail.google.com/mail/?view=cm&fs=1&to='; export const gmeetCallLink = 'https://meet.google.com/calling/'; + +export const PHONE_NUMBER = 'phoneNumber'; + +export const SOURCE = 'source'; + +export const OTHER_SOURCE = 'otherSource'; + +export enum PhoneNumberValidations { + VALID_PHONE_NUMBER = 'Enter a valid 10 digit mobile number', + PHONE_NUMBER_EXISTS = 'Phone number already exists', + NUMBER_MARKED_INVALID = 'Number marked invalid previously', + ERROR = 'Error in validating number' +} + +export enum OtherSourceValidations { + MIN_SOURCE_LENGTH = 'Enter at least 3 characters', + SAME_CHARACTERS = 'All characters cannot be same', + ONLY_SPECIAL_CHARACTERS = 'Only special characters are not allowed' +} diff --git a/src/pages/CaseDetails/constants/index.tsx b/src/pages/CaseDetails/constants/index.tsx index c385dca7..851c06e4 100644 --- a/src/pages/CaseDetails/constants/index.tsx +++ b/src/pages/CaseDetails/constants/index.tsx @@ -200,7 +200,8 @@ export enum STORE_KEYS { SHERLOCK = 'sherlock', EMAILS = 'emails', GEOLOCATIONS_ADDRESSES = 'geolocationsAndAddresses', - SIMILAR_GEOLOCATIONS = 'similarGeolocations' + SIMILAR_GEOLOCATIONS = 'similarGeolocations', + SECONDARY_SOURCES = 'secondarySources' } export const ExcludedRedirectionEndPointsList = [COSMOS_PATH]; diff --git a/src/pages/CaseDetails/interfaces/CaseDetail.type.ts b/src/pages/CaseDetails/interfaces/CaseDetail.type.ts index dc0d0346..8070194b 100644 --- a/src/pages/CaseDetails/interfaces/CaseDetail.type.ts +++ b/src/pages/CaseDetails/interfaces/CaseDetail.type.ts @@ -372,6 +372,9 @@ export interface ITelePhoneData { createdAt: string; isPoc: boolean; limit: IPhoneNumberLimit[]; + showCreatedBy: boolean; + createdByName?: string; + createdByEmail?: string; } interface IAddresses extends IApiData { @@ -529,8 +532,13 @@ export interface ReduxCaseDetail { detailsApiLoading: boolean; pageData: PageData; caseDetailTabsFeatureFlag: Record; + secondarySources: ISecondarySources[]; } +export interface ISecondarySources { + label: string; + value: string; +} export interface ISherlock { type: string; date: string; diff --git a/src/pages/CaseDetails/interfaces/MoreDetails.type.ts b/src/pages/CaseDetails/interfaces/MoreDetails.type.ts index 9bcf30b1..a38c3740 100644 --- a/src/pages/CaseDetails/interfaces/MoreDetails.type.ts +++ b/src/pages/CaseDetails/interfaces/MoreDetails.type.ts @@ -23,12 +23,16 @@ export interface IAddNewNumber { open: boolean; loading: boolean; newNumber: string; + source: string; + otherSource?: string; } export interface IAddNewNumberAction { type: AddNewNumberKind; payload?: { newNumber?: string; + source: string; + otherSource?: string; }; } export interface IAddNewEmail { diff --git a/src/pages/CaseDetails/reducers/caseDetailSlice.ts b/src/pages/CaseDetails/reducers/caseDetailSlice.ts index 2b8f5ef3..d7eec322 100644 --- a/src/pages/CaseDetails/reducers/caseDetailSlice.ts +++ b/src/pages/CaseDetails/reducers/caseDetailSlice.ts @@ -15,7 +15,8 @@ import { feeWaiveTransformedEmiData } from '../components/EmiSchedule/utils'; const initialState: ReduxCaseDetail = { detailsApiLoading: false, pageData: {}, - caseDetailTabsFeatureFlag: {} + caseDetailTabsFeatureFlag: {}, + secondarySources: [] }; const caseDetailsSlice = createSlice({ @@ -67,6 +68,9 @@ const caseDetailsSlice = createSlice({ } }; }, + setSecondarySources: (state, action) => { + state.secondarySources = action.payload; + }, setCustomerInfo: (state, action) => { const { lan, customerRefrenceId, data } = action.payload; const pageKey = createKey(lan, customerRefrenceId); @@ -471,6 +475,7 @@ export const { setTelephones, setMonthlyEnach, setContacts, + setSecondarySources, setCustomerInfo, setRepaymentHistory, setTelephonesV2, diff --git a/src/utils/ApiHelper.ts b/src/utils/ApiHelper.ts index 59ca7153..2d992918 100644 --- a/src/utils/ApiHelper.ts +++ b/src/utils/ApiHelper.ts @@ -251,6 +251,8 @@ export enum ApiKeys { HUMAN_REMINDER_DISCONNECT_V2, HUMAN_REMINDER_GET_CASE_DETAILS_V2, HUMAN_REMNINDER_DISPOSE_CALL_V2, + PHONENUMBER_SECONDARY_SOURCES, + VALIDATE_PHONE_NUMBER, REMOVE_CONTACT } @@ -505,6 +507,8 @@ API_URLS[ApiKeys.HUMAN_REMINDER_GET_CASE_DETAILS_V2] = '/hrc/case-details'; API_URLS[ApiKeys.HUMAN_REMNINDER_DISPOSE_CALL_V2] = '/hrc/dispose'; API_URLS[ApiKeys.HUMAN_REMINDER_AVAILABLITY_V2] = '/hrc/agent-activity/{agentAvailabilityStatus}/{callId}'; +API_URLS[ApiKeys.PHONENUMBER_SECONDARY_SOURCES] = '/list-longhorn-secondary-sources'; +API_URLS[ApiKeys.VALIDATE_PHONE_NUMBER] = '/validate-existing-telephone/{number}'; API_URLS[ApiKeys.REMOVE_CONTACT] = '/global-access/telephone/remove'; // TODO: try to get rid of `as` diff --git a/src/utils/commonUtils.ts b/src/utils/commonUtils.ts index ab0526bb..d3a4a46a 100644 --- a/src/utils/commonUtils.ts +++ b/src/utils/commonUtils.ts @@ -445,7 +445,7 @@ export const isValidEmail = (email: string) => { export const isValidPhoneNumber = (phoneNumber: string) => { // Regular expression for validating a phone number - const phoneNumberRegex = /^[1-9][0-9]{9}$/; + const phoneNumberRegex = /^[6-9][0-9]{9}$/; return phoneNumberRegex.test(phoneNumber); }; diff --git a/web-ui-library b/web-ui-library index ceb04ad9..a5d0e015 160000 --- a/web-ui-library +++ b/web-ui-library @@ -1 +1 @@ -Subproject commit ceb04ad98f0e741cb0a6edb6559123c24d0cd7f4 +Subproject commit a5d0e0155adfc8cb1b2821d7cf23722a2b309793