TP-30455 | Foreclosure payments (#448)
* TP-30455 | Foreclosure payment * TP-30455 | Foreclosure payments * TP-30455 | fix * TP-30455 | submodule update * TP-30455 | yarn lock update * TP-30455 | fixes * TP-30455 | submodule update
This commit is contained in:
committed by
GitHub Enterprise
parent
1eefbe36db
commit
42987636fa
Submodule RN-UI-LIB updated: ca813aaac4...a72cd5971e
@@ -1,6 +1,5 @@
|
||||
import { ToastMessages } from '../screens/allCases/constants';
|
||||
import { Dispatch } from '@reduxjs/toolkit';
|
||||
import { appendLoanIdToValue, setLoading, setPaymentLink } from '../reducer/paymentSlice';
|
||||
import axiosInstance, {
|
||||
ApiKeys,
|
||||
API_STATUS_CODE,
|
||||
@@ -11,17 +10,19 @@ import { toast } from '../../RN-UI-LIB/src/components/toast';
|
||||
import { CLICKSTREAM_EVENT_NAMES } from '../common/Constants';
|
||||
import { addClickstreamEvent } from '../services/clickstreamEventService';
|
||||
import { logError } from '../components/utlis/errorUtils';
|
||||
import { DEFAULT_FORECLOSURE_BREAKUP } from '../screens/registerPayements/Foreclosure';
|
||||
|
||||
export enum PaymentType {
|
||||
EMI = 'EMI',
|
||||
CUSTOM = 'CUSTOM',
|
||||
FORECLOSURE = 'FORECLOSURE',
|
||||
}
|
||||
export interface GeneratePaymentPayload {
|
||||
alternateContactNumber: string;
|
||||
customAmount: {
|
||||
amount: number;
|
||||
currency: string;
|
||||
};
|
||||
customAmountProvided: boolean;
|
||||
type: PaymentType;
|
||||
phoneNumber: string;
|
||||
customAmount?: number;
|
||||
loanAccountNumber: string;
|
||||
notifyToAlternateContact: boolean;
|
||||
customerReferenceId: string;
|
||||
customerId: string;
|
||||
}
|
||||
export interface IGeneratePaymentLinkApiResponse {
|
||||
paymentLink: string;
|
||||
@@ -31,75 +32,76 @@ export interface IGeneratePaymentLinkApiResponse {
|
||||
|
||||
export interface ILoanIdValue
|
||||
extends IGeneratePaymentLinkApiResponse,
|
||||
Pick<GeneratePaymentPayload, 'alternateContactNumber' | 'customAmount'> {}
|
||||
Pick<GeneratePaymentPayload, 'phoneNumber' | 'customAmount'> {}
|
||||
|
||||
export const generatePaymentLinkAction =
|
||||
(payload: GeneratePaymentPayload) => (dispatch: Dispatch) => {
|
||||
const url = getApiUrl(ApiKeys.GENERATE_PAYMENT_LINK);
|
||||
const { loanAccountNumber } = payload;
|
||||
export const generatePaymentLinkAction = (
|
||||
payload: GeneratePaymentPayload,
|
||||
successCb: (paymentLink: string) => void,
|
||||
failureCb: () => void
|
||||
) => {
|
||||
const url = getApiUrl(ApiKeys.GENERATE_PAYMENT_LINK);
|
||||
axiosInstance
|
||||
.post(url, payload)
|
||||
.then((response) => {
|
||||
if (response?.status === API_STATUS_CODE.OK) {
|
||||
const responseData = response.data;
|
||||
const { paymentLink, retriesLeft } = responseData || {};
|
||||
if (paymentLink) {
|
||||
successCb(paymentLink);
|
||||
toast({
|
||||
type: 'success',
|
||||
text1: `${ToastMessages.PAYMENT_LINK_SUCCESS} ${retriesLeft} tr${
|
||||
retriesLeft > 1 ? 'ies' : 'y'
|
||||
} remaining.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
logError(err);
|
||||
failureCb();
|
||||
if (isAxiosError(err)) {
|
||||
const { type, loanAccountNumber, customAmount, phoneNumber } = payload;
|
||||
const clickstreamPayload = {
|
||||
amount: customAmount,
|
||||
lan: loanAccountNumber,
|
||||
phoneNumber,
|
||||
type,
|
||||
};
|
||||
addClickstreamEvent(
|
||||
CLICKSTREAM_EVENT_NAMES.FA_SEND_PAYMENT_LINK_FAILED,
|
||||
clickstreamPayload
|
||||
);
|
||||
if (err?.response?.status === API_STATUS_CODE.TOO_MANY_REQUESTS) {
|
||||
addClickstreamEvent(
|
||||
CLICKSTREAM_EVENT_NAMES.FA_SEND_PAYMENT_LINK_FAILED_LIMIT_REACHED,
|
||||
clickstreamPayload
|
||||
);
|
||||
toast({
|
||||
type: 'error',
|
||||
text1: ToastMessages.PAYMENT_LINK_RETRY,
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
type: 'error',
|
||||
text1: ToastMessages.PAYMENT_LINK_ERROR,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
dispatch(setLoading(true));
|
||||
try {
|
||||
axiosInstance
|
||||
.post(url, payload)
|
||||
.then((response) => {
|
||||
if (response?.status === API_STATUS_CODE.OK) {
|
||||
const responseData = response.data;
|
||||
const { paymentLink, retriesLeft } = responseData || {};
|
||||
if (paymentLink) {
|
||||
dispatch(setPaymentLink(paymentLink));
|
||||
const storePayload = {
|
||||
...responseData,
|
||||
customAmount: payload.customAmount,
|
||||
alternateContactNumber: payload.alternateContactNumber,
|
||||
};
|
||||
dispatch(
|
||||
appendLoanIdToValue({
|
||||
[loanAccountNumber]: storePayload,
|
||||
})
|
||||
);
|
||||
toast({
|
||||
type: 'success',
|
||||
text1: `${ToastMessages.PAYMENT_LINK_SUCCESS} ${retriesLeft} tr${
|
||||
retriesLeft > 1 ? 'ies' : 'y'
|
||||
} remaining.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
logError(err);
|
||||
if (isAxiosError(err)) {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_SEND_PAYMENT_LINK_FAILED, {
|
||||
amount: payload.customAmount,
|
||||
lan: payload.loanAccountNumber,
|
||||
phoneNumber: payload.alternateContactNumber,
|
||||
});
|
||||
if (err?.response?.status === 429) {
|
||||
addClickstreamEvent(
|
||||
CLICKSTREAM_EVENT_NAMES.FA_SEND_PAYMENT_LINK_FAILED_LIMIT_REACHED,
|
||||
{
|
||||
amount: payload.customAmount,
|
||||
lan: payload.loanAccountNumber,
|
||||
phoneNumber: payload.alternateContactNumber,
|
||||
}
|
||||
);
|
||||
toast({
|
||||
type: 'error',
|
||||
text1: ToastMessages.PAYMENT_LINK_RETRY,
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
type: 'error',
|
||||
text1: ToastMessages.PAYMENT_LINK_ERROR,
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
dispatch(setLoading(false));
|
||||
});
|
||||
} catch (err) {
|
||||
dispatch(setLoading(false));
|
||||
}
|
||||
};
|
||||
export const getForeclosureAmount = async (loanAccountNumber: string, preclosureDate: string) => {
|
||||
try {
|
||||
const url = getApiUrl(
|
||||
ApiKeys.GET_FORECLOSURE_AMOUNT,
|
||||
{ loanAccountNumber },
|
||||
{ preclosureDate }
|
||||
);
|
||||
const response = await axiosInstance.get(url);
|
||||
return response?.data;
|
||||
} catch (err) {
|
||||
logError(err as Error, 'Error fetching foreclosure amount');
|
||||
return DEFAULT_FORECLOSURE_BREAKUP;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,51 +1,90 @@
|
||||
import React, { useState } from 'react';
|
||||
import { View, StyleSheet, Pressable } from 'react-native';
|
||||
import Text from '../../../RN-UI-LIB/src/components/Text';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import {
|
||||
View,
|
||||
StyleSheet,
|
||||
Pressable,
|
||||
StyleProp,
|
||||
ViewStyle,
|
||||
Animated,
|
||||
Easing,
|
||||
LayoutChangeEvent,
|
||||
} from 'react-native';
|
||||
import { COLORS } from '../../../RN-UI-LIB/src/styles/colors';
|
||||
import ArrowSolidDownIcon from '../../../RN-UI-LIB/src/Icons/ArrowSolidDownIcon';
|
||||
import DropdownIcon from '../../../RN-UI-LIB/src/Icons/DropdownIcon';
|
||||
import { GenericStyles } from '../../../RN-UI-LIB/src/styles';
|
||||
|
||||
interface IAccordion {
|
||||
title: string;
|
||||
content: React.ReactNode;
|
||||
title: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
containerStyle?: StyleProp<ViewStyle>;
|
||||
defaultExpanded?: boolean;
|
||||
}
|
||||
|
||||
const Accordion: React.FC<IAccordion> = ({ title, content }) => {
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
const Accordion: React.FC<IAccordion> = ({ title, children, containerStyle, defaultExpanded }) => {
|
||||
const [isExpanded, setIsExpanded] = useState(defaultExpanded);
|
||||
const [bodySectionHeight, setBodySectionHeight] = useState(0);
|
||||
const animatedController = useRef(new Animated.Value(isExpanded ? 1 : 0)).current;
|
||||
|
||||
const bodyHeight = animatedController.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, bodySectionHeight],
|
||||
});
|
||||
|
||||
const arrowAngle = animatedController.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: ['0rad', `${Math.PI}rad`],
|
||||
});
|
||||
|
||||
const handlePress = () => {
|
||||
const sharedAnimationConfig = {
|
||||
duration: 300,
|
||||
useNativeDriver: false,
|
||||
};
|
||||
if (isExpanded) {
|
||||
Animated.timing(animatedController, {
|
||||
...sharedAnimationConfig,
|
||||
toValue: 0,
|
||||
easing: Easing.bezier(0.4, 0.0, 0.2, 1),
|
||||
}).start();
|
||||
} else {
|
||||
Animated.timing(animatedController, {
|
||||
...sharedAnimationConfig,
|
||||
toValue: 1,
|
||||
easing: Easing.bezier(0.4, 0.0, 0.2, 1),
|
||||
}).start();
|
||||
}
|
||||
setIsExpanded(!isExpanded);
|
||||
};
|
||||
|
||||
const handleLayout = (event: LayoutChangeEvent) => {
|
||||
setBodySectionHeight(event.nativeEvent.layout.height);
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={[styles.container, containerStyle]}>
|
||||
<Pressable onPress={handlePress}>
|
||||
<View style={[styles.titleContainer]}>
|
||||
<Text dark bold>
|
||||
{title}
|
||||
</Text>
|
||||
<View
|
||||
style={[
|
||||
{
|
||||
transform: [{ rotate: isExpanded ? '180deg' : '0deg' }],
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ArrowSolidDownIcon size={10} />
|
||||
</View>
|
||||
{title}
|
||||
<Animated.View style={{ transform: [{ rotateZ: arrowAngle }] }}>
|
||||
<DropdownIcon />
|
||||
</Animated.View>
|
||||
</View>
|
||||
</Pressable>
|
||||
{isExpanded ? <View style={styles.content}>{content}</View> : null}
|
||||
<Animated.View style={[GenericStyles.overflowHidden, { height: bodyHeight }]}>
|
||||
<View style={styles.content} onLayout={handleLayout}>
|
||||
{children}
|
||||
</View>
|
||||
</Animated.View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
marginVertical: 10,
|
||||
marginHorizontal: 16,
|
||||
borderRadius: 4,
|
||||
borderRadius: 8,
|
||||
borderWidth: 1,
|
||||
borderColor: COLORS.BORDER.GREY,
|
||||
position: 'relative',
|
||||
},
|
||||
titleContainer: {
|
||||
flexDirection: 'row',
|
||||
@@ -56,7 +95,10 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
content: {
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 18,
|
||||
paddingBottom: 16,
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
width: '100%',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,53 +1,58 @@
|
||||
import { View, StyleSheet, TouchableOpacity } from 'react-native';
|
||||
import { View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
|
||||
import React, { useState } from 'react';
|
||||
import { GenericStyles, getShadowStyle } from '../../../RN-UI-LIB/src/styles';
|
||||
import { GenericStyles } from '../../../RN-UI-LIB/src/styles';
|
||||
import Text from '../../../RN-UI-LIB/src/components/Text';
|
||||
import { COLORS } from '../../../RN-UI-LIB/src/styles/colors';
|
||||
import InfoIcon from '../../../RN-UI-LIB/src/Icons/InfoIcon';
|
||||
import CloseIconSmall from '../../../RN-UI-LIB/src/Icons/CloseIconSmall';
|
||||
import Button from '../../../RN-UI-LIB/src/components/Button';
|
||||
|
||||
interface IFloatingInfoText {
|
||||
top?: number;
|
||||
bottom?: number;
|
||||
visible?: boolean;
|
||||
message: string;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
const FloatingInfoText: React.FC<IFloatingInfoText> = ({ message, top, bottom }) => {
|
||||
const [showText, setShowText] = useState(true);
|
||||
const toggleText = () => setShowText(false);
|
||||
const FloatingInfoText: React.FC<IFloatingInfoText> = ({
|
||||
visible = true,
|
||||
message,
|
||||
onClose,
|
||||
style,
|
||||
}) => {
|
||||
const [showText, setShowText] = useState(visible);
|
||||
const toggleText = () => {
|
||||
setShowText(false);
|
||||
onClose && typeof onClose === 'function' && onClose();
|
||||
};
|
||||
if (!showText) return null;
|
||||
return (
|
||||
<View style={[styles.container, GenericStyles.centerAlignedRow, { top, bottom }]}>
|
||||
<View style={[styles.textContainer, getShadowStyle(2), GenericStyles.centerAlignedRow]}>
|
||||
<InfoIcon color={COLORS.BASE.BLUE} />
|
||||
<Text small style={styles.text}>
|
||||
{message}
|
||||
</Text>
|
||||
<TouchableOpacity onPress={toggleText} style={styles.closeIcon}>
|
||||
<CloseIconSmall />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
styles.textContainer,
|
||||
GenericStyles.row,
|
||||
GenericStyles.spaceBetween,
|
||||
GenericStyles.alignCenter,
|
||||
style,
|
||||
]}
|
||||
>
|
||||
<Text small light>
|
||||
{message}
|
||||
</Text>
|
||||
<Button
|
||||
variant="primaryText"
|
||||
onPress={toggleText}
|
||||
title="Understood"
|
||||
textStyle={GenericStyles.fontSize12}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
},
|
||||
textContainer: {
|
||||
paddingHorizontal: 30,
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 3,
|
||||
borderRadius: 4,
|
||||
backgroundColor: COLORS.BACKGROUND.BLUE,
|
||||
},
|
||||
text: {
|
||||
color: COLORS.BASE.BLUE,
|
||||
marginLeft: 10,
|
||||
},
|
||||
closeIcon: {
|
||||
marginLeft: 10,
|
||||
backgroundColor: COLORS.BACKGROUND.SILVER,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ const FiltersContainer: React.FC<FilterContainerProps> = (props) => {
|
||||
<>
|
||||
<View
|
||||
style={[
|
||||
styles.filterGroupHeader,
|
||||
styles.silverBackground,
|
||||
GenericStyles.row,
|
||||
GenericStyles.alignCenter,
|
||||
GenericStyles.spaceBetween,
|
||||
|
||||
@@ -65,7 +65,7 @@ const styles = StyleSheet.create({
|
||||
borderRadius: 20,
|
||||
alignItems: 'center',
|
||||
},
|
||||
filterGroupHeader: {
|
||||
silverBackground: {
|
||||
backgroundColor: COLORS.BACKGROUND.SILVER,
|
||||
},
|
||||
filterOption: {
|
||||
|
||||
@@ -46,6 +46,7 @@ export enum ApiKeys {
|
||||
CASES_SYNC_STATUS = 'CASES_SYNC_STATUS',
|
||||
CASES_SEND_ID = 'CASES_SEND_ID',
|
||||
FETCH_CASES = 'FETCH_CASES',
|
||||
GET_FORECLOSURE_AMOUNT = 'GET_FORECLOSURE_AMOUNT',
|
||||
}
|
||||
|
||||
export const API_URLS: Record<ApiKeys, string> = {} as Record<ApiKeys, string>;
|
||||
@@ -58,7 +59,7 @@ API_URLS[ApiKeys.LOGOUT] = '/auth/logout';
|
||||
API_URLS[ApiKeys.FEEDBACK] = '/cases/feedback';
|
||||
API_URLS[ApiKeys.FILTERS] = '/cases/filters';
|
||||
API_URLS[ApiKeys.JANUS] = '/events/json';
|
||||
API_URLS[ApiKeys.GENERATE_PAYMENT_LINK] = '/send-payment-link';
|
||||
API_URLS[ApiKeys.GENERATE_PAYMENT_LINK] = '/payments/send-payment-link';
|
||||
API_URLS[ApiKeys.ADDRESSES_GEOLOCATION] = '/addresses-geolocations';
|
||||
API_URLS[ApiKeys.NEW_ADDRESS] = '/addresses';
|
||||
API_URLS[ApiKeys.GET_SIGNED_URL] = '/cases/get-signed-urls';
|
||||
@@ -78,6 +79,7 @@ API_URLS[ApiKeys.TELEPHONES] = '/telephones';
|
||||
API_URLS[ApiKeys.CASES_SYNC_STATUS] = '/cases/agents/sync-status';
|
||||
API_URLS[ApiKeys.CASES_SEND_ID] = '/cases/sync';
|
||||
API_URLS[ApiKeys.FETCH_CASES] = '/cases/agents/{agentReferenceId}';
|
||||
API_URLS[ApiKeys.GET_FORECLOSURE_AMOUNT] = '/{loanAccountNumber}/pre-closure-amount';
|
||||
|
||||
export const API_STATUS_CODE = {
|
||||
OK: 200,
|
||||
@@ -87,6 +89,7 @@ export const API_STATUS_CODE = {
|
||||
FORBIDDEN: 403,
|
||||
NOT_FOUND: 404,
|
||||
INTERNAL_SERVER_ERROR: 500,
|
||||
TOO_MANY_REQUESTS: 429,
|
||||
};
|
||||
|
||||
const API_TIMEOUT_INTERVAL = 2e4; // 20s
|
||||
|
||||
@@ -6,12 +6,12 @@ import {
|
||||
IDocument,
|
||||
IPhoneSources,
|
||||
PhoneNumber,
|
||||
PhoneNumberSource,
|
||||
TDocumentObj,
|
||||
} from '../../screens/caseDetails/interface';
|
||||
import { getPrefixBase64Image, LocalStorageKeys, MimeType } from '../../common/Constants';
|
||||
import NetInfo from '@react-native-community/netinfo';
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import address from '../form/components/Address';
|
||||
import { useWindowDimensions } from 'react-native';
|
||||
import { GlobalDocumentMap } from '../../../App';
|
||||
import { GenericType } from '../../common/GenericTypes';
|
||||
@@ -169,6 +169,21 @@ export const getPhoneSourceString = (sources: IPhoneSources[]) => {
|
||||
return '';
|
||||
};
|
||||
|
||||
export const getPrimaryPhoneNumber = (phoneNumbers: PhoneNumber[] | null) => {
|
||||
if (!phoneNumbers?.length) {
|
||||
return '';
|
||||
}
|
||||
const index = phoneNumbers.findIndex((number) => {
|
||||
const { sources } = number;
|
||||
const isPrimaryNumber = sources.some((source) => source.type === PhoneNumberSource.PRIMARY);
|
||||
return isPrimaryNumber;
|
||||
});
|
||||
if (index !== -1) {
|
||||
return phoneNumbers[2]?.number;
|
||||
}
|
||||
return phoneNumbers[0]?.number;
|
||||
};
|
||||
|
||||
export const getDynamicBottomSheetHeightPercentageFn = (headerOffset = 100, rowHeight = 50) => {
|
||||
const SCREEN_HEIGHT = useWindowDimensions().height;
|
||||
|
||||
@@ -320,11 +335,10 @@ export const getMaxByPropFromList = (arr: GenericType, prop: string) => {
|
||||
return arr.find((x: GenericType) => x[prop] == max);
|
||||
};
|
||||
|
||||
|
||||
export const getGoogleMapUrl = (latitude: string | number, longitude: string | number) => {
|
||||
if (!latitude || !longitude) return;
|
||||
return `https://www.google.com/maps/search/${latitude},+${longitude}`;
|
||||
}
|
||||
};
|
||||
|
||||
export const isValidAmountEntered = (value: number) => {
|
||||
return typeof value === 'number' && !isNaN(value);
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { LocalStorageKeys } from '../../common/Constants';
|
||||
import { ILoanIdToValue } from '../../reducer/paymentSlice';
|
||||
import { setAsyncStorageItem } from './commonFunctions';
|
||||
|
||||
export const getLoanIdToValueFromLocal = async () => {
|
||||
const loanIdToValueStored = await AsyncStorage.getItem(LocalStorageKeys.LOAN_ID_TO_VALUE);
|
||||
if (loanIdToValueStored) {
|
||||
let parsedLoanIdToValue = JSON.parse(loanIdToValueStored) as ILoanIdToValue;
|
||||
const currentDate = Date.now();
|
||||
for (const [key, value] of Object.entries(parsedLoanIdToValue)) {
|
||||
if (Number(value.expiresAt) < currentDate) {
|
||||
delete parsedLoanIdToValue?.[key];
|
||||
}
|
||||
}
|
||||
await setAsyncStorageItem(LocalStorageKeys.LOAN_ID_TO_VALUE, parsedLoanIdToValue);
|
||||
return parsedLoanIdToValue;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -4,52 +4,23 @@ import { ILoanIdValue } from '../action/paymentActions';
|
||||
export type ILoanIdToValue = Record<string, ILoanIdValue>;
|
||||
|
||||
interface IPaymentState {
|
||||
paymentLink: string;
|
||||
isLoading: boolean;
|
||||
loanIdToValue: ILoanIdToValue;
|
||||
showForeclosureInfoText: boolean;
|
||||
}
|
||||
|
||||
const initialState: IPaymentState = {
|
||||
paymentLink: '',
|
||||
isLoading: false,
|
||||
loanIdToValue: {},
|
||||
showForeclosureInfoText: true,
|
||||
};
|
||||
|
||||
const paymentSlice = createSlice({
|
||||
name: 'payment',
|
||||
initialState,
|
||||
reducers: {
|
||||
setPaymentLink: (state, action: PayloadAction<string>) => {
|
||||
return {
|
||||
...state,
|
||||
paymentLink: action.payload,
|
||||
};
|
||||
},
|
||||
setLoading: (state, action: PayloadAction<boolean>) => {
|
||||
return {
|
||||
...state,
|
||||
isLoading: action.payload,
|
||||
};
|
||||
},
|
||||
setLoanIdToValue: (state, action: PayloadAction<ILoanIdToValue>) => {
|
||||
return {
|
||||
...state,
|
||||
loanIdToValue: action.payload,
|
||||
};
|
||||
},
|
||||
appendLoanIdToValue: (state, action: PayloadAction<ILoanIdToValue>) => {
|
||||
return {
|
||||
...state,
|
||||
loanIdToValue: {
|
||||
...state.loanIdToValue,
|
||||
...action.payload,
|
||||
},
|
||||
};
|
||||
setShowForeclosureInfoText: (state, action) => {
|
||||
state.showForeclosureInfoText = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { setPaymentLink, setLoading, setLoanIdToValue, appendLoanIdToValue } =
|
||||
paymentSlice.actions;
|
||||
export const { setShowForeclosureInfoText } = paymentSlice.actions;
|
||||
|
||||
export default paymentSlice.reducer;
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
import {
|
||||
Pressable,
|
||||
RefreshControl,
|
||||
SafeAreaView,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { RefreshControl, SafeAreaView, ScrollView, StyleSheet, View } from 'react-native';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import Layout from '../layout/Layout';
|
||||
import { GenericStyles } from '../../../RN-UI-LIB/src/styles';
|
||||
@@ -25,11 +18,18 @@ import { getCaseUnifiedData, UnifiedCaseDetailsTypes } from '../../action/caseAp
|
||||
import RepaymentsTab from './repayments/RepaymentsTab';
|
||||
import { toast } from '../../../RN-UI-LIB/src/components/toast';
|
||||
import { ToastMessages } from '../allCases/constants';
|
||||
import CustomTabs from '../../../RN-UI-LIB/src/components/customTabs/CustomTabs';
|
||||
|
||||
enum EmiTab {
|
||||
Schedule,
|
||||
Repayments,
|
||||
}
|
||||
const TABS = [
|
||||
{
|
||||
key: 'schedule',
|
||||
label: 'Schedule',
|
||||
},
|
||||
{
|
||||
key: 'repayments',
|
||||
label: 'Repayments',
|
||||
},
|
||||
];
|
||||
|
||||
const PAGE_TITLE = 'EMI schedule';
|
||||
interface IEmiSchedule {
|
||||
@@ -47,7 +47,7 @@ const EmiSchedule: React.FC<IEmiSchedule> = (props) => {
|
||||
params: { loanAccountNumber, customerReferenceId, caseId },
|
||||
},
|
||||
} = props;
|
||||
const [currentTab, setCurrentTab] = useState<EmiTab>(EmiTab.Schedule);
|
||||
const [currentTab, setCurrentTab] = useState<string>(TABS[0].key);
|
||||
const dispatch = useAppDispatch();
|
||||
const isOnline = useIsOnline();
|
||||
const { refreshing, onRefresh } = useRefresh(() => {
|
||||
@@ -70,7 +70,7 @@ const EmiSchedule: React.FC<IEmiSchedule> = (props) => {
|
||||
const backHandler = () => {
|
||||
popToScreen(1);
|
||||
};
|
||||
const onTabChange = (tab: EmiTab) => {
|
||||
const onTabChange = (tab: string) => {
|
||||
setCurrentTab(tab);
|
||||
};
|
||||
|
||||
@@ -127,53 +127,15 @@ const EmiSchedule: React.FC<IEmiSchedule> = (props) => {
|
||||
<Text light>Start date</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={[styles.tabContainer]}>
|
||||
<View style={[GenericStyles.row, GenericStyles.alignCenter]}>
|
||||
<Pressable
|
||||
onPress={() => onTabChange(EmiTab.Schedule)}
|
||||
style={{
|
||||
borderBottomWidth: 2,
|
||||
borderBottomColor:
|
||||
currentTab === EmiTab.Schedule ? COLORS.BASE.BLUE : 'transparent',
|
||||
padding: 8,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
{
|
||||
color: currentTab === EmiTab.Schedule ? COLORS.BASE.BLUE : COLORS.TEXT.GREY,
|
||||
},
|
||||
]}
|
||||
>
|
||||
Schedule
|
||||
</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
onPress={() => onTabChange(EmiTab.Repayments)}
|
||||
style={{
|
||||
borderBottomWidth: 2,
|
||||
borderBottomColor:
|
||||
currentTab === EmiTab.Repayments ? COLORS.BASE.BLUE : 'transparent',
|
||||
padding: 8,
|
||||
marginLeft: 24,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
{
|
||||
color: currentTab === EmiTab.Repayments ? COLORS.BASE.BLUE : COLORS.TEXT.GREY,
|
||||
},
|
||||
]}
|
||||
>
|
||||
Repayments
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
<CustomTabs
|
||||
tabs={TABS}
|
||||
currentTab={currentTab}
|
||||
onTabChange={onTabChange}
|
||||
containerStyle={[GenericStyles.ml4, styles.pt31]}
|
||||
/>
|
||||
</ScrollView>
|
||||
<View style={styles.tabItemContainer}>
|
||||
{currentTab === EmiTab.Schedule ? (
|
||||
{currentTab === TABS[0].key ? (
|
||||
<EmiScheduleTab loanAccountNumber={loanAccountNumber} />
|
||||
) : (
|
||||
<RepaymentsTab loanAccountNumber={loanAccountNumber} />
|
||||
@@ -214,6 +176,9 @@ const styles = StyleSheet.create({
|
||||
paddingBottom: 100,
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
pt31: {
|
||||
paddingTop: 31,
|
||||
},
|
||||
});
|
||||
|
||||
export default EmiSchedule;
|
||||
|
||||
295
src/screens/registerPayements/Foreclosure.tsx
Normal file
295
src/screens/registerPayements/Foreclosure.tsx
Normal file
@@ -0,0 +1,295 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { ScrollView, StyleSheet, TouchableOpacity, View } from 'react-native';
|
||||
import Button from '../../../RN-UI-LIB/src/components/Button';
|
||||
import Text from '../../../RN-UI-LIB/src/components/Text';
|
||||
import Dropdown from '../../../RN-UI-LIB/src/components/dropdown/Dropdown';
|
||||
import { GenericStyles } from '../../../RN-UI-LIB/src/styles';
|
||||
import { COLORS } from '../../../RN-UI-LIB/src/styles/colors';
|
||||
import {
|
||||
PaymentType,
|
||||
generatePaymentLinkAction,
|
||||
getForeclosureAmount,
|
||||
} from '../../action/paymentActions';
|
||||
import { CLICKSTREAM_EVENT_NAMES } from '../../common/Constants';
|
||||
import {
|
||||
copyToClipboard,
|
||||
getDynamicBottomSheetHeightPercentageFn,
|
||||
getPhoneNumberString,
|
||||
} from '../../components/utlis/commonFunctions';
|
||||
import { useAppDispatch, useAppSelector } from '../../hooks';
|
||||
import useIsOnline from '../../hooks/useIsOnline';
|
||||
import { setShowForeclosureInfoText } from '../../reducer/paymentSlice';
|
||||
import { addClickstreamEvent } from '../../services/clickstreamEventService';
|
||||
import { PhoneNumber } from '../caseDetails/interface';
|
||||
import DropdownItem from './DropdownItem';
|
||||
import ModalWrapper from '../../../RN-UI-LIB/src/components/modalWrapper/ModalWrapper';
|
||||
import QrCodeModal from './QrCodeModal';
|
||||
import { toast } from '../../../RN-UI-LIB/src/components/toast';
|
||||
import { ToastMessages } from '../allCases/constants';
|
||||
import { BUSINESS_DATE_FORMAT_SHORT_YEAR, dateFormat } from '../../../RN-UI-LIB/src/utlis/dates';
|
||||
import Chevron from '../../../RN-UI-LIB/src/Icons/Chevron';
|
||||
import FloatingInfoText from '../../components/floatingInfoText';
|
||||
import ForeclosureBottomSheet from './ForeclosureBottomSheet';
|
||||
import ForeclosureBreakupAccordion, { IForeclosureBreakup } from './ForeclosureBreakupAccordion';
|
||||
|
||||
interface IForeclosure {
|
||||
caseId: string;
|
||||
numbers: PhoneNumber[];
|
||||
primaryPhoneNumber: string;
|
||||
}
|
||||
|
||||
interface IForeclosureForm {
|
||||
selectedPhoneNumber: string;
|
||||
}
|
||||
|
||||
const HEADER_HEIGHT = 100;
|
||||
const ROW_HEIGHT = 40;
|
||||
|
||||
export const DEFAULT_FORECLOSURE_BREAKUP = {
|
||||
totalAmount: 0,
|
||||
principal: 0,
|
||||
interest: 0,
|
||||
otherFees: 0,
|
||||
};
|
||||
|
||||
const Foreclosure: React.FC<IForeclosure> = ({ caseId, numbers, primaryPhoneNumber }) => {
|
||||
const { caseDetail, showForeclosureInfoText } = useAppSelector((state) => ({
|
||||
caseDetail: state.allCases.caseDetails[caseId],
|
||||
showForeclosureInfoText: state.payment.showForeclosureInfoText,
|
||||
}));
|
||||
const dispatch = useAppDispatch();
|
||||
const isOnline = useIsOnline();
|
||||
const [generateClicked, setGenerateClicked] = useState(false);
|
||||
const [showQrCodeModal, setShowQrCodeModal] = React.useState<boolean>(false);
|
||||
const [showForeclosureBottomSheet, setShowForeclosureBottomSheet] = useState(false);
|
||||
const [paymentLink, setPaymentLink] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [foreclosureBreakup, setForeclosureBreakup] = useState<IForeclosureBreakup>(
|
||||
DEFAULT_FORECLOSURE_BREAKUP
|
||||
);
|
||||
const { loanAccountNumber } = caseDetail;
|
||||
|
||||
useEffect(() => {
|
||||
if (paymentLink && generateClicked) {
|
||||
setShowQrCodeModal(true);
|
||||
}
|
||||
}, [paymentLink, generateClicked]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const foreclosureAmount: IForeclosureBreakup = await getForeclosureAmount(
|
||||
loanAccountNumber!!,
|
||||
dateFormat(new Date(), 'YYYY-MM-DD')
|
||||
);
|
||||
setForeclosureBreakup(foreclosureAmount);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
const {
|
||||
control,
|
||||
getValues,
|
||||
formState: { isValid },
|
||||
setValue,
|
||||
trigger,
|
||||
} = useForm<IForeclosureForm>({
|
||||
defaultValues: {
|
||||
selectedPhoneNumber: primaryPhoneNumber,
|
||||
},
|
||||
mode: 'onChange',
|
||||
});
|
||||
|
||||
const getBottomSheetHeight = getDynamicBottomSheetHeightPercentageFn(HEADER_HEIGHT, ROW_HEIGHT);
|
||||
const ChildComponents = useMemo(
|
||||
() =>
|
||||
numbers?.map((phoneNumber) => {
|
||||
const { number, createdAt, sourceText } = phoneNumber;
|
||||
return (
|
||||
<DropdownItem
|
||||
createdAt={createdAt}
|
||||
id={number}
|
||||
label={getPhoneNumberString(phoneNumber)}
|
||||
sourceText={sourceText}
|
||||
/>
|
||||
);
|
||||
}),
|
||||
[numbers]
|
||||
);
|
||||
|
||||
const onSuccess = (link: string) => {
|
||||
setIsLoading(false);
|
||||
setPaymentLink(link);
|
||||
};
|
||||
|
||||
const onError = () => {
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
const generatePaymentLink = () => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_SEND_PAYMENT_LINK, {
|
||||
lan: caseDetail.loanAccountNumber!!,
|
||||
phoneNumber: getValues('selectedPhoneNumber'),
|
||||
type: PaymentType.FORECLOSURE,
|
||||
});
|
||||
setGenerateClicked(false);
|
||||
if (paymentLink) {
|
||||
setShowQrCodeModal(true);
|
||||
} else {
|
||||
setIsLoading(true);
|
||||
generatePaymentLinkAction(
|
||||
{
|
||||
type: PaymentType.FORECLOSURE,
|
||||
phoneNumber: getValues('selectedPhoneNumber'),
|
||||
loanAccountNumber: caseDetail.loanAccountNumber!!,
|
||||
customerId: caseDetail.customerReferenceId,
|
||||
},
|
||||
onSuccess,
|
||||
onError
|
||||
);
|
||||
setGenerateClicked(true);
|
||||
}
|
||||
};
|
||||
|
||||
const isCopyButtonDisabled = !isValid || !isOnline || !paymentLink || paymentLink === '';
|
||||
const isPaymentButtonDisabled = !isValid || !isOnline;
|
||||
|
||||
const copyButtonClick = () => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_COPY_LINK_CLICKED, {
|
||||
lan: caseDetail.loanAccountNumber!!,
|
||||
phoneNumber: getValues('selectedPhoneNumber'),
|
||||
paymentLink,
|
||||
});
|
||||
if (paymentLink) {
|
||||
copyToClipboard(paymentLink);
|
||||
toast({
|
||||
type: 'info',
|
||||
text1: ToastMessages.SUCCESS_COPYING_PAYMENT_LINK,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const toggleForeclosureBottomSheet = () => {
|
||||
setShowForeclosureBottomSheet(!showForeclosureBottomSheet);
|
||||
};
|
||||
|
||||
const handleCloseInfoText = () => {
|
||||
dispatch(setShowForeclosureInfoText(false));
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={GenericStyles.fill}>
|
||||
<ScrollView style={[GenericStyles.p16, GenericStyles.whiteBackground]}>
|
||||
<Text style={[GenericStyles.mb8]}>Select number to share foreclosure link</Text>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field: { value } }) => (
|
||||
<Dropdown
|
||||
placeholder="Select phone number"
|
||||
onValueChange={(number) => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_COLLECT_MONEY_NUMBER_CHANGED, {
|
||||
phoneNumber: number,
|
||||
lan: caseDetail.loanAccountNumber!!,
|
||||
});
|
||||
setValue('selectedPhoneNumber', number);
|
||||
setPaymentLink('');
|
||||
trigger();
|
||||
}}
|
||||
bottomSheetHeight={getBottomSheetHeight(numbers?.length)}
|
||||
header="Select phone number"
|
||||
value={value}
|
||||
>
|
||||
{ChildComponents}
|
||||
</Dropdown>
|
||||
)}
|
||||
name="selectedPhoneNumber"
|
||||
rules={{ required: true }}
|
||||
/>
|
||||
|
||||
<ForeclosureBreakupAccordion
|
||||
title={`Foreclosure amount as of ${dateFormat(
|
||||
new Date(),
|
||||
BUSINESS_DATE_FORMAT_SHORT_YEAR
|
||||
)}`}
|
||||
foreclosureBreakup={foreclosureBreakup}
|
||||
/>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
GenericStyles.row,
|
||||
GenericStyles.alignCenter,
|
||||
GenericStyles.pv10,
|
||||
GenericStyles.mt4,
|
||||
]}
|
||||
activeOpacity={0.7}
|
||||
onPress={toggleForeclosureBottomSheet}
|
||||
>
|
||||
<Text style={styles.futureText} bold>
|
||||
Check amount for future date
|
||||
</Text>
|
||||
<View style={styles.mt3}>
|
||||
<Chevron fillColor={COLORS.TEXT.BLUE} />
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</ScrollView>
|
||||
<FloatingInfoText
|
||||
visible={showForeclosureInfoText}
|
||||
message={'The amount is updated daily on the same link!'}
|
||||
style={GenericStyles.brt8}
|
||||
onClose={handleCloseInfoText}
|
||||
/>
|
||||
<View
|
||||
style={[
|
||||
GenericStyles.whiteBackground,
|
||||
GenericStyles.row,
|
||||
GenericStyles.alignCenter,
|
||||
GenericStyles.spaceBetween,
|
||||
GenericStyles.elevation10,
|
||||
GenericStyles.p16,
|
||||
]}
|
||||
>
|
||||
<Button
|
||||
title="Generate and share link"
|
||||
style={[GenericStyles.w100]}
|
||||
disabled={isPaymentButtonDisabled}
|
||||
showLoader={isLoading}
|
||||
onPress={generatePaymentLink}
|
||||
/>
|
||||
</View>
|
||||
<ModalWrapper
|
||||
animationType="slide"
|
||||
animated
|
||||
onRequestClose={() => setShowQrCodeModal((prev) => !prev)}
|
||||
visible={showQrCodeModal}
|
||||
>
|
||||
<QrCodeModal
|
||||
closeQrCodeModal={() => setShowQrCodeModal(false)}
|
||||
isCopyButtonDisabled={isCopyButtonDisabled}
|
||||
copyButtonClick={copyButtonClick}
|
||||
caseId={caseId}
|
||||
paymentLink={paymentLink}
|
||||
/>
|
||||
</ModalWrapper>
|
||||
<ForeclosureBottomSheet
|
||||
showForeclosureBottomSheet={showForeclosureBottomSheet}
|
||||
loanAccountNumber={loanAccountNumber!!}
|
||||
toggleForeclosureBottomSheet={toggleForeclosureBottomSheet}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default Foreclosure;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
amount: {
|
||||
fontSize: 13,
|
||||
color: COLORS.TEXT.GREEN,
|
||||
marginRight: 14,
|
||||
},
|
||||
futureText: {
|
||||
color: COLORS.TEXT.BLUE,
|
||||
marginRight: 8,
|
||||
},
|
||||
mt3: {
|
||||
marginTop: 3,
|
||||
},
|
||||
});
|
||||
74
src/screens/registerPayements/ForeclosureBottomSheet.tsx
Normal file
74
src/screens/registerPayements/ForeclosureBottomSheet.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import { TouchableOpacity, View } from 'react-native';
|
||||
import React, { useState } from 'react';
|
||||
import Text from '../../../RN-UI-LIB/src/components/Text';
|
||||
import BottomSheet from '../../../RN-UI-LIB/src/components/bottom_sheet/BottomSheet';
|
||||
import Heading from '../../../RN-UI-LIB/src/components/Heading';
|
||||
import { row } from '../emiSchedule/constants';
|
||||
import { GenericStyles } from '../../../RN-UI-LIB/src/styles';
|
||||
import CloseIcon from '../../../RN-UI-LIB/src/Icons/CloseIcon';
|
||||
import { COLORS } from '../../../RN-UI-LIB/src/styles/colors';
|
||||
import DateTimePicker, {
|
||||
IDateTimePickerMode,
|
||||
} from '../../../RN-UI-LIB/src/components/dateTimePicker/DateTimePicker';
|
||||
import ForeclosureBreakupAccordion, { IForeclosureBreakup } from './ForeclosureBreakupAccordion';
|
||||
import { getForeclosureAmount } from '../../action/paymentActions';
|
||||
import { DEFAULT_FORECLOSURE_BREAKUP } from './Foreclosure';
|
||||
import { BUSINESS_DATE_FORMAT } from '../../../RN-UI-LIB/src/utlis/dates';
|
||||
|
||||
interface IForeclosureBottomSheet {
|
||||
showForeclosureBottomSheet: boolean;
|
||||
loanAccountNumber: string;
|
||||
toggleForeclosureBottomSheet: () => void;
|
||||
}
|
||||
|
||||
const ForeclosureBottomSheet: React.FC<IForeclosureBottomSheet> = ({
|
||||
showForeclosureBottomSheet,
|
||||
loanAccountNumber,
|
||||
toggleForeclosureBottomSheet,
|
||||
}) => {
|
||||
const [foreclosureBreakup, setForeclosureBreakup] = useState<IForeclosureBreakup>(
|
||||
DEFAULT_FORECLOSURE_BREAKUP
|
||||
);
|
||||
|
||||
const handleDateChange = async (date: string) => {
|
||||
setForeclosureBreakup(DEFAULT_FORECLOSURE_BREAKUP);
|
||||
const foreclosureAmount: IForeclosureBreakup = await getForeclosureAmount(
|
||||
loanAccountNumber!!,
|
||||
date
|
||||
);
|
||||
setForeclosureBreakup(foreclosureAmount);
|
||||
};
|
||||
return (
|
||||
<BottomSheet
|
||||
HeaderNode={() => (
|
||||
<View style={[...row, GenericStyles.p16]}>
|
||||
<Heading dark type="h3">
|
||||
Foreclosure amount by selected date
|
||||
</Heading>
|
||||
<TouchableOpacity activeOpacity={0.7} onPress={toggleForeclosureBottomSheet}>
|
||||
<CloseIcon color={COLORS.TEXT.LIGHT} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
heightPercentage={60}
|
||||
visible={showForeclosureBottomSheet}
|
||||
setVisible={toggleForeclosureBottomSheet}
|
||||
>
|
||||
<View style={GenericStyles.ph16}>
|
||||
<Text style={[GenericStyles.fontSize13]}>Select payment date</Text>
|
||||
<DateTimePicker
|
||||
inputFieldClickable={true}
|
||||
disabled={true}
|
||||
mode={IDateTimePickerMode.DATE}
|
||||
handleConfirm={handleDateChange}
|
||||
minimumDate={new Date()}
|
||||
resultDateFormat="YYYY-MM-DD"
|
||||
displayDateTimeFormat={BUSINESS_DATE_FORMAT}
|
||||
/>
|
||||
<ForeclosureBreakupAccordion foreclosureBreakup={foreclosureBreakup} defaultExpanded />
|
||||
</View>
|
||||
</BottomSheet>
|
||||
);
|
||||
};
|
||||
|
||||
export default ForeclosureBottomSheet;
|
||||
@@ -0,0 +1,99 @@
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import React, { useMemo } from 'react';
|
||||
import { GenericStyles } from '../../../RN-UI-LIB/src/styles';
|
||||
import Accordion from '../../components/accordion';
|
||||
import Text from '../../../RN-UI-LIB/src/components/Text';
|
||||
import { COLORS } from '../../../RN-UI-LIB/src/styles/colors';
|
||||
import { formatAmount } from '../../../RN-UI-LIB/src/utlis/amount';
|
||||
import LineLoader from '../../../RN-UI-LIB/src/components/suspense_loader/LineLoader';
|
||||
import SuspenseLoader from '../../../RN-UI-LIB/src/components/suspense_loader/SuspenseLoader';
|
||||
|
||||
interface IForeclosureBreakupAccordion {
|
||||
title?: string;
|
||||
defaultExpanded?: boolean;
|
||||
foreclosureBreakup: IForeclosureBreakup;
|
||||
}
|
||||
|
||||
export interface IForeclosureBreakup {
|
||||
totalAmount: number;
|
||||
principal: number;
|
||||
interest: number;
|
||||
otherFees: number;
|
||||
}
|
||||
|
||||
const ForeclosureBreakupAccordion: React.FC<IForeclosureBreakupAccordion> = ({
|
||||
title,
|
||||
foreclosureBreakup,
|
||||
defaultExpanded,
|
||||
}) => {
|
||||
const { totalAmount, principal, interest, otherFees } = foreclosureBreakup;
|
||||
const foreclosureBreakupMap = useMemo(
|
||||
() => [
|
||||
{
|
||||
label: 'Principal',
|
||||
value: principal,
|
||||
},
|
||||
{
|
||||
label: 'Interest',
|
||||
value: interest,
|
||||
},
|
||||
{
|
||||
label: 'Fees',
|
||||
value: otherFees,
|
||||
},
|
||||
],
|
||||
[principal, interest, otherFees]
|
||||
);
|
||||
return (
|
||||
<View style={GenericStyles.mt16}>
|
||||
{title ? <Text style={[GenericStyles.mb8]}>{title}</Text> : null}
|
||||
<Accordion
|
||||
title={
|
||||
<View style={[GenericStyles.fill, GenericStyles.row, GenericStyles.spaceBetween]}>
|
||||
<Text light>Total foreclosure amount</Text>
|
||||
<Text style={styles.amount} bold>
|
||||
<SuspenseLoader
|
||||
loading={totalAmount === 0}
|
||||
fallBack={<LineLoader width={80} height={10} />}
|
||||
>
|
||||
{formatAmount(totalAmount)}
|
||||
</SuspenseLoader>
|
||||
</Text>
|
||||
</View>
|
||||
}
|
||||
containerStyle={GenericStyles.silverBackground}
|
||||
defaultExpanded={defaultExpanded}
|
||||
>
|
||||
{foreclosureBreakupMap.map(({ label, value }) => (
|
||||
<View
|
||||
style={[
|
||||
GenericStyles.row,
|
||||
GenericStyles.spaceBetween,
|
||||
GenericStyles.alignCenter,
|
||||
GenericStyles.borderTop,
|
||||
GenericStyles.pv10,
|
||||
]}
|
||||
>
|
||||
<Text light>{label}</Text>
|
||||
<SuspenseLoader
|
||||
loading={totalAmount === 0}
|
||||
fallBack={<LineLoader width={60} height={10} />}
|
||||
>
|
||||
<Text dark>{formatAmount(value)}</Text>
|
||||
</SuspenseLoader>
|
||||
</View>
|
||||
))}
|
||||
</Accordion>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default ForeclosureBreakupAccordion;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
amount: {
|
||||
fontSize: 13,
|
||||
color: COLORS.TEXT.GREEN,
|
||||
marginRight: 14,
|
||||
},
|
||||
});
|
||||
263
src/screens/registerPayements/OnlinePayment.tsx
Normal file
263
src/screens/registerPayements/OnlinePayment.tsx
Normal file
@@ -0,0 +1,263 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { ScrollView, StyleSheet, View } from 'react-native';
|
||||
import Button from '../../../RN-UI-LIB/src/components/Button';
|
||||
import Text from '../../../RN-UI-LIB/src/components/Text';
|
||||
import TextInput, { TextInputMaskType } from '../../../RN-UI-LIB/src/components/TextInput';
|
||||
import Dropdown from '../../../RN-UI-LIB/src/components/dropdown/Dropdown';
|
||||
import { GenericStyles } from '../../../RN-UI-LIB/src/styles';
|
||||
import { COLORS } from '../../../RN-UI-LIB/src/styles/colors';
|
||||
import { formatAmount } from '../../../RN-UI-LIB/src/utlis/amount';
|
||||
import { PaymentType, generatePaymentLinkAction } from '../../action/paymentActions';
|
||||
import { CLICKSTREAM_EVENT_NAMES } from '../../common/Constants';
|
||||
import {
|
||||
copyToClipboard,
|
||||
getDynamicBottomSheetHeightPercentageFn,
|
||||
getPhoneNumberString,
|
||||
isValidAmountEntered,
|
||||
} from '../../components/utlis/commonFunctions';
|
||||
import { useAppSelector } from '../../hooks';
|
||||
import useIsOnline from '../../hooks/useIsOnline';
|
||||
import { addClickstreamEvent } from '../../services/clickstreamEventService';
|
||||
import { PhoneNumber } from '../caseDetails/interface';
|
||||
import DropdownItem from './DropdownItem';
|
||||
import ModalWrapper from '../../../RN-UI-LIB/src/components/modalWrapper/ModalWrapper';
|
||||
import QrCodeModal from './QrCodeModal';
|
||||
import { toast } from '../../../RN-UI-LIB/src/components/toast';
|
||||
import { ToastMessages } from '../allCases/constants';
|
||||
|
||||
interface IOnlinePayment {
|
||||
caseId: string;
|
||||
amount: number;
|
||||
numbers: PhoneNumber[];
|
||||
pos: number;
|
||||
primaryPhoneNumber: string;
|
||||
}
|
||||
|
||||
interface IRegisterForm {
|
||||
selectedPhoneNumber: string;
|
||||
amount: string;
|
||||
}
|
||||
|
||||
const HEADER_HEIGHT = 100;
|
||||
const ROW_HEIGHT = 40;
|
||||
|
||||
const OnlinePayment: React.FC<IOnlinePayment> = ({
|
||||
caseId,
|
||||
amount,
|
||||
numbers,
|
||||
pos,
|
||||
primaryPhoneNumber,
|
||||
}) => {
|
||||
const { caseDetail } = useAppSelector((state) => ({
|
||||
caseDetail: state.allCases.caseDetails[caseId],
|
||||
}));
|
||||
const isOnline = useIsOnline();
|
||||
const [generateClicked, setGenerateClicked] = useState(false);
|
||||
const [showQrCodeModal, setShowQrCodeModal] = React.useState<boolean>(false);
|
||||
const [paymentLink, setPaymentLink] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (paymentLink && generateClicked) {
|
||||
setShowQrCodeModal(true);
|
||||
}
|
||||
}, [paymentLink, generateClicked]);
|
||||
|
||||
const maxAmount = pos + amount;
|
||||
const {
|
||||
control,
|
||||
getValues,
|
||||
formState: { isValid, errors },
|
||||
setValue,
|
||||
trigger,
|
||||
} = useForm<IRegisterForm>({
|
||||
defaultValues: {
|
||||
amount: String(amount) ?? '',
|
||||
selectedPhoneNumber: primaryPhoneNumber,
|
||||
},
|
||||
mode: 'onChange',
|
||||
});
|
||||
|
||||
const getBottomSheetHeight = getDynamicBottomSheetHeightPercentageFn(HEADER_HEIGHT, ROW_HEIGHT);
|
||||
const ChildComponents = numbers?.map((phoneNumber) => {
|
||||
const { number, createdAt, sourceText } = phoneNumber;
|
||||
return (
|
||||
<DropdownItem
|
||||
createdAt={createdAt}
|
||||
id={number}
|
||||
label={getPhoneNumberString(phoneNumber)}
|
||||
sourceText={sourceText}
|
||||
/>
|
||||
);
|
||||
});
|
||||
let errorMessage = '';
|
||||
switch (errors.amount?.type) {
|
||||
case 'required':
|
||||
errorMessage = 'This is a required field';
|
||||
break;
|
||||
case 'min':
|
||||
case 'max':
|
||||
errorMessage = `The entered amount should be between ${formatAmount(1)} and ${formatAmount(
|
||||
maxAmount
|
||||
)}`;
|
||||
break;
|
||||
}
|
||||
const isPaymentButtonDisabled = !isValid || !isOnline;
|
||||
|
||||
const onSuccess = (link: string) => {
|
||||
setPaymentLink(link);
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
const onError = () => {
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
const generatePaymentLink = () => {
|
||||
setIsLoading(true);
|
||||
const [amountValue, phoneNumberValue] = [
|
||||
Number(getValues('amount')),
|
||||
getValues('selectedPhoneNumber'),
|
||||
];
|
||||
const { loanAccountNumber } = caseDetail;
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_SEND_PAYMENT_LINK, {
|
||||
amount: amountValue,
|
||||
lan: loanAccountNumber!!,
|
||||
phoneNumber: phoneNumberValue,
|
||||
});
|
||||
setGenerateClicked(false);
|
||||
if (paymentLink) {
|
||||
setShowQrCodeModal(true);
|
||||
} else {
|
||||
generatePaymentLinkAction(
|
||||
{
|
||||
type: PaymentType.CUSTOM,
|
||||
phoneNumber: phoneNumberValue,
|
||||
customAmount: amountValue,
|
||||
loanAccountNumber: loanAccountNumber!!,
|
||||
customerId: caseDetail.customerReferenceId,
|
||||
},
|
||||
onSuccess,
|
||||
onError
|
||||
);
|
||||
setGenerateClicked(true);
|
||||
}
|
||||
};
|
||||
|
||||
const isCopyButtonDisabled = !isValid || !isOnline || !paymentLink || paymentLink === '';
|
||||
|
||||
const copyButtonClick = () => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_COPY_LINK_CLICKED, {
|
||||
amount: Number(getValues('amount')),
|
||||
lan: caseDetail.loanAccountNumber!!,
|
||||
phoneNumber: getValues('selectedPhoneNumber'),
|
||||
paymentLink,
|
||||
});
|
||||
if (paymentLink) {
|
||||
copyToClipboard(paymentLink);
|
||||
toast({
|
||||
type: 'info',
|
||||
text1: ToastMessages.SUCCESS_COPYING_PAYMENT_LINK,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={GenericStyles.fill}>
|
||||
<ScrollView style={[GenericStyles.p16, GenericStyles.whiteBackground]}>
|
||||
<Text light>Share a payment link on the selected number</Text>
|
||||
<Text style={[GenericStyles.mb8, GenericStyles.mt16]}>Select number</Text>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field: { value } }) => (
|
||||
<Dropdown
|
||||
placeholder="Select phone number"
|
||||
onValueChange={(number) => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_COLLECT_MONEY_NUMBER_CHANGED, {
|
||||
phoneNumber: number,
|
||||
lan: caseDetail.loanAccountNumber!!,
|
||||
});
|
||||
setValue('selectedPhoneNumber', number);
|
||||
setPaymentLink('');
|
||||
trigger();
|
||||
}}
|
||||
bottomSheetHeight={getBottomSheetHeight(numbers?.length)}
|
||||
header="Select phone number"
|
||||
value={value}
|
||||
>
|
||||
{ChildComponents}
|
||||
</Dropdown>
|
||||
)}
|
||||
name="selectedPhoneNumber"
|
||||
rules={{ required: true }}
|
||||
/>
|
||||
|
||||
<View style={GenericStyles.mt16}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field: { onChange, onBlur, value } }) => (
|
||||
<TextInput
|
||||
titleStyle={{ color: COLORS.TEXT.BLACK }}
|
||||
keyboardType="decimal-pad"
|
||||
title="Enter amount"
|
||||
onChangeText={(s) => {
|
||||
setPaymentLink('');
|
||||
onChange(s);
|
||||
}}
|
||||
maskType={TextInputMaskType.CURRENCY}
|
||||
onBlur={onBlur}
|
||||
placeholder="Enter amount"
|
||||
value={value}
|
||||
LeftComponent={<Text dark>₹</Text>}
|
||||
error={errors?.amount ? true : false}
|
||||
errorMessage={errorMessage}
|
||||
/>
|
||||
)}
|
||||
name="amount"
|
||||
rules={{
|
||||
required: true,
|
||||
min: 1,
|
||||
max: maxAmount,
|
||||
validate: (value) => isValidAmountEntered(Number(value)),
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
<View
|
||||
style={[
|
||||
GenericStyles.whiteBackground,
|
||||
GenericStyles.row,
|
||||
GenericStyles.alignCenter,
|
||||
GenericStyles.spaceBetween,
|
||||
GenericStyles.elevation10,
|
||||
GenericStyles.p16,
|
||||
]}
|
||||
>
|
||||
<Button
|
||||
title="Generate and share link"
|
||||
style={[GenericStyles.w100]}
|
||||
disabled={isPaymentButtonDisabled}
|
||||
showLoader={isLoading}
|
||||
onPress={generatePaymentLink}
|
||||
/>
|
||||
</View>
|
||||
<ModalWrapper
|
||||
animationType="slide"
|
||||
animated
|
||||
onRequestClose={() => setShowQrCodeModal((prev) => !prev)}
|
||||
visible={showQrCodeModal}
|
||||
>
|
||||
<QrCodeModal
|
||||
closeQrCodeModal={() => setShowQrCodeModal(false)}
|
||||
isCopyButtonDisabled={isCopyButtonDisabled}
|
||||
copyButtonClick={copyButtonClick}
|
||||
caseId={caseId}
|
||||
paymentLink={paymentLink}
|
||||
/>
|
||||
</ModalWrapper>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default OnlinePayment;
|
||||
@@ -5,8 +5,6 @@ import Heading from '../../../RN-UI-LIB/src/components/Heading';
|
||||
import Text from '../../../RN-UI-LIB/src/components/Text';
|
||||
import { COLORS } from '../../../RN-UI-LIB/src/styles/colors';
|
||||
import QRCode from 'react-native-qrcode-svg';
|
||||
import { useAppSelector } from '../../hooks';
|
||||
import { RootState } from '../../store/store';
|
||||
import Button from '../../../RN-UI-LIB/src/components/Button';
|
||||
import { useEffect } from 'react';
|
||||
import { addClickstreamEvent } from '../../services/clickstreamEventService';
|
||||
@@ -17,6 +15,7 @@ interface IQrCodeModalProps {
|
||||
isCopyButtonDisabled: boolean;
|
||||
copyButtonClick: () => void;
|
||||
caseId: string;
|
||||
paymentLink: string;
|
||||
}
|
||||
|
||||
const QrCodeModal: React.FC<IQrCodeModalProps> = ({
|
||||
@@ -24,8 +23,8 @@ const QrCodeModal: React.FC<IQrCodeModalProps> = ({
|
||||
isCopyButtonDisabled,
|
||||
copyButtonClick,
|
||||
caseId,
|
||||
paymentLink,
|
||||
}) => {
|
||||
const { paymentLink } = useAppSelector((state: RootState) => state.payment);
|
||||
useEffect(() => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_QR_SCREEN_LOADED, {
|
||||
caseId: caseId,
|
||||
|
||||
@@ -1,41 +1,19 @@
|
||||
import { View } from 'react-native';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import NavigationHeader from '../../../RN-UI-LIB/src/components/NavigationHeader';
|
||||
import { goBack } from '../../components/utlis/navigationUtlis';
|
||||
import Text from '../../../RN-UI-LIB/src/components/Text';
|
||||
import Layout from '../layout/Layout';
|
||||
import { GenericStyles } from '../../../RN-UI-LIB/src/styles';
|
||||
import Dropdown from '../../../RN-UI-LIB/src/components/dropdown/Dropdown';
|
||||
import TextInput, { TextInputMaskType } from '../../../RN-UI-LIB/src/components/TextInput';
|
||||
import Button from '../../../RN-UI-LIB/src/components/Button';
|
||||
import { COLORS } from '../../../RN-UI-LIB/src/styles/colors';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { toast } from '../../../RN-UI-LIB/src/components/toast';
|
||||
import {
|
||||
copyToClipboard,
|
||||
getDynamicBottomSheetHeightPercentageFn,
|
||||
getPhoneNumberString,
|
||||
isValidAmountEntered,
|
||||
} from '../../components/utlis/commonFunctions';
|
||||
import useIsOnline from '../../hooks/useIsOnline';
|
||||
import { RootState } from '../../store/store';
|
||||
import { generatePaymentLinkAction } from '../../action/paymentActions';
|
||||
import { useAppDispatch, useAppSelector } from '../../hooks';
|
||||
import DropdownItem from './DropdownItem';
|
||||
import { PhoneNumber } from '../caseDetails/interface';
|
||||
import { setLoading, setPaymentLink } from '../../reducer/paymentSlice';
|
||||
import { ToastMessages } from '../allCases/constants';
|
||||
import { addClickstreamEvent } from '../../services/clickstreamEventService';
|
||||
import { CLICKSTREAM_EVENT_NAMES } from '../../common/Constants';
|
||||
import QrCodeModal from './QrCodeModal';
|
||||
import ModalWrapper from '../../../RN-UI-LIB/src/components/modalWrapper/ModalWrapper';
|
||||
import OfflineScreen from '../../common/OfflineScreen';
|
||||
import { formatAmount } from '../../../RN-UI-LIB/src/utlis/amount';
|
||||
|
||||
interface IRegisterForm {
|
||||
selectedPhoneNumber: string;
|
||||
amount: string;
|
||||
}
|
||||
import OnlinePayment from './OnlinePayment';
|
||||
import CustomTabs from '../../../RN-UI-LIB/src/components/customTabs/CustomTabs';
|
||||
import { GenericStyles } from '../../../RN-UI-LIB/src/styles';
|
||||
import { View } from 'react-native';
|
||||
import Foreclosure from './Foreclosure';
|
||||
import { getPrimaryPhoneNumber } from '../../components/utlis/commonFunctions';
|
||||
import { PaymentType } from '../../action/paymentActions';
|
||||
|
||||
interface IRegisterPayments {
|
||||
route: {
|
||||
@@ -48,119 +26,35 @@ interface IRegisterPayments {
|
||||
};
|
||||
}
|
||||
|
||||
const HEADER_HEIGHT = 100;
|
||||
const ROW_HEIGHT = 40;
|
||||
const PAGE_TITLE = 'Collect Money';
|
||||
|
||||
const TABS = [
|
||||
{
|
||||
key: PaymentType.CUSTOM,
|
||||
label: 'Online payment',
|
||||
},
|
||||
{
|
||||
key: PaymentType.FORECLOSURE,
|
||||
label: 'Foreclosure',
|
||||
},
|
||||
];
|
||||
|
||||
const RegisterPayments: React.FC<IRegisterPayments> = ({ route }) => {
|
||||
let {
|
||||
params: { caseId, numbers, amount, pos },
|
||||
params: { caseId, amount, numbers, pos },
|
||||
} = route;
|
||||
const { isLoading, paymentLink } = useAppSelector((state: RootState) => state.payment);
|
||||
const dispatch = useAppDispatch();
|
||||
const isOnline = useIsOnline();
|
||||
const caseDetail = useAppSelector((state) => state.allCases.caseDetails[caseId]);
|
||||
const maxAmount = pos + amount;
|
||||
const {
|
||||
control,
|
||||
getValues,
|
||||
formState: { isValid, defaultValues, errors },
|
||||
setValue,
|
||||
trigger,
|
||||
} = useForm<IRegisterForm>({
|
||||
defaultValues: {
|
||||
amount: String(amount) ?? '',
|
||||
selectedPhoneNumber: '',
|
||||
},
|
||||
mode: 'onChange',
|
||||
});
|
||||
const [generateClicked, setGenerateClicked] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (paymentLink && generateClicked) {
|
||||
setShowQrCodeModal(true);
|
||||
}
|
||||
}, [paymentLink, generateClicked]);
|
||||
|
||||
const ChildComponents = numbers?.map((phoneNumber) => {
|
||||
const { number, createdAt, sourceText } = phoneNumber;
|
||||
return (
|
||||
<DropdownItem
|
||||
createdAt={createdAt}
|
||||
id={number}
|
||||
label={getPhoneNumberString(phoneNumber)}
|
||||
sourceText={sourceText}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const [showQrCodeModal, setShowQrCodeModal] = React.useState<boolean>(false);
|
||||
|
||||
const copyButtonClick = () => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_COPY_LINK_CLICKED, {
|
||||
amount: Number(getValues('amount')),
|
||||
lan: caseDetail.loanAccountNumber!!,
|
||||
phoneNumber: getValues('selectedPhoneNumber'),
|
||||
paymentLink,
|
||||
});
|
||||
if (paymentLink) {
|
||||
copyToClipboard(paymentLink);
|
||||
toast({
|
||||
type: 'info',
|
||||
text1: ToastMessages.SUCCESS_COPYING_PAYMENT_LINK,
|
||||
});
|
||||
}
|
||||
};
|
||||
const isPaymentButtonDisabled = !isValid || !isOnline;
|
||||
const isCopyButtonDisabled = !isValid || !isOnline || !paymentLink || paymentLink === '';
|
||||
let errorMessage = '';
|
||||
switch (errors.amount?.type) {
|
||||
case 'required':
|
||||
errorMessage = 'This is a required field';
|
||||
break;
|
||||
case 'min':
|
||||
case 'max':
|
||||
errorMessage = `The entered amount should be between ${formatAmount(1)} and ${formatAmount(
|
||||
maxAmount
|
||||
)}`;
|
||||
break;
|
||||
}
|
||||
|
||||
const generatePaymentLink = () => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_SEND_PAYMENT_LINK, {
|
||||
amount: Number(getValues('amount')),
|
||||
lan: caseDetail.loanAccountNumber!!,
|
||||
phoneNumber: getValues('selectedPhoneNumber'),
|
||||
});
|
||||
setGenerateClicked(false);
|
||||
if (paymentLink) {
|
||||
setShowQrCodeModal(true);
|
||||
} else {
|
||||
dispatch(
|
||||
generatePaymentLinkAction({
|
||||
alternateContactNumber: getValues('selectedPhoneNumber'),
|
||||
customAmount: {
|
||||
currency: 'INR',
|
||||
amount: Number(getValues('amount')),
|
||||
},
|
||||
customAmountProvided: Number(getValues('amount')) > -1,
|
||||
loanAccountNumber: caseDetail.loanAccountNumber!!,
|
||||
notifyToAlternateContact: true,
|
||||
customerReferenceId: caseDetail.customerReferenceId,
|
||||
})
|
||||
);
|
||||
setGenerateClicked(true);
|
||||
}
|
||||
};
|
||||
const [currentTab, setCurrentTab] = useState<string>(PaymentType.CUSTOM);
|
||||
|
||||
useEffect(() => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_COLLECT_MONEY_LOADED, { caseId: caseId });
|
||||
return () => {
|
||||
dispatch(setPaymentLink(''));
|
||||
dispatch(setLoading(false));
|
||||
};
|
||||
}, []);
|
||||
|
||||
const getBottomSheetHeight = getDynamicBottomSheetHeightPercentageFn(HEADER_HEIGHT, ROW_HEIGHT);
|
||||
const onTabChange = (tab: string) => {
|
||||
setCurrentTab(tab);
|
||||
};
|
||||
|
||||
const primaryPhoneNumber: string = useMemo(() => getPrimaryPhoneNumber(numbers), [numbers]);
|
||||
|
||||
if (!isOnline) {
|
||||
return <OfflineScreen goBack={goBack} pageTitle={PAGE_TITLE} />;
|
||||
@@ -168,86 +62,26 @@ const RegisterPayments: React.FC<IRegisterPayments> = ({ route }) => {
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<NavigationHeader title={PAGE_TITLE} onBack={goBack} />
|
||||
<View style={[GenericStyles.fill, GenericStyles.p16, GenericStyles.whiteBackground]}>
|
||||
<Text light>Share a payment link on the selected number</Text>
|
||||
<Text style={[GenericStyles.mb8, GenericStyles.mt16]}>Select number</Text>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field: { onChange, onBlur, value } }) => (
|
||||
<Dropdown
|
||||
placeholder="Select phone number"
|
||||
onValueChange={(number) => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_COLLECT_MONEY_NUMBER_CHANGED, {
|
||||
phoneNumber: number,
|
||||
lan: caseDetail.loanAccountNumber!!,
|
||||
});
|
||||
setValue('selectedPhoneNumber', number);
|
||||
dispatch(setPaymentLink(''));
|
||||
trigger();
|
||||
}}
|
||||
bottomSheetHeight={getBottomSheetHeight(numbers?.length)}
|
||||
header="Select phone number"
|
||||
value={value}
|
||||
>
|
||||
{ChildComponents}
|
||||
</Dropdown>
|
||||
)}
|
||||
name="selectedPhoneNumber"
|
||||
rules={{ required: true }}
|
||||
<View style={[GenericStyles.fill, GenericStyles.whiteBackground]}>
|
||||
<NavigationHeader title={PAGE_TITLE} onBack={goBack} />
|
||||
<CustomTabs
|
||||
tabs={TABS}
|
||||
currentTab={currentTab}
|
||||
onTabChange={onTabChange}
|
||||
containerStyle={[GenericStyles.ml16, GenericStyles.pt16]}
|
||||
/>
|
||||
|
||||
<View style={GenericStyles.mt16}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field: { onChange, onBlur, value } }) => (
|
||||
<TextInput
|
||||
titleStyle={{ color: COLORS.TEXT.BLACK }}
|
||||
keyboardType="decimal-pad"
|
||||
title="Enter amount"
|
||||
onChangeText={(s) => {
|
||||
dispatch(setPaymentLink(''));
|
||||
onChange(s);
|
||||
}}
|
||||
maskType={TextInputMaskType.CURRENCY}
|
||||
onBlur={onBlur}
|
||||
placeholder="Enter amount"
|
||||
value={value}
|
||||
LeftComponent={<Text dark>₹</Text>}
|
||||
error={errors?.amount ? true : false}
|
||||
errorMessage={errorMessage}
|
||||
/>
|
||||
)}
|
||||
name="amount"
|
||||
rules={{
|
||||
required: true,
|
||||
min: 1,
|
||||
max: maxAmount,
|
||||
validate: (value) => isValidAmountEntered(Number(value)),
|
||||
}}
|
||||
{currentTab === PaymentType.CUSTOM ? (
|
||||
<OnlinePayment
|
||||
caseId={caseId}
|
||||
amount={amount}
|
||||
numbers={numbers}
|
||||
pos={pos}
|
||||
primaryPhoneNumber={primaryPhoneNumber}
|
||||
/>
|
||||
</View>
|
||||
<Button
|
||||
title="Generate and share payment link"
|
||||
style={[GenericStyles.mt32, GenericStyles.w100]}
|
||||
disabled={isPaymentButtonDisabled}
|
||||
showLoader={isLoading}
|
||||
onPress={generatePaymentLink}
|
||||
/>
|
||||
) : (
|
||||
<Foreclosure caseId={caseId} numbers={numbers} primaryPhoneNumber={primaryPhoneNumber} />
|
||||
)}
|
||||
</View>
|
||||
<ModalWrapper
|
||||
animationType="slide"
|
||||
animated
|
||||
onRequestClose={() => setShowQrCodeModal((prev) => !prev)}
|
||||
visible={showQrCodeModal}
|
||||
>
|
||||
<QrCodeModal
|
||||
closeQrCodeModal={() => setShowQrCodeModal(false)}
|
||||
isCopyButtonDisabled={isCopyButtonDisabled}
|
||||
copyButtonClick={copyButtonClick}
|
||||
caseId={caseId}
|
||||
/>
|
||||
</ModalWrapper>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -7566,7 +7566,7 @@ react-native-call-log@2.1.2:
|
||||
resolved "https://registry.yarnpkg.com/react-native-call-log/-/react-native-call-log-2.1.2.tgz#f80d2fcb45f72118eb8048d5bfdef191fd4a3df3"
|
||||
integrity sha512-nWHmb+QMN/AbbZFEuUGiQePssPgjQr5dibNAURDlqO4S5wuLk1XzxxsLUAVHZnB0FdJoMlajD7tUAXtaoxYwUQ==
|
||||
|
||||
react-native-clarity@^0.0.3:
|
||||
react-native-clarity@0.0.3:
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/react-native-clarity/-/react-native-clarity-0.0.3.tgz#e9c83dd097c0cdaf0751c7f6683fc3b038134637"
|
||||
integrity sha512-uXccmC9iKwxBpsy7jOLaEo87RBQax/WNCI5EDa/hldn1+8eNCwwhkUdJZeNgi1pZ/dyBmHZj0Be0jgWy4K4gkA==
|
||||
|
||||
Reference in New Issue
Block a user