diff --git a/RN-UI-LIB b/RN-UI-LIB index ca813aaa..a72cd597 160000 --- a/RN-UI-LIB +++ b/RN-UI-LIB @@ -1 +1 @@ -Subproject commit ca813aaac452da2ddcf827151508a9c2581c7a6e +Subproject commit a72cd5971edd1cafd6b79be118c0b7875d15ef52 diff --git a/src/action/paymentActions.ts b/src/action/paymentActions.ts index d45e673c..49070bbd 100644 --- a/src/action/paymentActions.ts +++ b/src/action/paymentActions.ts @@ -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 {} + Pick {} -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; + } +}; diff --git a/src/components/accordion/index.tsx b/src/components/accordion/index.tsx index a07e0dc7..ab2b9667 100644 --- a/src/components/accordion/index.tsx +++ b/src/components/accordion/index.tsx @@ -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; + defaultExpanded?: boolean; } -const Accordion: React.FC = ({ title, content }) => { - const [isExpanded, setIsExpanded] = useState(false); +const Accordion: React.FC = ({ 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 ( - + - - {title} - - - - + {title} + + + - {isExpanded ? {content} : null} + + + {children} + + ); }; 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%', }, }); diff --git a/src/components/floatingInfoText/index.tsx b/src/components/floatingInfoText/index.tsx index 512ef793..3e7956bf 100644 --- a/src/components/floatingInfoText/index.tsx +++ b/src/components/floatingInfoText/index.tsx @@ -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; + onClose?: () => void; } -const FloatingInfoText: React.FC = ({ message, top, bottom }) => { - const [showText, setShowText] = useState(true); - const toggleText = () => setShowText(false); +const FloatingInfoText: React.FC = ({ + visible = true, + message, + onClose, + style, +}) => { + const [showText, setShowText] = useState(visible); + const toggleText = () => { + setShowText(false); + onClose && typeof onClose === 'function' && onClose(); + }; if (!showText) return null; return ( - - - - - {message} - - - - - + + + {message} + +