From a0723cde1b93f9234561aef815832bece072480c Mon Sep 17 00:00:00 2001 From: Mantri Ramkishor Date: Wed, 2 Apr 2025 18:56:16 +0530 Subject: [PATCH] NTP-50799 | Top 5 Addresses (#1120) Co-authored-by: Aman Chaturvedi --- RN-UI-LIB | 2 +- android/app/build.gradle | 4 +- buildFlavor/field/buildNumber.txt | 2 +- buildFlavor/field/buildVersion.txt | 2 +- buildFlavor/tele/buildNumber.txt | 2 +- buildFlavor/tele/buildVersion.txt | 2 +- package.json | 4 +- src/action/filterActions.ts | 1 + src/assets/icons/AddFeedbackIcon.tsx | 56 ++++++ src/assets/icons/CopyOutlineIcon.tsx | 17 ++ src/assets/icons/GoogleMapIcon.tsx | 27 +++ src/assets/icons/LocationOnMap3DIcon.tsx | 45 +++++ src/assets/icons/LocationOnMapIcon.tsx | 48 +++++ src/assets/icons/LollipopIcon.tsx | 23 +++ src/assets/icons/MapDirectionFilledIcon.tsx | 20 +++ src/assets/icons/MapDirectionIcon.tsx | 17 ++ src/assets/icons/OldFeedbacksIcon.tsx | 56 ++++++ src/assets/icons/RankTagIcon.tsx | 16 ++ src/assets/icons/TickSmallIcon.tsx | 15 ++ src/assets/icons/TickSmallSolidIcon.tsx | 18 ++ src/assets/icons/VisitedTagIcon.tsx | 30 ++++ src/assets/lottie/SwipeAnimation.json | 1 + src/common/Constants.ts | 53 +++++- src/common/ModalWrapperForAlfredV2.tsx | 8 +- src/common/ShadowLine.tsx | 41 +++++ .../Tour/components/CopilotStep.tsx | 2 + src/components/Tour/components/Tooltip.tsx | 5 + src/components/Tour/components/style.ts | 6 + src/components/Tour/types.ts | 2 + src/components/carousel/Carousel.tsx | 93 ++++++++++ src/components/carousel/CarouselItem.tsx | 62 +++++++ src/components/carousel/Pagination.tsx | 37 ++++ src/components/carousel/PaginationItem.tsx | 83 +++++++++ src/components/carousel/constants.ts | 3 + src/components/carousel/interfaces.ts | 40 +++++ src/components/form/AnswerRender.tsx | 17 +- src/components/form/Submit.tsx | 2 +- .../form/components/AddressAnswer.tsx | 31 ++++ .../AddressSelection/AddressSelectionBody.tsx | 123 +++++++++++++ .../AddressSelection/AddressSelectionV2.tsx | 47 +++++ .../components/AddressSelection/interface.ts | 18 ++ .../form/components/PhoneNumberSelection.tsx | 2 +- .../form/services/formComponents.ts | 4 +- .../utlis/addressGeolocationUtils.ts | 4 +- src/components/utlis/apiHelper.ts | 5 +- src/components/utlis/commonFunctions.ts | 5 + src/reducer/topAddressesSlice.ts | 91 ++++++++++ src/screens/addresses/actions.ts | 87 +++++++++ .../common/AddressItemAddressString.tsx | 93 ++++++++++ .../addresses/common/AddressItemFeedback.tsx | 64 +++++++ .../addresses/common/AddressItemHeader.tsx | 92 ++++++++++ src/screens/addresses/constants.ts | 14 ++ src/screens/addresses/interfaces.ts | 166 ++++++++++++++++++ .../otherAddresses/OtherAddressActions.tsx | 108 ++++++++++++ .../otherAddresses/OtherAddressItem.tsx | 38 ++++ .../otherAddresses/OtherAddressList.tsx | 60 +++++++ .../otherAddresses/OtherAddresses.tsx | 45 +++++ .../TopAddressSimilarAddressCard.tsx | 83 +++++++++ .../TopAddressSimilarAddresses.tsx | 94 ++++++++++ .../addresses/topAddresses/TopAddressItem.tsx | 46 +++++ .../TopAddressItemBottomActions.tsx | 90 ++++++++++ .../topAddresses/TopAddressItemRankTag.tsx | 42 +++++ .../topAddresses/TopAddressItemTags.tsx | 36 ++++ .../TopAddressOtherAddressesItem.tsx | 71 ++++++++ .../addresses/topAddresses/TopAddresses.tsx | 125 +++++++++++++ src/screens/addresses/utils.ts | 76 ++++++++ .../caseDetails/AllocatedAddressDetails.tsx | 102 +++++++++++ src/screens/caseDetails/CaseDetailStack.tsx | 6 + .../caseDetails/CollectionCaseDetail.tsx | 12 +- src/screens/caseDetails/EmiDetailsSection.tsx | 2 +- .../caseDetails/ViewAddressSection.tsx | 76 ++++++-- src/screens/caseDetails/interface.ts | 2 +- src/screens/layout/Layout.tsx | 3 - src/screens/login/index.tsx | 24 +-- src/services/casePayload.transformer.ts | 15 +- src/store/store.ts | 2 + 76 files changed, 2787 insertions(+), 79 deletions(-) create mode 100644 src/assets/icons/AddFeedbackIcon.tsx create mode 100644 src/assets/icons/CopyOutlineIcon.tsx create mode 100644 src/assets/icons/GoogleMapIcon.tsx create mode 100644 src/assets/icons/LocationOnMap3DIcon.tsx create mode 100644 src/assets/icons/LocationOnMapIcon.tsx create mode 100644 src/assets/icons/LollipopIcon.tsx create mode 100644 src/assets/icons/MapDirectionFilledIcon.tsx create mode 100644 src/assets/icons/MapDirectionIcon.tsx create mode 100644 src/assets/icons/OldFeedbacksIcon.tsx create mode 100644 src/assets/icons/RankTagIcon.tsx create mode 100644 src/assets/icons/TickSmallIcon.tsx create mode 100644 src/assets/icons/TickSmallSolidIcon.tsx create mode 100644 src/assets/icons/VisitedTagIcon.tsx create mode 100644 src/assets/lottie/SwipeAnimation.json create mode 100644 src/common/ShadowLine.tsx create mode 100644 src/components/carousel/Carousel.tsx create mode 100644 src/components/carousel/CarouselItem.tsx create mode 100644 src/components/carousel/Pagination.tsx create mode 100644 src/components/carousel/PaginationItem.tsx create mode 100644 src/components/carousel/constants.ts create mode 100644 src/components/carousel/interfaces.ts create mode 100644 src/components/form/components/AddressAnswer.tsx create mode 100644 src/components/form/components/AddressSelection/AddressSelectionBody.tsx create mode 100644 src/components/form/components/AddressSelection/AddressSelectionV2.tsx create mode 100644 src/components/form/components/AddressSelection/interface.ts create mode 100644 src/reducer/topAddressesSlice.ts create mode 100644 src/screens/addresses/actions.ts create mode 100644 src/screens/addresses/common/AddressItemAddressString.tsx create mode 100644 src/screens/addresses/common/AddressItemFeedback.tsx create mode 100644 src/screens/addresses/common/AddressItemHeader.tsx create mode 100644 src/screens/addresses/constants.ts create mode 100644 src/screens/addresses/interfaces.ts create mode 100644 src/screens/addresses/otherAddresses/OtherAddressActions.tsx create mode 100644 src/screens/addresses/otherAddresses/OtherAddressItem.tsx create mode 100644 src/screens/addresses/otherAddresses/OtherAddressList.tsx create mode 100644 src/screens/addresses/otherAddresses/OtherAddresses.tsx create mode 100644 src/screens/addresses/similarAddresses/TopAddressSimilarAddressCard.tsx create mode 100644 src/screens/addresses/similarAddresses/TopAddressSimilarAddresses.tsx create mode 100644 src/screens/addresses/topAddresses/TopAddressItem.tsx create mode 100644 src/screens/addresses/topAddresses/TopAddressItemBottomActions.tsx create mode 100644 src/screens/addresses/topAddresses/TopAddressItemRankTag.tsx create mode 100644 src/screens/addresses/topAddresses/TopAddressItemTags.tsx create mode 100644 src/screens/addresses/topAddresses/TopAddressOtherAddressesItem.tsx create mode 100644 src/screens/addresses/topAddresses/TopAddresses.tsx create mode 100644 src/screens/addresses/utils.ts create mode 100644 src/screens/caseDetails/AllocatedAddressDetails.tsx diff --git a/RN-UI-LIB b/RN-UI-LIB index 69b6c0b7..d19cd8f5 160000 --- a/RN-UI-LIB +++ b/RN-UI-LIB @@ -1 +1 @@ -Subproject commit 69b6c0b76feb0d1e48456b25377aee2faa166a5d +Subproject commit d19cd8f56bf491b2eeee12eca853c119742c0535 diff --git a/android/app/build.gradle b/android/app/build.gradle index 84518593..78d77126 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -113,8 +113,8 @@ def jscFlavor = 'org.webkit:android-jsc:+' def enableHermes = project.ext.react.get("enableHermes", false); -def VERSION_CODE = 250 -def VERSION_NAME = "2.18.7" +def VERSION_CODE = 251 +def VERSION_NAME = "2.18.8" android { namespace "com.avapp" diff --git a/buildFlavor/field/buildNumber.txt b/buildFlavor/field/buildNumber.txt index 8a32cf78..f123c4f4 100644 --- a/buildFlavor/field/buildNumber.txt +++ b/buildFlavor/field/buildNumber.txt @@ -1 +1 @@ -250 \ No newline at end of file +251 \ No newline at end of file diff --git a/buildFlavor/field/buildVersion.txt b/buildFlavor/field/buildVersion.txt index 394c48dd..caa23986 100644 --- a/buildFlavor/field/buildVersion.txt +++ b/buildFlavor/field/buildVersion.txt @@ -1 +1 @@ -2.18.7 \ No newline at end of file +2.18.8 \ No newline at end of file diff --git a/buildFlavor/tele/buildNumber.txt b/buildFlavor/tele/buildNumber.txt index e0da8ae0..3bac779c 100644 --- a/buildFlavor/tele/buildNumber.txt +++ b/buildFlavor/tele/buildNumber.txt @@ -1 +1 @@ -309 \ No newline at end of file +310 \ No newline at end of file diff --git a/buildFlavor/tele/buildVersion.txt b/buildFlavor/tele/buildVersion.txt index 065ed156..9f7b5569 100644 --- a/buildFlavor/tele/buildVersion.txt +++ b/buildFlavor/tele/buildVersion.txt @@ -1 +1 @@ -100.2.5 \ No newline at end of file +100.2.6 \ No newline at end of file diff --git a/package.json b/package.json index ce474575..cf4798d9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "AV_APP", - "version": "2.18.7", - "buildNumber": "250", + "version": "2.18.8", + "buildNumber": "251", "private": true, "scripts": { "android:dev": "yarn move:dev && react-native run-android", diff --git a/src/action/filterActions.ts b/src/action/filterActions.ts index cf76c004..65440a23 100644 --- a/src/action/filterActions.ts +++ b/src/action/filterActions.ts @@ -12,6 +12,7 @@ dayjs.extend(timezone); export const CoachMarkFeatures = { CASE_STATUS_FILTERS: 'caseStatusFilters', + TOP_5_ADDRESSES: 'top5Addresses' }; export const showCoachMark = async ( diff --git a/src/assets/icons/AddFeedbackIcon.tsx b/src/assets/icons/AddFeedbackIcon.tsx new file mode 100644 index 00000000..d6afbabc --- /dev/null +++ b/src/assets/icons/AddFeedbackIcon.tsx @@ -0,0 +1,56 @@ +import * as React from 'react'; +import Svg, { Path } from 'react-native-svg'; +const AddFeedbackIcon = () => ( + + + + + + + + + +); +export default AddFeedbackIcon; diff --git a/src/assets/icons/CopyOutlineIcon.tsx b/src/assets/icons/CopyOutlineIcon.tsx new file mode 100644 index 00000000..e07d00ef --- /dev/null +++ b/src/assets/icons/CopyOutlineIcon.tsx @@ -0,0 +1,17 @@ +import * as React from 'react'; +import Svg, { Mask, Rect, G, Path } from 'react-native-svg'; + +const CopyOutlineIcon = () => ( + + + + + + + + +); +export default CopyOutlineIcon; diff --git a/src/assets/icons/GoogleMapIcon.tsx b/src/assets/icons/GoogleMapIcon.tsx new file mode 100644 index 00000000..0dd1df1c --- /dev/null +++ b/src/assets/icons/GoogleMapIcon.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; +import Svg, { Path } from 'react-native-svg'; +const GoogleMapIcon = () => ( + + + + + + + +); +export default GoogleMapIcon; diff --git a/src/assets/icons/LocationOnMap3DIcon.tsx b/src/assets/icons/LocationOnMap3DIcon.tsx new file mode 100644 index 00000000..9043a421 --- /dev/null +++ b/src/assets/icons/LocationOnMap3DIcon.tsx @@ -0,0 +1,45 @@ +import * as React from 'react'; +import Svg, { Path, Ellipse } from 'react-native-svg'; +const LocationOnMap3DIcon = () => ( + + + + + + + + + + + + +); +export default LocationOnMap3DIcon; diff --git a/src/assets/icons/LocationOnMapIcon.tsx b/src/assets/icons/LocationOnMapIcon.tsx new file mode 100644 index 00000000..6daf679a --- /dev/null +++ b/src/assets/icons/LocationOnMapIcon.tsx @@ -0,0 +1,48 @@ +import * as React from 'react'; +import Svg, { Path } from 'react-native-svg'; +const LocationOnMapIcon = () => ( + + + + + + + + + + + + +); +export default LocationOnMapIcon; diff --git a/src/assets/icons/LollipopIcon.tsx b/src/assets/icons/LollipopIcon.tsx new file mode 100644 index 00000000..6dc51b85 --- /dev/null +++ b/src/assets/icons/LollipopIcon.tsx @@ -0,0 +1,23 @@ +import * as React from 'react'; +import Svg, { Path } from 'react-native-svg'; + +const LollipopIcon = () => ( + + + + + +); +export default LollipopIcon; diff --git a/src/assets/icons/MapDirectionFilledIcon.tsx b/src/assets/icons/MapDirectionFilledIcon.tsx new file mode 100644 index 00000000..e1cde36f --- /dev/null +++ b/src/assets/icons/MapDirectionFilledIcon.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { G, Mask, Path, Rect, Svg } from 'react-native-svg'; + +const MapDirectionFilledIcon = () => { + return ( + + + + + + + + + ); +}; + +export default MapDirectionFilledIcon; diff --git a/src/assets/icons/MapDirectionIcon.tsx b/src/assets/icons/MapDirectionIcon.tsx new file mode 100644 index 00000000..1387326e --- /dev/null +++ b/src/assets/icons/MapDirectionIcon.tsx @@ -0,0 +1,17 @@ +import * as React from 'react'; +import Svg, { Mask, Rect, G, Path } from 'react-native-svg'; + +const MapDirectionIcon = () => ( + + + + + + + + +); +export default MapDirectionIcon; diff --git a/src/assets/icons/OldFeedbacksIcon.tsx b/src/assets/icons/OldFeedbacksIcon.tsx new file mode 100644 index 00000000..a4eaaf83 --- /dev/null +++ b/src/assets/icons/OldFeedbacksIcon.tsx @@ -0,0 +1,56 @@ +import * as React from 'react'; +import Svg, { Path } from 'react-native-svg'; +const OldFeedbacksIcon = () => ( + + + + + + + + + +); +export default OldFeedbacksIcon; diff --git a/src/assets/icons/RankTagIcon.tsx b/src/assets/icons/RankTagIcon.tsx new file mode 100644 index 00000000..33d1d29a --- /dev/null +++ b/src/assets/icons/RankTagIcon.tsx @@ -0,0 +1,16 @@ +import * as React from 'react'; +import Svg, { Path, Rect } from 'react-native-svg'; + +const RankTagIcon = () => ( + + + + + + +); + +export default RankTagIcon; diff --git a/src/assets/icons/TickSmallIcon.tsx b/src/assets/icons/TickSmallIcon.tsx new file mode 100644 index 00000000..929922fe --- /dev/null +++ b/src/assets/icons/TickSmallIcon.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; +import Svg, { Path } from 'react-native-svg'; + +const TickSmallIcon = () => ( + + + +); +export default TickSmallIcon; diff --git a/src/assets/icons/TickSmallSolidIcon.tsx b/src/assets/icons/TickSmallSolidIcon.tsx new file mode 100644 index 00000000..16549c02 --- /dev/null +++ b/src/assets/icons/TickSmallSolidIcon.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import Svg, { G, Mask, Path, Rect } from 'react-native-svg'; + +const TickSmallSolidIcon = () => ( + + + + + + + + + +); +export default TickSmallSolidIcon; diff --git a/src/assets/icons/VisitedTagIcon.tsx b/src/assets/icons/VisitedTagIcon.tsx new file mode 100644 index 00000000..154e6c44 --- /dev/null +++ b/src/assets/icons/VisitedTagIcon.tsx @@ -0,0 +1,30 @@ +import * as React from 'react'; +import Svg, { Path } from 'react-native-svg'; + +const VisitedTagIcon = () => ( + + + + + + + + +); +export default VisitedTagIcon; diff --git a/src/assets/lottie/SwipeAnimation.json b/src/assets/lottie/SwipeAnimation.json new file mode 100644 index 00000000..0abab25a --- /dev/null +++ b/src/assets/lottie/SwipeAnimation.json @@ -0,0 +1 @@ +{"v":"5.9.0","fr":25,"ip":0,"op":70,"w":125,"h":125,"nm":"Swipe icon for animation 1x","ddd":0,"assets":[{"id":"comp_0","nm":"Swipe icon for animation 5x","fr":25,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Finger outline","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.735],"y":[1]},"o":{"x":[0.447],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.628],"y":[0]},"t":8.19,"s":[18]},{"i":{"x":[0.782],"y":[1]},"o":{"x":[0.484],"y":[0]},"t":11.515,"s":[18]},{"i":{"x":[0.479],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":31.026,"s":[-20]},{"t":41,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.738,"y":1},"o":{"x":0.447,"y":0},"t":0,"s":[254.895,395.88,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.628,"y":0.628},"t":8.19,"s":[284.895,395.88,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.782,"y":1},"o":{"x":0.484,"y":0},"t":11.515,"s":[284.895,395.88,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.479,"y":1},"o":{"x":0.333,"y":0},"t":31.026,"s":[210.895,395.88,0],"to":[0,0,0],"ti":[0,0,0]},{"t":41,"s":[254.895,395.88,0]}],"ix":2,"l":2},"a":{"a":0,"k":[112.203,273.98,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.704,"y":1},"o":{"x":0.634,"y":0},"t":0,"s":[{"i":[[0,0],[0,0],[7.999,-5.792],[-4.873,-7.86],[0,0],[-20.501,0.092],[0,0],[-16.502,24.179],[-1.057,4.827],[-0.874,36.819],[0.166,9.493],[4.332,3.554],[3.664,0.488],[0,0],[18.156,2.712],[0,0],[18.111,2.483],[2.62,-21.559],[0,0],[8.044,0],[0,-8.045],[0,0]],"o":[[0,0],[-5.7,-8.044],[-7.492,5.424],[0,0],[11.951,19.26],[0,0],[0,0],[2.804,-4.091],[2.253,-10.572],[0,0],[-0.098,-5.603],[-2.223,-1.823],[-11.4,-1.517],[0,0],[-18.157,-2.712],[0,0],[0,0],[0,0],[0,-8.091],[-8.09,0],[0,0],[0,0]],"v":[[-32.299,27.832],[-61.947,-14.089],[-86.77,-18.18],[-91.366,5.401],[-32.023,101.057],[23.78,131.44],[24.608,131.44],[82.294,109.836],[87.902,96.138],[95.717,22.914],[96.072,-4.672],[89.27,-19.233],[80.503,-23.052],[62.392,-9.4],[48.97,-35.463],[29.617,-19.559],[15.965,-45.622],[-3.018,-26.775],[-3.018,-107.397],[-17.635,-122.015],[-32.253,-107.397],[-32.253,27.787]],"c":true}]},{"i":{"x":0.851,"y":1},"o":{"x":0.628,"y":0},"t":8.021,"s":[{"i":[[0,0],[0,0],[7.999,-5.792],[-4.873,-7.86],[0,0],[-20.501,0.092],[0,0],[-16.502,24.179],[-1.057,4.827],[-0.874,36.819],[0.166,9.493],[4.332,3.554],[3.664,0.488],[0,0],[18.156,2.712],[0,0],[18.111,2.483],[2.62,-21.559],[0,0],[8.044,0],[0,-8.045],[0,0]],"o":[[0,0],[-5.7,-8.044],[-7.492,5.424],[0,0],[11.951,19.26],[0,0],[0,0],[2.804,-4.091],[2.253,-10.572],[0,0],[-0.098,-5.603],[-2.223,-1.823],[-11.4,-1.517],[0,0],[-18.157,-2.712],[0,0],[0,0],[0,0],[0,-8.091],[-8.09,0],[0,0],[0,0]],"v":[[-32.299,27.832],[-61.947,-14.089],[-86.77,-18.18],[-91.366,5.401],[-32.023,101.057],[23.78,131.44],[24.608,131.44],[82.294,109.836],[87.902,96.138],[95.717,22.914],[96.072,-4.672],[89.27,-19.233],[80.503,-23.052],[62.392,-9.4],[48.97,-35.463],[29.617,-19.559],[15.965,-45.622],[-3.018,-26.775],[-1.047,-87.445],[-15.664,-102.063],[-30.282,-87.445],[-32.253,27.787]],"c":true}]},{"i":{"x":0.577,"y":1},"o":{"x":0.628,"y":0},"t":11.587,"s":[{"i":[[0,0],[0,0],[7.999,-5.792],[-4.873,-7.86],[0,0],[-20.501,0.092],[0,0],[-16.502,24.179],[-1.057,4.827],[-0.874,36.819],[0.166,9.493],[4.332,3.554],[3.664,0.488],[0,0],[18.156,2.712],[0,0],[18.111,2.483],[2.62,-21.559],[0,0],[8.044,0],[0,-8.045],[0,0]],"o":[[0,0],[-5.7,-8.044],[-7.492,5.424],[0,0],[11.951,19.26],[0,0],[0,0],[2.804,-4.091],[2.253,-10.572],[0,0],[-0.098,-5.603],[-2.223,-1.823],[-11.4,-1.517],[0,0],[-18.157,-2.712],[0,0],[0,0],[0,0],[0,-8.091],[-8.09,0],[0,0],[0,0]],"v":[[-32.299,27.832],[-61.947,-14.089],[-86.77,-18.18],[-91.366,5.401],[-32.023,101.057],[23.78,131.44],[24.608,131.44],[82.294,109.836],[87.902,96.138],[95.717,22.914],[96.072,-4.672],[89.27,-19.233],[80.503,-23.052],[62.392,-9.4],[48.97,-35.463],[29.617,-19.559],[15.965,-45.622],[-3.018,-26.775],[-1.047,-87.445],[-15.664,-102.063],[-30.282,-87.445],[-32.253,27.787]],"c":true}]},{"i":{"x":0.699,"y":1},"o":{"x":0.467,"y":0},"t":31,"s":[{"i":[[0,0],[0,0],[7.999,-5.792],[-4.873,-7.86],[0,0],[-20.501,0.092],[0,0],[-16.502,24.179],[-1.057,4.827],[-0.874,36.819],[0.166,9.493],[4.332,3.554],[3.664,0.488],[0,0],[18.156,2.712],[0,0],[18.111,2.483],[2.62,-21.559],[0,0],[8.044,0],[0,-8.045],[0,0]],"o":[[0,0],[-5.7,-8.044],[-7.492,5.424],[0,0],[11.951,19.26],[0,0],[0,0],[2.804,-4.091],[2.253,-10.572],[0,0],[-0.098,-5.603],[-2.223,-1.823],[-11.4,-1.517],[0,0],[-18.157,-2.712],[0,0],[0,0],[0,0],[-1.359,-7.89],[-8.09,0],[0,0],[0,0]],"v":[[-32.299,27.832],[-61.947,-14.089],[-86.77,-18.18],[-91.366,5.401],[-32.023,101.057],[23.78,131.44],[24.608,131.44],[82.294,109.836],[87.902,96.138],[95.717,22.914],[96.072,-4.672],[89.27,-19.233],[80.503,-23.052],[62.392,-9.4],[48.97,-35.463],[29.617,-19.559],[15.965,-45.622],[-3.018,-26.775],[-24.047,-97.843],[-38.664,-112.461],[-53.282,-97.843],[-32.253,27.787]],"c":true}]},{"t":41,"s":[{"i":[[0,0],[0,0],[7.999,-5.792],[-4.873,-7.86],[0,0],[-20.501,0.092],[0,0],[-16.502,24.179],[-1.057,4.827],[-0.874,36.819],[0.166,9.493],[4.332,3.554],[3.664,0.488],[0,0],[18.156,2.712],[0,0],[18.111,2.483],[2.62,-21.559],[0,0],[8.044,0],[0,-8.045],[0,0]],"o":[[0,0],[-5.7,-8.044],[-7.492,5.424],[0,0],[11.951,19.26],[0,0],[0,0],[2.804,-4.091],[2.253,-10.572],[0,0],[-0.098,-5.603],[-2.223,-1.823],[-11.4,-1.517],[0,0],[-18.157,-2.712],[0,0],[0,0],[0,0],[0,-8.091],[-8.09,0],[0,0],[0,0]],"v":[[-32.299,27.832],[-61.947,-14.089],[-86.77,-18.18],[-91.366,5.401],[-32.023,101.057],[23.78,131.44],[24.608,131.44],[82.294,109.836],[87.902,96.138],[95.717,22.914],[96.072,-4.672],[89.27,-19.233],[80.503,-23.052],[62.392,-9.4],[48.97,-35.463],[29.617,-19.559],[15.965,-45.622],[-3.018,-26.775],[-3.018,-107.397],[-17.635,-122.015],[-32.253,-107.397],[-32.253,27.787]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9.965,"ix":5},"lc":1,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[106.203,143.98],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":150,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Arrow pointer","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":28,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":31,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":49,"s":[100]},{"t":52,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[100.5,95,0],"ix":2,"l":2},"a":{"a":0,"k":[24.965,37.465,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[15,-27.5],[-15,-2.5],[15,27.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":28,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":31,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":49,"s":[0]},{"t":52,"s":[100]}],"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9.965,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[24.965,37.465],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":150,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Arrow line","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.825,"y":1},"o":{"x":0.423,"y":0},"t":12,"s":[272,92.5,0],"to":[0,0,0],"ti":[0,0,0]},{"t":37,"s":[248,92.5,0]}],"ix":2,"l":2},"a":{"a":0,"k":[139.982,4.982,0],"ix":1,"l":2},"s":{"a":0,"k":[107.593,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[274.982,4.982],[-6.171,4.982]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.46],"y":[1]},"o":{"x":[0.103],"y":[0]},"t":37,"s":[0]},{"t":52,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.71],"y":[1]},"o":{"x":[0.658],"y":[0]},"t":11,"s":[0]},{"t":28.5947265625,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9.965,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":11,"op":150,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"Swipe icon for animation 5x","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[62.5,62.5,0],"ix":2,"l":2},"a":{"a":0,"k":[250,250,0],"ix":1,"l":2},"s":{"a":0,"k":[25,25,100],"ix":6,"l":2}},"ao":0,"w":500,"h":500,"ip":0,"op":150,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/src/common/Constants.ts b/src/common/Constants.ts index ad328041..4e9fe847 100644 --- a/src/common/Constants.ts +++ b/src/common/Constants.ts @@ -1346,27 +1346,27 @@ export const CLICKSTREAM_EVENT_NAMES = { }, FA_UNSYNC_FEEDBACK_CAPTURED: { name: 'FA_UNSYNC_FEEDBACK_CAPTURED', - description: 'Unsync feedback captured' + description: 'Unsync feedback captured', }, FA_UNSYNC_FEEDBACK_CAPTURING: { name: 'FA_UNSYNC_FEEDBACK_CAPTURING', - description: 'Feedback capturing' + description: 'Feedback capturing', }, FA_UNSYNC_FEEDBACK_CAPTURE_SUCCESS: { name: 'FA_UNSYNC_FEEDBACK_CAPTURE_SUCCESS', - description: 'Feedback capture success' + description: 'Feedback capture success', }, FA_FEEDBACK_SUCCESS: { name: 'FA_FEEDBACK_SUCCESS', - description: 'Feedback capture success' + description: 'Feedback capture success', }, FA_ORIGINAL_IMAGE_UPLOADING: { name: 'FA_ORIGINAL_IMAGE_UPLOADING', - description: 'Original image uploading' + description: 'Original image uploading', }, FA_WRONG_QUESTION_KEY: { name: 'FA_WRONG_QUESTION_KEY', - description: 'Wrong question key' + description: 'Wrong question key', }, FA_API_FAILED: { name: 'FA_API_FAILED', @@ -1495,7 +1495,45 @@ export const CLICKSTREAM_EVENT_NAMES = { FA_COSMOS_CONNECTED_CALL_EVENTS: { name: 'FA_COSMOS_CONNECTED_CALL_EVENTS', description: 'Cosmos connected call events', - } + }, + + // Top Addresses + FA_VIEW_TOP_ADDRESSES_CLICKED: { + name: 'FA_VIEW_TOP_ADDRESSES_CLICKED', + description: 'View top addresses clicked', + }, + FA_TOP_ADDRESSES_SCREEN_LOADED: { + name: 'FA_TOP_ADDRESSES_SCREEN_LOADED', + description: 'Top addresses screen loaded', + }, + FA_DIRECTIONS_BUTTON_CLICKED: { + name: 'FA_DIRECTIONS_BUTTON_CLICKED', + description: 'Directions button clicked', + }, + FA_OPEN_MAP_BUTTON_CLICKED: { + name: 'FA_OPEN_MAP_BUTTON_CLICKED', + description: 'Open map button clicked', + }, + FA_VIEW_SIMILAR_ADDRESSES_BUTTON_CLICKED: { + name: 'FA_VIEW_SIMILAR_ADDRESSES_BUTTON_CLICKED', + description: 'View similar addresses button clicked', + }, + FA_VIEW_OTHER_ADDRESSES_CLICKED: { + name: 'FA_VIEW_OTHER_ADDRESSES_CLICKED', + description: 'View other addresses clicked', + }, + FA_OTHER_ADDRESSES_SCREEN_LOADED: { + name: 'FA_OTHER_ADDRESSES_SCREEN_LOADED', + description: 'Top addresses screen loaded', + }, + FA_CAROUSEL_ADDRESS_PAGINATION_CLICKED: { + name: 'FA_CAROUSEL_ADDRESS_PAGINATION_CLICKED', + description: 'Carousel address pagination clicked', + }, + FA_CAROUSEL_ADDRESS_SWIPE_GESTURE: { + name: 'FA_CAROUSEL_ADDRESS_SWIPE_GESTURE', + description: 'Carousel address swipe gesture', + }, } as const; export enum MimeType { @@ -1635,7 +1673,6 @@ export const HIT_SLOP = { right: 8, }; - export const SELF_CALL_SHARED_PREFERENCE_KEY = 'selfCallSharedPreference'; export const LITMUS_URL = 'https://longhorn.navi.com/litmus'; diff --git a/src/common/ModalWrapperForAlfredV2.tsx b/src/common/ModalWrapperForAlfredV2.tsx index 6e85cc3c..ce40973e 100644 --- a/src/common/ModalWrapperForAlfredV2.tsx +++ b/src/common/ModalWrapperForAlfredV2.tsx @@ -1,11 +1,7 @@ -import React, { useEffect } from 'react'; +import React from 'react'; import { Modal, NativeSyntheticEvent, View, findNodeHandle } from 'react-native'; import { IModalWrapper } from '../../RN-UI-LIB/src/components/modalWrapper/ModalWrapper'; -import { - clearBottomSheet, - sendBottomSheetOpenSignal, - setBottomSheetView, -} from '../components/utlis/DeviceUtils'; +import { clearBottomSheet, setBottomSheetView } from '../components/utlis/DeviceUtils'; import { GenericStyles } from '@rn-ui-lib/styles'; const ModalWrapperForAlfredV2: React.FC = ({ children, ...props }) => { diff --git a/src/common/ShadowLine.tsx b/src/common/ShadowLine.tsx new file mode 100644 index 00000000..0d0a1c06 --- /dev/null +++ b/src/common/ShadowLine.tsx @@ -0,0 +1,41 @@ +import { StyleSheet, Text, View } from 'react-native'; +import React from 'react'; + +const ShadowLine = () => { + return ( + <> + + + + + ); +}; + +const styles = StyleSheet.create({ + line1: { + position: 'absolute', + top: -1, + left: 0, + right: 0, + height: 1, + backgroundColor: 'rgba(0,0,0,0.05)', + }, + line2: { + position: 'absolute', + top: -2, + left: 0, + right: 0, + height: 1, + backgroundColor: 'rgba(0,0,0,0.025)', + }, + line3: { + position: 'absolute', + top: -3, + left: 0, + right: 0, + height: 1, + backgroundColor: 'rgba(0,0,0,0.0125)', + }, +}); + +export default ShadowLine; diff --git a/src/components/Tour/components/CopilotStep.tsx b/src/components/Tour/components/CopilotStep.tsx index c9e8276a..4c2206e9 100644 --- a/src/components/Tour/components/CopilotStep.tsx +++ b/src/components/Tour/components/CopilotStep.tsx @@ -8,6 +8,7 @@ export const CopilotStep = ({ name, order, text, + subText, children, active = true, width, @@ -63,6 +64,7 @@ export const CopilotStep = ({ registerStep({ name, text, + subText, order, measure, wrapperRef, diff --git a/src/components/Tour/components/Tooltip.tsx b/src/components/Tour/components/Tooltip.tsx index 41d77c42..921e82ef 100644 --- a/src/components/Tour/components/Tooltip.tsx +++ b/src/components/Tour/components/Tooltip.tsx @@ -54,6 +54,11 @@ export const Tooltip: React.FC = ({ labels }) => { {currentStep?.text} + {currentStep?.subText ? ( + + {currentStep?.subText} + + ) : null} ; measure: () => Promise; text: ReactNode; + subText?: ReactNode; isCircularHighlight?: boolean; noHighlightArea?: boolean; } @@ -105,6 +106,7 @@ export interface CopilotStepProps { name: string; order: number; text: ReactNode; + subText?: ReactNode; children: React.ReactElement; active?: boolean; width?: number; diff --git a/src/components/carousel/Carousel.tsx b/src/components/carousel/Carousel.tsx new file mode 100644 index 00000000..db14d2f5 --- /dev/null +++ b/src/components/carousel/Carousel.tsx @@ -0,0 +1,93 @@ +import React from 'react'; +import { FlatList, View } from 'react-native'; +import CarouselItem from './CarouselItem'; +import Animated, { useAnimatedScrollHandler, useSharedValue } from 'react-native-reanimated'; +import { GenericStyles, SCREEN_WIDTH } from '@rn-ui-lib/styles'; +import Pagination from './Pagination'; +import { ICarousel } from './interfaces'; +import { GenericType } from '@common/GenericTypes'; +import { addClickstreamEvent } from '@services/clickstreamEventService'; +import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants'; + +const Carousel: React.FC = ({ + data, + gapBetweenItems, + sideItemScale, + horizontalPadding, + renderItem, + isLoading, +}) => { + const scrollX = useSharedValue(0); + const canMomentum = React.useRef(false); + const [activePageNumber, setActivePageNumber] = React.useState(1); + const flatListRef = React.useRef>(null); + + const onScrollHandler = useAnimatedScrollHandler({ + onScroll: (event) => { + scrollX.value = event.contentOffset.x; + }, + }); + + const scrollTo = (index: number) => { + flatListRef.current?.scrollToOffset({ + offset: index * SCREEN_WIDTH, + animated: true, + }); + setActivePageNumber(index + 1); + }; + + const handleMomentumScrollBegin = () => { + canMomentum.current = true; + }; + + const handleMomentumScrollEnd = () => { + if (canMomentum.current) { + const index = Math.round(scrollX.value / SCREEN_WIDTH); + addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_CAROUSEL_ADDRESS_SWIPE_GESTURE, { + page: index, + }); + setActivePageNumber(index + 1); + } + + canMomentum.current = false; + }; + + return ( + + ( + + )} + horizontal + scrollEnabled={!isLoading} + pagingEnabled + showsHorizontalScrollIndicator={false} + onScroll={onScrollHandler} + removeClippedSubviews={false} + onMomentumScrollBegin={handleMomentumScrollBegin} + onMomentumScrollEnd={handleMomentumScrollEnd} + /> + + + + + ); +}; + +export default Carousel; diff --git a/src/components/carousel/CarouselItem.tsx b/src/components/carousel/CarouselItem.tsx new file mode 100644 index 00000000..38ad2714 --- /dev/null +++ b/src/components/carousel/CarouselItem.tsx @@ -0,0 +1,62 @@ +import { StyleSheet, View } from 'react-native'; +import React from 'react'; +import { SCREEN_WIDTH } from '@rn-ui-lib/styles'; +import Animated, { Extrapolation, interpolate, useAnimatedStyle } from 'react-native-reanimated'; +import { COLORS } from '@rn-ui-lib/colors'; +import { GAP_BETWEEN_ITEMS, HORIZONTAL_PADDING, SIDE_ITEM_SCALE } from './constants'; +import { ICarouselItem } from './interfaces'; + +const CarouselItem: React.FC = ({ + item, + scrollX, + index, + horizontalPadding = HORIZONTAL_PADDING, + gapBetweenItems = GAP_BETWEEN_ITEMS, + sideItemScale = SIDE_ITEM_SCALE, + renderItem, +}) => { + const TRANSLATE_X = horizontalPadding + gapBetweenItems + ((1 - 0.8) / 2) * SCREEN_WIDTH; + const animatedStyle = useAnimatedStyle(() => { + return { + transform: [ + { + translateX: interpolate( + scrollX.value, + [(index - 1) * SCREEN_WIDTH, index * SCREEN_WIDTH, (index + 1) * SCREEN_WIDTH], + [-TRANSLATE_X, 0, TRANSLATE_X], + Extrapolation.CLAMP + ), + }, + { + scale: interpolate( + scrollX.value, + [(index - 1) * SCREEN_WIDTH, index * SCREEN_WIDTH, (index + 1) * SCREEN_WIDTH], + [sideItemScale, 1, sideItemScale], + Extrapolation.CLAMP + ), + }, + ], + }; + }); + + return ( + + {renderItem({ item, index })} + + ); +}; +const styles = StyleSheet.create({ + container: { + paddingVertical: 16, + paddingHorizontal: HORIZONTAL_PADDING, + width: SCREEN_WIDTH, + }, + listItem: { + height: '100%', + borderRadius: 20, + elevation: 6, + backgroundColor: COLORS.BACKGROUND.PRIMARY, + }, +}); + +export default CarouselItem; diff --git a/src/components/carousel/Pagination.tsx b/src/components/carousel/Pagination.tsx new file mode 100644 index 00000000..9971da44 --- /dev/null +++ b/src/components/carousel/Pagination.tsx @@ -0,0 +1,37 @@ +import { StyleSheet, View } from 'react-native'; +import { IPagination } from './interfaces'; +import PaginationItem from './PaginationItem'; +import { CopilotStep } from '@components/Tour/components/CopilotStep'; + +const Pagination: React.FC = ({ data, activePageNumber, scrollTo, isLoading }) => { + if (!data?.length) return null; + return ( + + + {data?.map((_, index) => ( + + ))} + + + ); +}; + +const styles = StyleSheet.create({ + pagination: { + flexDirection: 'row', + alignItems: 'center', + borderRadius: 20, + padding: 8, + backgroundColor: 'rgba(50, 70, 91, 0.40)', + }, +}); + +export default Pagination; diff --git a/src/components/carousel/PaginationItem.tsx b/src/components/carousel/PaginationItem.tsx new file mode 100644 index 00000000..8f7fdefe --- /dev/null +++ b/src/components/carousel/PaginationItem.tsx @@ -0,0 +1,83 @@ +import React from 'react'; +import { Pressable, StyleSheet, View } from 'react-native'; +import { IPaginationItem } from './interfaces'; +import { COLORS } from '@rn-ui-lib/colors'; +import Text from '@rn-ui-lib/components/Text'; +import TickSmallSolidIcon from '@assets/icons/TickSmallSolidIcon'; +import { addClickstreamEvent } from '@services/clickstreamEventService'; +import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants'; + +const PaginationItem: React.FC = ({ + index, + activePageNumber, + isLastItem, + scrollTo, + isVisited, + isLoading, +}) => { + const pageNumber = index + 1; + const isActivePage = activePageNumber === pageNumber; + + return ( + { + if (isLoading) return; + addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_CAROUSEL_ADDRESS_PAGINATION_CLICKED, { + page: index, + }); + scrollTo(index); + }} + style={isLoading && styles.opacity70} + hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }} + > + + + {isLastItem ? 'Others' : pageNumber} + + {isVisited ? ( + + + + ) : null} + + + ); +}; + +const styles = StyleSheet.create({ + numberCTA: { + justifyContent: 'center', + alignItems: 'center', + marginRight: 12, + }, + paginationContainer: { + position: 'absolute', + top: 14, + left: 10, + }, + activeNumberText: { + backgroundColor: COLORS.BACKGROUND.PRIMARY, + color: COLORS.TEXT.DARK, + borderColor: COLORS.BORDER.PRIMARY, + }, + numberText: { + fontSize: 13, + color: COLORS.TEXT.WHITE, + paddingHorizontal: 5, + height: 20, + textAlign: 'center', + textAlignVertical: 'center', + lineHeight: 16, + borderRadius: 10, + borderWidth: 1, + borderColor: 'transparent', + }, + mr0: { + marginRight: 0, + }, + opacity70: { + opacity: 0.7, + }, +}); + +export default PaginationItem; diff --git a/src/components/carousel/constants.ts b/src/components/carousel/constants.ts new file mode 100644 index 00000000..241ce99a --- /dev/null +++ b/src/components/carousel/constants.ts @@ -0,0 +1,3 @@ +export const HORIZONTAL_PADDING = 30; +export const SIDE_ITEM_SCALE = 0.8; +export const GAP_BETWEEN_ITEMS = 12; diff --git a/src/components/carousel/interfaces.ts b/src/components/carousel/interfaces.ts new file mode 100644 index 00000000..f4385187 --- /dev/null +++ b/src/components/carousel/interfaces.ts @@ -0,0 +1,40 @@ +import { GenericType } from '@common/GenericTypes'; +import { SharedValue } from 'react-native-reanimated'; + +type RenderItem = (data: { item: GenericType; index: number }) => React.ReactNode; + +export interface ICarouselItem { + item: GenericType; + scrollX: SharedValue; + index: number; + renderItem: RenderItem; + horizontalPadding?: number; + sideItemScale?: number; + gapBetweenItems?: number; +} + +export interface ICarousel { + data: GenericType[]; + horizontalPadding?: number; + gapBetweenItems?: number; + sideItemScale?: number; + renderItem: RenderItem; + isLoading: boolean; +} + +export interface IPagination { + data: GenericType[]; + scrollX: SharedValue; + activePageNumber: number; + scrollTo: (index: number) => void; + isLoading: boolean; +} + +export interface IPaginationItem { + index: number; + activePageNumber: number; + isLastItem: boolean; + scrollTo: (index: number) => void; + isVisited: boolean; + isLoading: boolean; +} diff --git a/src/components/form/AnswerRender.tsx b/src/components/form/AnswerRender.tsx index 8a7f9f0e..d042df7d 100644 --- a/src/components/form/AnswerRender.tsx +++ b/src/components/form/AnswerRender.tsx @@ -7,13 +7,12 @@ import { GenericStyles, SCREEN_WIDTH } from '../../../RN-UI-LIB/src/styles'; import { GenericObject, GenericType } from '../../common/GenericTypes'; import StarRating from '../../../RN-UI-LIB/src/components/star_rating/StarRating'; import { getPhoneNumberString, memoize } from '../utlis/commonFunctions'; -import { - getImageHeightWrtAspectRatio, -} from '../../services/casePayload.transformer'; +import { getImageHeightWrtAspectRatio } from '../../services/casePayload.transformer'; import { formatAmount } from '../../../RN-UI-LIB/src/utlis/amount'; -import GeolocationAddressAnswer from './components/GeolocationAddressAnswer'; import dayjs from 'dayjs'; import { BUSINESS_DATE_FORMAT, CUSTOM_ISO_DATE_FORMAT } from '@rn-ui-lib/utils/dates'; +import AddressAnswer from './components/AddressAnswer'; +import { CaseAllocationType } from '@screens/allCases/interface'; const RATING_COMPONENT = 'Rating'; const MAX_RATING = 5; @@ -51,7 +50,9 @@ const DarkBoldText = ({ text }: IDarkBoldText) => { const AnswerRender: React.FC = (props) => { const { answer, visited, section, caseId, journey, metaData, questionId } = props; const data = useAppSelector((state) => state.case.caseForm?.[caseId]?.[journey]); - const caseType = useAppSelector((state) => state.allCases.caseDetails[caseId]?.caseType); + const caseType = useAppSelector( + (state) => state.allCases.caseDetails[caseId]?.caseType || CaseAllocationType.COLLECTION_CASE + ); const templateData = useAppSelector((state) => state.case.templateData[caseType]); const mobileNumbers = useAppSelector((state) => state?.telephoneNumbers?.telephoneNumbers?.[caseId]) || []; @@ -122,7 +123,7 @@ const AnswerRender: React.FC = (props) => { } return ; case AnswerType.image: - if(!imageDoc?.fileUri) return null; + if (!imageDoc?.fileUri) return null; return ( = (props) => { > ); case AnswerType.address: - return ( - - ); + return ; case AnswerType.phoneNumber: return ; case AnswerType.date: diff --git a/src/components/form/Submit.tsx b/src/components/form/Submit.tsx index 10df3b0f..30c73171 100644 --- a/src/components/form/Submit.tsx +++ b/src/components/form/Submit.tsx @@ -35,7 +35,7 @@ const Submit: React.FC = (props) => { const data = useAppSelector((state) => state.case.caseForm?.[caseId]?.[journey]); const caseType = useAppSelector( (state) => - state.allCases.caseDetails[caseId]?.caseType || CaseAllocationType.ADDRESS_VERIFICATION_CASE + state.allCases.caseDetails[caseId]?.caseType || CaseAllocationType.COLLECTION_CASE ); const templateData = useAppSelector((state) => state.case.templateData[caseType]); const sections = templateData?.sections; diff --git a/src/components/form/components/AddressAnswer.tsx b/src/components/form/components/AddressAnswer.tsx new file mode 100644 index 00000000..4e9e46d5 --- /dev/null +++ b/src/components/form/components/AddressAnswer.tsx @@ -0,0 +1,31 @@ +import React, { useMemo } from 'react'; +import { useAppSelector } from '../../../hooks'; +import Text from '../../../../RN-UI-LIB/src/components/Text'; +import { RootState } from '../../../store/store'; +import { GenericStyles } from '@rn-ui-lib/styles'; +import { View } from 'react-native'; + +interface IAddressAnswer { + caseId: string; + addressId: string; +} + +const AddressAnswer: React.FC = ({ caseId, addressId }) => { + const feedbackAddresses = useAppSelector( + (state: RootState) => state.topAddresses?.[caseId]?.feedbackAddresses + ); + const address = useMemo(() => { + return feedbackAddresses?.find((address) => address.referenceId === addressId); + }, [feedbackAddresses, addressId]); + + return ( + + + {[address?.pinCode, address?.city].filter(Boolean).join(', ') || '-'} + + {address?.addressText?.trim()} + + ); +}; + +export default AddressAnswer; diff --git a/src/components/form/components/AddressSelection/AddressSelectionBody.tsx b/src/components/form/components/AddressSelection/AddressSelectionBody.tsx new file mode 100644 index 00000000..a2be127e --- /dev/null +++ b/src/components/form/components/AddressSelection/AddressSelectionBody.tsx @@ -0,0 +1,123 @@ +import { getAddressString } from '@components/utlis/commonFunctions'; +import Text from '@rn-ui-lib/components/Text'; +import RadioGroup from '@rn-ui-lib/components/radio_button/RadioGroup'; +import { GenericStyles } from '@rn-ui-lib/styles'; +import React from 'react'; +import RNRadioButton from '@rn-ui-lib/components/radio_button/RadioButton'; +import { TouchableOpacity, View } from 'react-native'; +import { useAppDispatch, useAppSelector } from '@hooks'; +import { RootState } from '@store'; +import { addClickstreamEvent } from '@services/clickstreamEventService'; +import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants'; +import { AnswerType } from '@components/form/interface'; +import LineLoader from '@rn-ui-lib/components/suspense_loader/LineLoader'; +import LoadingIcon from '@rn-ui-lib/icons/LoadingIcon'; +import { COLORS } from '@rn-ui-lib/colors'; +import NoLocationsIcon from '@rn-ui-lib/icons/NoLocationIcon'; +import { getFeedbackAddresses } from '@screens/addresses/actions'; +import { IAddressSelectionBody } from './interface'; + +const AddressSelectionBody = (props: IAddressSelectionBody) => { + const { caseId, questionId, error, widgetId, sectionId, control, questionType, value, onChange } = + props; + + const feedbackAddresses = useAppSelector( + (state: RootState) => state.topAddresses?.[caseId]?.feedbackAddresses + ); + const isFeedbackAddressesLoading = useAppSelector( + (state: RootState) => state.topAddresses?.[caseId]?.isFeedbackAddressesLoading + ); + + const dispatch = useAppDispatch(); + + const handleChange = (change: string) => { + addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.AV_FORM_ELEMENT_CHANGED, { + caseId, + questionType, + value: change, + questionId, + error, + sectionId, + widgetId, + }); + onChange({ + answer: change, + type: AnswerType.address, + }); + }; + + const getAddresses = () => { + dispatch(getFeedbackAddresses(caseId)); + }; + + if (isFeedbackAddressesLoading) { + return ( + + {[...Array(7).keys()].map((_, index) => ( + + ))} + + ); + } + + if (!feedbackAddresses?.length) { + return ( + + + + + No addresses found + + + + + + + Retry + + + ); + } + + return ( + + {feedbackAddresses?.map((address) => { + return ( + + + {[address.pinCode, address.city].filter(Boolean).join(', ') || '-'} + + {address?.addressText?.trim()} + + } + containerStyle={GenericStyles.containerStyle} + /> + ); + })} + + ); +}; + +export default AddressSelectionBody; diff --git a/src/components/form/components/AddressSelection/AddressSelectionV2.tsx b/src/components/form/components/AddressSelection/AddressSelectionV2.tsx new file mode 100644 index 00000000..1f34a13b --- /dev/null +++ b/src/components/form/components/AddressSelection/AddressSelectionV2.tsx @@ -0,0 +1,47 @@ +import { View } from 'react-native'; +import React from 'react'; +import { useAppSelector } from '../../../../hooks'; +import { CaseAllocationType } from '../../../../screens/allCases/interface'; +import { isQuestionMandatory, validateInput } from '../../services/validation.service'; +import { Control, Controller } from 'react-hook-form'; +import ErrorMessage from '../ErrorMessage'; +import { GenericStyles } from '../../../../../RN-UI-LIB/src/styles'; +import Text from '@rn-ui-lib/components/Text'; +import AddressSelectionBody from './AddressSelectionBody'; +import { IAddressSelectionV2 } from './interface'; + +const AddressSelectionV2: React.FC = (props) => { + const { questionId, error, widgetId, sectionId, control } = props; + const template = useAppSelector( + (state) => state.case.templateData[CaseAllocationType.COLLECTION_CASE] + ); + const question = template?.questions?.[questionId]; + const controllerName = `widgetContext.${widgetId}.sectionContext.${sectionId}.questionContext.${questionId}`; + + return ( + + + {question?.text || ' '} + {isQuestionMandatory(question) && *} + + + validateInput(data, question.metadata.validators) }} + render={({ field: { onChange, value } }) => ( + + )} + name={controllerName} + /> + + ); +}; + +export default AddressSelectionV2; diff --git a/src/components/form/components/AddressSelection/interface.ts b/src/components/form/components/AddressSelection/interface.ts new file mode 100644 index 00000000..a15169b6 --- /dev/null +++ b/src/components/form/components/AddressSelection/interface.ts @@ -0,0 +1,18 @@ +import { GenericType } from '@common/GenericTypes'; +import { Control } from 'react-hook-form'; + +export interface IAddressSelectionV2 { + questionType: string; + questionId: string; + widgetId: string; + journeyId: string; + caseId: string; + sectionId: string; + control: Control; + error: GenericType; +} + +export interface IAddressSelectionBody extends IAddressSelectionV2 { + value: GenericType; + onChange: (...event: GenericType[]) => void; +} diff --git a/src/components/form/components/PhoneNumberSelection.tsx b/src/components/form/components/PhoneNumberSelection.tsx index 92acde25..ceecb3fd 100644 --- a/src/components/form/components/PhoneNumberSelection.tsx +++ b/src/components/form/components/PhoneNumberSelection.tsx @@ -32,7 +32,7 @@ const PhoneNumberSelection: React.FC = (props) => { const { caseId, questionId, error, widgetId, sectionId, control, questionType } = props; const currentCase = useAppSelector((state) => state.allCases?.caseDetails?.[caseId]) || {}; const { mobileNumbers, loading } = useMobileNumbers(caseId); - const caseType = currentCase?.caseType || CaseAllocationType.ADDRESS_VERIFICATION_CASE; + const caseType = currentCase?.caseType || CaseAllocationType.COLLECTION_CASE; const template = useAppSelector((state) => state.case.templateData[caseType]); const question = template.questions[questionId]; const handleChange = (change: string | null, onChange: (...event: any[]) => void) => { diff --git a/src/components/form/services/formComponents.ts b/src/components/form/services/formComponents.ts index 553c17f1..360216c0 100644 --- a/src/components/form/services/formComponents.ts +++ b/src/components/form/services/formComponents.ts @@ -6,10 +6,10 @@ import Rating from '../components/Rating'; import TextArea from '../components/TextArea'; import TextInput from '../components/TextInput'; import Dropdown from '../components/Dropdown'; -import AddressSelection from '../components/AddressSelection'; import PhoneNumberSelection from '../components/PhoneNumberSelection'; import DateInput from '../components/DateInput'; import TimeInput from '../components/TimeInput'; +import AddressSelectionV2 from '../components/AddressSelection/AddressSelectionV2'; export const FormComponentList = { TextInput, @@ -20,7 +20,7 @@ export const FormComponentList = { Rating, Dropdown, CheckboxGroup, - AddressSelection, + AddressSelection: AddressSelectionV2, PhoneNumberSelection, DateInput, TimeInput, diff --git a/src/components/utlis/addressGeolocationUtils.ts b/src/components/utlis/addressGeolocationUtils.ts index a71adbcf..877c11d3 100644 --- a/src/components/utlis/addressGeolocationUtils.ts +++ b/src/components/utlis/addressGeolocationUtils.ts @@ -8,9 +8,7 @@ export const getCollectionFeedbackOnAddressPreDefinedJourney = ( isGeolocation?: boolean ) => { try { - const templateStr = isGeolocation - ? JSON.stringify(templateJson.geolocationTemplate) - : JSON.stringify(templateJson.addressTemplate); + const templateStr = JSON.stringify(templateJson.addressTemplate); const updatedStr = templateStr.replace(key, value); return JSON.parse(updatedStr); } catch (err) { diff --git a/src/components/utlis/apiHelper.ts b/src/components/utlis/apiHelper.ts index 5b9f8764..4f6b0bce 100644 --- a/src/components/utlis/apiHelper.ts +++ b/src/components/utlis/apiHelper.ts @@ -113,6 +113,8 @@ export enum ApiKeys { GET_REPAYMENTS = 'GET_REPAYMENTS', GET_FEEDBACK_HISTORY = 'GET_FEEDBACK_HISTORY', GET_PRIORTIY_FEEDBACK = 'GET_PRIORTIY_FEEDBACK', + GET_TOP_ADDRESSES = 'GET_TOP_ADDRESSES', + GET_FEEDBACK_ADDRESSES = 'GET_FEEDBACK_ADDRESSES', GET_TRAINING_MATERIAL_LIST = 'GET_TRAINING_MATERIAL_LIST', GET_TRAINING_MATERIAL_DETAILS = 'GET_TRAINING_MATERIAL_DETAILS', SELF_CALL_ACK= '/api/v1/self-call' @@ -204,7 +206,6 @@ API_URLS[ApiKeys.CALL_CUSTOMER] = '/call-recording/v2/call-request'; API_URLS[ApiKeys.SYNC_ACTIVE_CALL_DETAILS] = '/call-recording/call-status'; API_URLS[ApiKeys.GET_CALL_HISTORY] = '/call-recording/v3/call-history'; API_URLS[ApiKeys.SYNC_CALL_FEEDBACK_NUDGE_DETAILS] = - '/call-recording/acknowledge-feedback-nudge/{callId}'; API_URLS[ApiKeys.FETCH_CUSTOMER_DOCUMENTS] = '/documents/{loanAccountNumber}'; API_URLS[ApiKeys.FETCH_AGENT_DOCUMENTS] = '/documents/agent'; @@ -223,6 +224,8 @@ API_URLS[ApiKeys.GET_PRIORTIY_FEEDBACK] = '/feedback/case-status'; API_URLS[ApiKeys.GET_TRAINING_MATERIAL_LIST] = '/training-page/content-list'; API_URLS[ApiKeys.GET_TRAINING_MATERIAL_DETAILS] = '/training-page/{docRefId}'; API_URLS[ApiKeys.SELF_CALL_ACK] = '/sync-data/self-call-metadata'; +API_URLS[ApiKeys.GET_TOP_ADDRESSES] = '/collection-cases/unified-locations'; +API_URLS[ApiKeys.GET_FEEDBACK_ADDRESSES] = '/collection-cases/unified-locations/lite'; export const API_STATUS_CODE = { OK: 200, diff --git a/src/components/utlis/commonFunctions.ts b/src/components/utlis/commonFunctions.ts index c41a5860..0636dacd 100644 --- a/src/components/utlis/commonFunctions.ts +++ b/src/components/utlis/commonFunctions.ts @@ -410,6 +410,11 @@ export const getGoogleMapUrl = (latitude: string | number, longitude: string | n return `https://www.google.com/maps/search/${latitude},+${longitude}`; }; +export const getGoogleMapUrlForAddressText = (address: string) => { + if (!address) return; + return `https://www.google.com/maps/search/?api=1&query=${address}`; +}; + export const isValidAmountEntered = (value: number) => { return typeof value === 'number' && !isNaN(value); }; diff --git a/src/reducer/topAddressesSlice.ts b/src/reducer/topAddressesSlice.ts new file mode 100644 index 00000000..07ba5ca6 --- /dev/null +++ b/src/reducer/topAddressesSlice.ts @@ -0,0 +1,91 @@ +import { createSlice } from '@reduxjs/toolkit'; +import { + IFeedbackAddress, + IFeedbackAddressMap, + ILocationData, +} from '@screens/addresses/interfaces'; + +type ITopAddressesSlice = Record< + string, + { + addresses: ILocationData[]; + totalLocationEntities: number; + otherAddresses: ILocationData[]; + isTopAddressesLoading: boolean; + isOtherAddressesLoading: boolean; + feedbackAddresses: IFeedbackAddress[]; + feedbackAddressesMap: Record; + isFeedbackAddressesLoading: boolean; + } +>; + +const initialState: ITopAddressesSlice = {}; + +const TopAddressesSlice = createSlice({ + name: 'topAddresses', + initialState, + reducers: { + setTopAddresses: (state, action) => { + const { caseId, addresses = [], totalLocationEntities } = action.payload; + state[caseId] = { + ...(state?.[caseId] || {}), + addresses, + totalLocationEntities, + }; + }, + setTopAddressesLoading: (state, action) => { + const { caseId, isLoading } = action.payload; + state[caseId] = { + ...(state?.[caseId] || {}), + isTopAddressesLoading: isLoading, + }; + }, + setOtherAddresses: (state, action) => { + const { caseId, addresses = [] } = action.payload; + state[caseId] = { + ...(state?.[caseId] || {}), + otherAddresses: addresses, + }; + }, + setOtherAddressesLoading: (state, action) => { + const { caseId, isLoading } = action.payload; + state[caseId] = { + ...(state?.[caseId] || {}), + isOtherAddressesLoading: isLoading, + }; + }, + setFeedbackAddresses: (state, action) => { + const { caseId, addresses = [] } = action.payload; + const feedbackAddressesMap = addresses?.reduce( + (acc: Record, address: IFeedbackAddress) => { + acc[address.referenceId] = { locationType: address?.locationType }; + return acc; + }, + {} + ); + state[caseId] = { + ...(state?.[caseId] || {}), + feedbackAddresses: addresses, + feedbackAddressesMap, + }; + }, + setFeedbackAddressesLoading: (state, action) => { + const { caseId, isLoading } = action.payload; + state[caseId] = { + ...(state?.[caseId] || {}), + isFeedbackAddressesLoading: isLoading, + }; + }, + }, +}); + +export const { + setTopAddresses, + setTopAddressesLoading, + setOtherAddresses, + setOtherAddressesLoading, + setFeedbackAddresses, + setFeedbackAddressesLoading, +} = TopAddressesSlice.actions; + +export default TopAddressesSlice.reducer; diff --git a/src/screens/addresses/actions.ts b/src/screens/addresses/actions.ts new file mode 100644 index 00000000..85e1b641 --- /dev/null +++ b/src/screens/addresses/actions.ts @@ -0,0 +1,87 @@ +import axiosInstance, { ApiKeys, getApiUrl } from '@components/utlis/apiHelper'; +import { + setFeedbackAddresses, + setFeedbackAddressesLoading, + setOtherAddresses, + setOtherAddressesLoading, + setTopAddresses, + setTopAddressesLoading, +} from '@reducers/topAddressesSlice'; +import { AppDispatch } from '@store'; +import { PAGE_END } from './constants'; + +export const getTopAddresses = + (caseId: string, start: number, end: number) => (dispatch: AppDispatch) => { + dispatch(setTopAddressesLoading({ caseId, isLoading: true })); + const url = getApiUrl(ApiKeys.GET_TOP_ADDRESSES); + axiosInstance + .get(url, { + params: { caseReferenceId: caseId, startingRank: start, endingRank: end }, + }) + .then((res) => { + if (res?.data) { + const { unifiedLocations = [], totalLocationEntities = 0 } = res?.data || {}; + dispatch( + setTopAddresses({ + caseId, + addresses: unifiedLocations, + totalLocationEntities: totalLocationEntities - 5, + }) + ); + } + }) + .catch((error) => {}) + .finally(() => { + dispatch(setTopAddressesLoading({ caseId, isLoading: false })); + }); + }; + +export const getOtherAddresses = (caseId: string) => (dispatch: AppDispatch) => { + dispatch(setOtherAddressesLoading({ caseId, isLoading: true })); + const url = getApiUrl(ApiKeys.GET_TOP_ADDRESSES); + axiosInstance + .get(url, { + params: { + caseReferenceId: caseId, + startingRank: PAGE_END + 1, + }, + }) + .then((res) => { + if (res?.data) { + const { unifiedLocations = [] } = res?.data || {}; + dispatch( + setOtherAddresses({ + caseId, + addresses: unifiedLocations, + }) + ); + } + }) + .finally(() => { + dispatch(setOtherAddressesLoading({ caseId, isLoading: false })); + }); +}; + +export const getFeedbackAddresses = (caseId: string) => (dispatch: AppDispatch) => { + dispatch(setFeedbackAddressesLoading({ caseId, isLoading: true })); + const url = getApiUrl(ApiKeys.GET_FEEDBACK_ADDRESSES); + axiosInstance + .get(url, { + params: { + caseReferenceId: caseId, + }, + }) + .then((res) => { + if (res?.data) { + dispatch( + setFeedbackAddresses({ + caseId, + addresses: res.data || [], + }) + ); + } + }) + .finally(() => { + dispatch(setFeedbackAddressesLoading({ caseId, isLoading: false })); + }); +}; diff --git a/src/screens/addresses/common/AddressItemAddressString.tsx b/src/screens/addresses/common/AddressItemAddressString.tsx new file mode 100644 index 00000000..13f49287 --- /dev/null +++ b/src/screens/addresses/common/AddressItemAddressString.tsx @@ -0,0 +1,93 @@ +import { Linking, Pressable, StyleSheet, View } from 'react-native'; +import React from 'react'; +import Text from '@rn-ui-lib/components/Text'; +import { ITopAddressItemAddressString, LocationType } from '../interfaces'; +import LollipopIcon from '@assets/icons/LollipopIcon'; +import { GenericStyles } from '@rn-ui-lib/styles'; +import { copyAddressToClipboard } from '@screens/addressGeolocation/utils/copyAddressText'; +import MapDirectionIcon from '@assets/icons/MapDirectionIcon'; +import { getGoogleMapUrl, getGoogleMapUrlForAddressText } from '@components/utlis/commonFunctions'; +import CopyOutlineIcon from '@assets/icons/CopyOutlineIcon'; +import { addClickstreamEvent } from '@services/clickstreamEventService'; +import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants'; + +const AddressItemAddressString = (props: ITopAddressItemAddressString) => { + const { locationDetails, showMapIcon = true, caseId, actionContainerStyle } = props; + const { addressText, latitude, longitude, locationSubType } = locationDetails || {}; + + const handleCopyAddress = () => { + addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_COPY_ADDRESS_CLICKED, { + caseId, + addressText, + }); + copyAddressToClipboard(addressText); + }; + + const handleOpenLocation = () => { + const isGeoLocation = + locationSubType === LocationType.GEO_LOCATION || + locationSubType === LocationType.SKIP_TRACE_ADDRESS; + + addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_OPEN_MAP_BUTTON_CLICKED, { + caseId, + ...(isGeoLocation ? { latitude, longitude } : { addressText }), + locationSubType, + }); + + const mapUrl = isGeoLocation + ? getGoogleMapUrl(latitude, longitude) + : getGoogleMapUrlForAddressText(addressText); + + if (mapUrl) { + return Linking.openURL(mapUrl); + } + }; + + return ( + + + + + + {addressText} + + + + + {showMapIcon ? ( + + + + ) : null} + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + marginHorizontal: 16, + paddingTop: 8, + }, + p4: { + padding: 4, + }, + fs1: { + flexShrink: 1, + }, +}); + +export default AddressItemAddressString; diff --git a/src/screens/addresses/common/AddressItemFeedback.tsx b/src/screens/addresses/common/AddressItemFeedback.tsx new file mode 100644 index 00000000..3c915d88 --- /dev/null +++ b/src/screens/addresses/common/AddressItemFeedback.tsx @@ -0,0 +1,64 @@ +import { View } from 'react-native'; +import React from 'react'; +import { ITopAddressItemFeedback } from '../interfaces'; +import { GenericStyles } from '@rn-ui-lib/styles'; +import Text from '@rn-ui-lib/components/Text'; +import dayjs from 'dayjs'; +import { BUSINESS_DATE_FORMAT } from '@rn-ui-lib/utils/dates'; +import { getFeedbackColors } from '../utils'; + +const AddressItemFeedback = (props: ITopAddressItemFeedback) => { + const { latestFeedback } = props; + const { + latestFeedbackStatus, + latestFeedbackTimestamp, + feedbackColourCode, + feedbackBgColourCode, + promiseToPayDate, + } = latestFeedback || {}; + + const { feedbackColor, feedbackBgColor } = getFeedbackColors( + latestFeedbackStatus, + feedbackColourCode, + feedbackBgColourCode + ); + + return ( + + + Last feedback{' '} + {latestFeedbackTimestamp ? ( + + ({dayjs(latestFeedbackTimestamp).format(BUSINESS_DATE_FORMAT)}) + + ) : null} + + + {latestFeedbackStatus + ? `${latestFeedbackStatus} ${ + promiseToPayDate ? `on ${dayjs(promiseToPayDate).format(BUSINESS_DATE_FORMAT)}` : '' + }` + : 'Unvisited address'} + + + ); +}; + +export default AddressItemFeedback; diff --git a/src/screens/addresses/common/AddressItemHeader.tsx b/src/screens/addresses/common/AddressItemHeader.tsx new file mode 100644 index 00000000..fa744485 --- /dev/null +++ b/src/screens/addresses/common/AddressItemHeader.tsx @@ -0,0 +1,92 @@ +import { StyleSheet, View } from 'react-native'; +import React, { useMemo } from 'react'; +import { ITopAddressItemHeader } from '../interfaces'; +import Text from '@rn-ui-lib/components/Text'; +import { GenericStyles } from '@rn-ui-lib/styles'; +import { useAppSelector } from '@hooks'; +import { getDistanceFromLatLonInKm } from '@components/utlis/commonFunctions'; +import relativeDistanceFormatter from '@screens/addressGeolocation/utils/relativeDistanceFormatter'; +import TopAddressItemRankTag from '../topAddresses/TopAddressItemRankTag'; +import { COLORS } from '@rn-ui-lib/colors'; +import { CopilotStep } from '@components/Tour/components/CopilotStep'; + +const AddressItemHeader = (props: ITopAddressItemHeader) => { + const { locationDetails, isOtherAddressView, containerStyle, isCoachMarkVisible } = props; + const { pinCode, city, latitude, longitude, rank, visited } = locationDetails || {}; + const deviceGeolocationCoordinate = useAppSelector( + (state) => state.foregroundService?.deviceGeolocationCoordinate + ); + + const relativeDistanceBwLatLong = useMemo(() => { + const distance = getDistanceFromLatLonInKm(deviceGeolocationCoordinate, { + latitude, + longitude, + }); + return `${relativeDistanceFormatter(distance)} km`; + }, [deviceGeolocationCoordinate]); + + const addressString = useMemo(() => { + return [pinCode, city].filter(Boolean).join(', '); + }, [pinCode, city]); + + return ( + + {isOtherAddressView ? ( + + {rank} + + ) : isCoachMarkVisible ? ( + + + + + + ) : ( + + )} + + + {addressString ? addressString : '-'} + + ({relativeDistanceBwLatLong}) + + + ); +}; + +const styles = StyleSheet.create({ + container: { + marginLeft: -5, + flexDirection: 'row', + alignItems: 'center', + paddingVertical: 8, + }, + rankCircle: { + marginLeft: 21, + width: 23, + height: 23, + borderRadius: 13, + backgroundColor: COLORS.BACKGROUND.SILVER, + borderWidth: 1, + borderColor: COLORS.BORDER.PRIMARY, + textAlignVertical: 'center', + textAlign: 'center', + lineHeight: 17, + fontSize: 10, + }, + mw70: { + maxWidth: '70%', + }, +}); + +export default AddressItemHeader; diff --git a/src/screens/addresses/constants.ts b/src/screens/addresses/constants.ts new file mode 100644 index 00000000..954b717a --- /dev/null +++ b/src/screens/addresses/constants.ts @@ -0,0 +1,14 @@ +import { TagVariant } from '@rn-ui-lib/components/Tag'; + +export enum LocationSourceMap { + VKYC = 'VKYC', + DATA_SUTRAM = 'Skip Tracing', + PRIMARY = 'Allocated address', +} + +export const LocationSourceTagVariantMap: Record = { + 'ALLOCATED_ADDRESS': TagVariant.white, +}; + +export const PAGE_START = 1; +export const PAGE_END = 5; diff --git a/src/screens/addresses/interfaces.ts b/src/screens/addresses/interfaces.ts new file mode 100644 index 00000000..4bb1ab0f --- /dev/null +++ b/src/screens/addresses/interfaces.ts @@ -0,0 +1,166 @@ +import { IPredefinedAddressScreenTemplate } from '@interfaces/template.types'; +import { CaseDetailStackEnum } from '@screens/caseDetails/CaseDetailStack'; +import { StyleProp, ViewStyle } from 'react-native'; + +export interface ILabel { + label: string; + val: string; +} + +export interface ILatestFeedback { + locationReferenceId: string; + latestFeedbackStatus: string; + latestFeedbackStatusTag: string; + latestFeedbackTimestamp: number; + feedbackColourCode: string; + feedbackBgColourCode: string; + isSuspicious: boolean; + feedbackPresent: boolean; + promiseToPayDate: string; +} + +export interface ILocationData { + referenceId: string; + customerReferenceId: string; + addressText: string; + pinCode: string; + city: string; + latitude: number; + longitude: number; + visited: boolean; + rank: number; + timestamp: number; + similarLocationEntities: ISimilarAddressEntity[]; + labels: ILabel[]; + relatedLocationIds: string[]; + latestFeedback: ILatestFeedback; + locationSubType: string; +} + +export interface ILocationDetails { + locationDetails: ILocationData; +} + +export interface ITopAddressItem extends ILocationDetails { + caseId: string; + isCoachMarkVisible: boolean; +} + +export interface ITopAddressItemHeader { + locationDetails: ILocationData; + isCoachMarkVisible?: boolean; + isOtherAddressView?: boolean; + containerStyle?: StyleProp; +} + +export interface ITopAddressItemFeedback { + latestFeedback: ILatestFeedback; +} + +export interface ITopAddressItemTags { + clusterLabels: ILabel[]; +} + +export interface ITopAddressItemAddressString { + locationDetails: ILocationData; + showMapIcon?: boolean; + caseId: string; + actionContainerStyle?: StyleProp; +} + +export interface ITopAddressItemBottomActions extends ILocationDetails { + caseId: string; +} + +export interface ITopAddressItemRankTag { + rank: number; + visited: boolean; +} + +export interface ITopAddressSimilarAddresses extends ILocationDetails { + caseId: string; +} +interface ISimilarAddressEntity { + id: string; + addressText: string; + pinCode: string; + city: string; + state: string; + source: string; + order: number; + capturedAt: number; +} + +export interface ITopAddressSimilarAddressCard { + similarAddressDetails: ISimilarAddressEntity; + caseId: string; +} + +export interface IOtherAddressItem extends ILocationDetails { + caseId: string; +} + +export interface IFeedbackAddress { + referenceId: string; + addressText: string; + city: string; + pinCode: string; + rank: number; + source: string; + capturedAt: string; + locationType: string; +} + +export interface IFeedbackAddressMap { + locationType: string; +} + +export enum LocationType { + GEO_LOCATION = 'GEO_LOCATION', + SKIP_TRACE_ADDRESS = 'SKIP_TRACE_ADDRESS', +} + +export interface IOtherAddressActions extends ILocationDetails { + caseId: string; +} + +export interface IOtherAddresses { + route: { + params: { + loanAccountNumber: string; + caseId: string; + }; + }; +} + +export interface IOtherAddressList { + caseId: string; +} + +export interface ITopAddress { + route: { + params: { + loanAccountNumber: string; + caseId: string; + }; + }; +} + +export interface ITopAddressOtherAddressesItem { + caseId: string; +} + +export interface IOpenOldFeedbacks { + loanAccountNumber: string; + addressText: string; + relatedLocationIds: string[]; + referenceId: string; + caseId: string; +} + +export interface INavigateToAddFeedbackScreen { + prefilledAddressScreenTemplate: IPredefinedAddressScreenTemplate | undefined; + caseId: string; + referenceId: string; + screen: CaseDetailStackEnum; +} diff --git a/src/screens/addresses/otherAddresses/OtherAddressActions.tsx b/src/screens/addresses/otherAddresses/OtherAddressActions.tsx new file mode 100644 index 00000000..ebeed1e3 --- /dev/null +++ b/src/screens/addresses/otherAddresses/OtherAddressActions.tsx @@ -0,0 +1,108 @@ +import { StyleSheet, View } from 'react-native'; +import React from 'react'; +import { GenericStyles } from '@rn-ui-lib/styles'; +import OldFeedbacksIcon from '@assets/icons/OldFeedbacksIcon'; +import { COLORS } from '@rn-ui-lib/colors'; +import AddFeedbackIcon from '@assets/icons/AddFeedbackIcon'; +import { CaseDetailStackEnum } from '@screens/caseDetails/CaseDetailStack'; +import { useAppDispatch, useAppSelector } from '@hooks'; +import Button from '@rn-ui-lib/components/Button'; +import { CaseAllocationType } from '@screens/allCases/interface'; +import { handlePostOperativeHourActivities } from '@screens/addressGeolocation/utils/operativeHourUtils'; +import { ToastMessages } from '@screens/allCases/constants'; +import { IOtherAddressActions } from '../interfaces'; +import { navigateToAddFeedbackScreen, openOldFeedbacks } from '../utils'; + +const OtherAddressActions = (props: IOtherAddressActions) => { + const { locationDetails, caseId } = props; + const { addressText, relatedLocationIds, referenceId } = locationDetails || {}; + + const loanAccountNumber = useAppSelector( + (state) => state?.allCases?.caseDetails?.[caseId]?.loanAccountNumber + ); + const addingNewFeedbackDisabled = useAppSelector( + (state) => state?.postOperationalHourRestrictionsSlice?.postOperationalHourRestrictions + ); + const prefilledAddressScreenTemplate = useAppSelector( + (state) => + state.case.templateData[CaseAllocationType.COLLECTION_CASE]?.prefilledAddressScreenTemplate + ); + const dispatch = useAppDispatch(); + + const handleOpenOldFeedbacks = () => { + openOldFeedbacks({ + loanAccountNumber, + addressText: addressText, + relatedLocationIds: relatedLocationIds, + referenceId: referenceId, + caseId, + }); + }; + + const handleAddFeedback = () => { + dispatch( + navigateToAddFeedbackScreen({ + prefilledAddressScreenTemplate, + caseId, + referenceId, + screen: CaseDetailStackEnum.OTHER_ADDRESSES, + }) + ); + }; + + const handleDisableAddFeedback = () => { + handlePostOperativeHourActivities( + ToastMessages.DISABLE_ADD_FEEDBACK_AFTER_POST_OPERATIVE_HOURS + ); + }; + + return ( + +