From f47f87a30ecae7abf1e394d503aad743bcea99c8 Mon Sep 17 00:00:00 2001 From: Martino Cesari Tomba <60693085+forrest57@users.noreply.github.com> Date: Mon, 29 Jan 2024 10:11:22 +0100 Subject: [PATCH] chore: [IOBP-490] Removal of BPD folder (#5432) ## Short description removal of the whole BPD folder, along with some weird references here and there DEPENDS ON https://github.com/pagopa/io-app/pull/5421 ## List of changes proposed in this pull request - removed the *whole* folder - removed feature flags in the env files - removed references to BPD around the app the api definitions for the whole bonus section will be removed in a future PR ## How to test same as last PR, take a stroll around the app and make sure nothing breaks; theoretically *the user* should not notice any difference. stay on the lookout especially in: - cgn onboarding - payment method onboarding - payment flow - messages - idpay flows --------- Co-authored-by: Federico Mastrini --- img/bonus/bpd/bonus_bg.png | Bin 30683 -> 0 bytes img/bonus/bpd/bonus_preview_bg.png | Bin 14097 -> 0 bytes img/bonus/bpd/fireworks.png | Bin 3533 -> 0 bytes img/bonus/bpd/logo_BonusCashback_White.png | Bin 36049 -> 0 bytes img/bonus/bpd/logo_cashback_blue.png | Bin 9473 -> 0 bytes ts/features/bonus/bpd/api/award-period/v1.ts | 13 - ts/features/bonus/bpd/api/backendBpdClient.ts | 109 ----- ts/features/bonus/bpd/api/citizen/v1.ts | 117 ----- ts/features/bonus/bpd/api/citizen/v2.ts | 63 --- ts/features/bonus/bpd/api/common.ts | 14 - ts/features/bonus/bpd/api/patchedTypes.ts | 49 -- .../bonus/bpd/api/payment-instrument/v1.ts | 51 -- .../patchedWinningTransactionPageResource.ts | 86 ---- .../bonus/bpd/api/winning-transactions/v1.ts | 56 --- .../bonus/bpd/api/winning-transactions/v2.ts | 60 --- .../components/BaseDailyTransactionHeader.tsx | 61 --- .../bpd/components/BpdLastUpdateComponent.tsx | 71 --- .../bonus/bpd/components/BpdTestOverlay.tsx | 73 --- .../BpdTransactionSummaryComponent.tsx | 155 ------ .../BottomSheetMethodsToDelete.test.tsx | 39 -- .../bpdCardComponent/BpdCardComponent.tsx | 440 ------------------ .../BpdOptInPaymentMethodsContainer.tsx | 46 -- .../RetryAfterDeletionFailsComponent.tsx | 54 --- .../ThankYouSuccessComponent.tsx | 46 -- .../RetryAfterDeletionFailsComponent.test.ts | 52 --- .../ThankYouSuccessComponent.test.ts | 39 -- .../BottomSheetMethodsToDelete.tsx | 100 ---- .../BpdPaymentMethodToggleFactory.tsx | 27 -- .../base/BpdToggle.tsx | 54 --- .../base/PaymentMethodBpdToggle.tsx | 217 --------- .../PaymentMethodRepresentationComponent.tsx | 43 -- .../BpdChangeActivationConfirmationScreen.tsx | 110 ----- .../BpdNotActivableInformation.tsx | 66 --- .../bottomsheet/OtherChannelInformation.tsx | 58 --- .../list/PaymentMethodGroupedList.tsx | 122 ----- .../list/PaymentMethodRawList.tsx | 20 - .../superCashbackRanking/LastPositionItem.tsx | 30 -- .../superCashbackRanking/RankPositionItem.tsx | 84 ---- .../SuperCashbackHeader.tsx | 62 --- .../SuperCashbackRanking.tsx | 121 ----- .../superCashbackRanking/UserPositionItem.tsx | 33 -- .../__test__/RankPositionItem.test.tsx | 90 ---- .../BaseBpdTransactionItem.tsx | 82 ---- .../transactionItem/BpdTransactionItem.tsx | 66 --- .../__test__/BdpTransactionItem.test.tsx | 46 -- .../BpdCardsInWalletComponent.tsx | 63 --- ts/features/bonus/bpd/saga/index.ts | 132 ------ .../networking/__test__/loadBpdData.test.ts | 59 --- .../__test__/loadPeriodsAmount.test.ts | 249 ---------- .../bonus/bpd/saga/networking/amount.ts | 78 ---- .../bonus/bpd/saga/networking/index.ts | 146 ------ .../bonus/bpd/saga/networking/loadBpdData.ts | 93 ---- .../saga/networking/loadPeriodsWithInfo.ts | 123 ----- .../bpd/saga/networking/patchCitizenIban.ts | 103 ---- .../bpd/saga/networking/paymentMethod.ts | 194 -------- .../bonus/bpd/saga/networking/periods.ts | 86 ---- .../bonus/bpd/saga/networking/ranking.ts | 151 ------ .../bonus/bpd/saga/networking/transactions.ts | 31 -- .../winning-transactions/countByDay.ts | 100 ---- .../loadTransactionsRequiredData.ts | 109 ----- .../winning-transactions/transactionsPage.ts | 111 ----- .../activateBpdOnNewAddedPaymentMethods.ts | 37 -- .../bpd/saga/orchestration/insertIban.ts | 59 --- .../orchestration/onboarding/enrollToBpd.ts | 55 --- .../onboarding/startOnboarding.ts | 97 ---- .../optInDeletionChoiceHandler.test.ts | 49 -- .../optInShouldShowChoiceHandler.test.ts | 199 -------- .../optInDeletionChoiceHandler.ts | 45 -- .../optInShouldShowChoiceHandler.ts | 144 ------ .../bpd/screens/details/BpdDetailsScreen.tsx | 167 ------- .../bpd/screens/details/BpdPeriodSelector.tsx | 114 ----- .../components/bottomsheet/HowItWorks.tsx | 36 -- .../components/bottomsheet/WhyOtherCards.tsx | 57 --- .../iban/BaseIbanInformationComponent.tsx | 92 ---- .../iban/IbanInformationComponent.tsx | 76 --- .../WalletPaymentMethodBpdList.tsx | 207 -------- .../summary/BpdSummaryComponent.tsx | 91 ---- .../summary/TransactionsGraphicalSummary.tsx | 121 ----- .../summary/base/BpdBaseShadowBoxLayout.tsx | 23 - .../components/summary/base/ShadowBox.tsx | 35 -- .../ranking/RankingNotReadyBottomSheet.tsx | 28 -- .../ranking/SuperCashbackRankingSummary.tsx | 154 ------ .../textualSummary/ActiveTextualSummary.tsx | 117 ----- .../textualSummary/ClosedTextualSummary.tsx | 157 ------- .../textualSummary/InactiveTextualSummary.tsx | 26 -- .../summary/textualSummary/TextualSummary.tsx | 27 -- .../unsubscribe/UnsubscribeComponent.tsx | 23 - .../unsubscribe/UnsubscribeToBpd.tsx | 91 ---- .../details/periods/BpdActivePeriod.tsx | 38 -- .../details/periods/BpdClosedPeriod.tsx | 48 -- .../details/periods/BpdInactivePeriod.tsx | 38 -- .../details/periods/BpdPeriodDetail.tsx | 43 -- .../BpdAvailableTransactionsScreen.tsx | 339 -------------- .../BpdCashbackMilestoneComponent.tsx | 46 -- .../transaction/BpdEmptyTransactionsList.tsx | 40 -- .../transaction/BpdTransactionsScreen.tsx | 78 ---- .../details/transaction/GoToTransactions.tsx | 40 -- .../details/transaction/LoadTransactions.tsx | 28 -- .../transaction/TransactionsUnavailable.tsx | 46 -- .../detail/BpdTransactionDetailComponent.tsx | 183 -------- .../detail/BpdTransactionWarning.tsx | 63 --- .../BdpTransactionDetailComponent.test.tsx | 263 ----------- .../v2/BpdAvailableTransactionsScreenV2.tsx | 28 -- .../v2/BpdTransactionsRouterScreen.tsx | 81 ---- .../v2/TransactionsSectionList.tsx | 251 ---------- .../v2/TransactionsUnavailableV2.tsx | 84 ---- .../bpd/screens/iban/IbanCTAEditScreen.tsx | 135 ------ .../bpd/screens/iban/IbanLoadingUpsert.tsx | 60 --- .../bonus/bpd/screens/iban/MainIbanScreen.tsx | 69 --- .../iban/insertion/IbanInsertionComponent.tsx | 118 ----- .../iban/insertion/IbanInsertionScreen.tsx | 53 --- .../screens/iban/ko/IbanKOCannotVerify.tsx | 66 --- .../bpd/screens/iban/ko/IbanKONotOwned.tsx | 114 ----- .../bonus/bpd/screens/iban/ko/IbanKOWrong.tsx | 68 --- .../bonus/bpd/screens/iban/ko/IbanKoBody.tsx | 55 --- .../onboarding/BpdInformationScreen.tsx | 63 --- .../onboarding/EnrollPaymentMethodsScreen.tsx | 129 ----- .../onboarding/ErrorPaymentMethodsScreen.tsx | 74 --- .../onboarding/LoadActivateBpdScreen.tsx | 58 --- .../onboarding/LoadBpdActivationStatus.tsx | 60 --- .../NoPaymentMethodsAvailableScreen.tsx | 74 --- .../declaration/DeclarationComponent.tsx | 144 ------ .../declaration/DeclarationScreen.tsx | 30 -- ...ptInPaymentMethodsCashbackUpdateScreen.tsx | 103 ---- .../OptInPaymentMethodsChoiceScreen.tsx | 206 -------- ...mentMethodsThankYouDeleteMethodsScreen.tsx | 90 ---- ...aymentMethodsThankYouKeepMethodsScreen.tsx | 67 --- .../bonus/bpd/store/actions/details.ts | 35 -- ts/features/bonus/bpd/store/actions/iban.ts | 54 --- ts/features/bonus/bpd/store/actions/index.ts | 18 - .../bonus/bpd/store/actions/onboarding.ts | 99 ---- .../bpd/store/actions/optInPaymentMethods.ts | 61 --- .../bonus/bpd/store/actions/paymentMethods.ts | 65 --- .../bonus/bpd/store/actions/periods.ts | 52 --- .../bonus/bpd/store/actions/selectedPeriod.ts | 10 - .../bonus/bpd/store/actions/transactions.ts | 131 ------ .../bpd/store/reducers/__mock__/amount.ts | 26 -- .../bpd/store/reducers/__mock__/bancomat.ts | 27 -- .../bpd/store/reducers/__mock__/periods.ts | 52 --- .../bpd/store/reducers/__mock__/ranking.ts | 17 - .../store/reducers/__test__/combiner.test.ts | 342 -------------- .../__test__/payoffInstruments.test.ts | 140 ------ .../details/__test__/paymentMethods.test.ts | 109 ----- .../reducers/details/activation/index.ts | 233 ---------- .../details/activation/payoffInstrument.ts | 130 ------ .../details/activation/technicalAccount.ts | 65 --- .../store/reducers/details/activation/ui.ts | 47 -- .../bpd/store/reducers/details/combiner.ts | 211 --------- .../bonus/bpd/store/reducers/details/index.ts | 45 -- .../bpd/store/reducers/details/lastUpdate.ts | 27 -- .../store/reducers/details/paymentMethods.ts | 155 ------ .../bpd/store/reducers/details/periods.ts | 137 ------ .../store/reducers/details/selectedPeriod.ts | 34 -- .../store/reducers/details/transactions.ts | 83 ---- .../transactionsv2/__mock__/transactions.ts | 18 - .../__test__/normalization.test.ts | 375 --------------- .../__test__/transactions.test.ts | 199 -------- .../details/transactionsv2/daysInfo.ts | 145 ------ .../details/transactionsv2/entities.ts | 238 ---------- .../reducers/details/transactionsv2/index.ts | 30 -- .../reducers/details/transactionsv2/ui.ts | 268 ----------- ts/features/bonus/bpd/store/reducers/index.ts | 15 - .../bpd/store/reducers/onboarding/enroll.ts | 37 -- .../bpd/store/reducers/onboarding/index.ts | 16 - .../bpd/store/reducers/onboarding/ongoing.ts | 38 -- .../PatchedWinningTransactionResource.ts | 46 -- .../bonus/bpd/utils/__test__/dates.test.ts | 34 -- ts/features/bonus/bpd/utils/dates.ts | 19 - .../bonus/cgn/store/reducers/unsubscribe.ts | 2 +- .../components}/DeclarationEntry.tsx | 6 +- .../components}/ProgressBar.tsx | 0 .../common/screens/AvailableBonusScreen.tsx | 2 - .../bonus/common/store/actions/index.ts | 2 - .../bonus/common/store/reducers/index.ts | 3 - .../DisabledAdditionalInfoScreen.tsx | 2 +- .../design-system/core/DSLegacyPictograms.tsx | 34 +- .../BarcodeExpirationProgressBar.tsx | 2 +- .../__test__/BPayWalletPreview.test.tsx | 2 +- .../__tests__/CobadgeWalletPreview.test.tsx | 2 +- .../__test__/PagoPaPaymentCapability.test.tsx | 4 +- .../component/card/FeaturedCardCarousel.tsx | 7 +- .../__tests__/CreditCardDetailScreen.test.tsx | 8 +- .../onboarding/bancomatPay/analytics/index.ts | 8 +- .../bancomatPay/navigation/action.ts | 14 - .../bancomatPay/navigation/routes.ts | 4 +- .../bancomatPay/saga/networking/index.ts | 2 +- .../saga/orchestration/addBPayToWallet.ts | 26 +- .../screens/ActivateBpdOnNewBPayScreen.tsx | 27 -- .../screens/add-account/AddBPayScreen.tsx | 6 +- .../bancomatPay/store/actions/index.ts | 4 +- .../bancomatPay/store/reducers/addedBPay.ts | 4 +- .../bancomatPay/store/reducers/addingBPay.ts | 8 +- .../onboarding/cobadge/navigation/action.ts | 14 - .../onboarding/cobadge/navigation/routes.ts | 4 +- .../saga/orchestration/addCoBadgeToWallet.ts | 22 +- .../screens/ActivateBpdOnNewCoBadgeScreen.tsx | 27 -- .../ActivateBpdOnNewPaymentMethodScreen.tsx | 109 ----- .../bpd/SuggestBpdActivationScreen.tsx | 72 --- ts/navigation/params/WalletParamsList.ts | 3 - ts/sagas/profile.ts | 17 - ts/sagas/wallet.ts | 33 +- .../profile/RemoveAccountDetailsScreen.tsx | 6 +- .../PickPaymentMethodScreen.test.tsx | 5 +- .../__tests__/TransactionErrorScreen.test.tsx | 4 + ts/store/reducers/backoffErrorConfig.ts | 13 - ts/store/reducers/wallet/__mocks__/wallets.ts | 30 +- .../reducers/wallet/__tests__/wallets.test.ts | 16 +- ts/store/reducers/wallet/wallets.ts | 49 +- .../paymentMethodCapabilities.test.ts | 2 +- ts/utils/testFaker.ts | 3 +- 210 files changed, 112 insertions(+), 15232 deletions(-) delete mode 100644 img/bonus/bpd/bonus_bg.png delete mode 100644 img/bonus/bpd/bonus_preview_bg.png delete mode 100644 img/bonus/bpd/fireworks.png delete mode 100644 img/bonus/bpd/logo_BonusCashback_White.png delete mode 100644 img/bonus/bpd/logo_cashback_blue.png delete mode 100644 ts/features/bonus/bpd/api/award-period/v1.ts delete mode 100644 ts/features/bonus/bpd/api/backendBpdClient.ts delete mode 100644 ts/features/bonus/bpd/api/citizen/v1.ts delete mode 100644 ts/features/bonus/bpd/api/citizen/v2.ts delete mode 100644 ts/features/bonus/bpd/api/common.ts delete mode 100644 ts/features/bonus/bpd/api/patchedTypes.ts delete mode 100644 ts/features/bonus/bpd/api/payment-instrument/v1.ts delete mode 100644 ts/features/bonus/bpd/api/winning-transactions/patchedWinningTransactionPageResource.ts delete mode 100644 ts/features/bonus/bpd/api/winning-transactions/v1.ts delete mode 100644 ts/features/bonus/bpd/api/winning-transactions/v2.ts delete mode 100644 ts/features/bonus/bpd/components/BaseDailyTransactionHeader.tsx delete mode 100644 ts/features/bonus/bpd/components/BpdLastUpdateComponent.tsx delete mode 100644 ts/features/bonus/bpd/components/BpdTestOverlay.tsx delete mode 100644 ts/features/bonus/bpd/components/BpdTransactionSummaryComponent.tsx delete mode 100644 ts/features/bonus/bpd/components/__test__/BottomSheetMethodsToDelete.test.tsx delete mode 100644 ts/features/bonus/bpd/components/bpdCardComponent/BpdCardComponent.tsx delete mode 100644 ts/features/bonus/bpd/components/optInPaymentMethods/BpdOptInPaymentMethodsContainer.tsx delete mode 100644 ts/features/bonus/bpd/components/optInPaymentMethods/RetryAfterDeletionFailsComponent.tsx delete mode 100644 ts/features/bonus/bpd/components/optInPaymentMethods/ThankYouSuccessComponent.tsx delete mode 100644 ts/features/bonus/bpd/components/optInPaymentMethods/__tests__/RetryAfterDeletionFailsComponent.test.ts delete mode 100644 ts/features/bonus/bpd/components/optInPaymentMethods/__tests__/ThankYouSuccessComponent.test.ts delete mode 100644 ts/features/bonus/bpd/components/optInStatus/BottomSheetMethodsToDelete.tsx delete mode 100644 ts/features/bonus/bpd/components/paymentMethodActivationToggle/BpdPaymentMethodToggleFactory.tsx delete mode 100644 ts/features/bonus/bpd/components/paymentMethodActivationToggle/base/BpdToggle.tsx delete mode 100644 ts/features/bonus/bpd/components/paymentMethodActivationToggle/base/PaymentMethodBpdToggle.tsx delete mode 100644 ts/features/bonus/bpd/components/paymentMethodActivationToggle/base/PaymentMethodRepresentationComponent.tsx delete mode 100644 ts/features/bonus/bpd/components/paymentMethodActivationToggle/bottomsheet/BpdChangeActivationConfirmationScreen.tsx delete mode 100644 ts/features/bonus/bpd/components/paymentMethodActivationToggle/bottomsheet/BpdNotActivableInformation.tsx delete mode 100644 ts/features/bonus/bpd/components/paymentMethodActivationToggle/bottomsheet/OtherChannelInformation.tsx delete mode 100644 ts/features/bonus/bpd/components/paymentMethodActivationToggle/list/PaymentMethodGroupedList.tsx delete mode 100644 ts/features/bonus/bpd/components/paymentMethodActivationToggle/list/PaymentMethodRawList.tsx delete mode 100644 ts/features/bonus/bpd/components/superCashbackRanking/LastPositionItem.tsx delete mode 100644 ts/features/bonus/bpd/components/superCashbackRanking/RankPositionItem.tsx delete mode 100644 ts/features/bonus/bpd/components/superCashbackRanking/SuperCashbackHeader.tsx delete mode 100644 ts/features/bonus/bpd/components/superCashbackRanking/SuperCashbackRanking.tsx delete mode 100644 ts/features/bonus/bpd/components/superCashbackRanking/UserPositionItem.tsx delete mode 100644 ts/features/bonus/bpd/components/superCashbackRanking/__test__/RankPositionItem.test.tsx delete mode 100644 ts/features/bonus/bpd/components/transactionItem/BaseBpdTransactionItem.tsx delete mode 100644 ts/features/bonus/bpd/components/transactionItem/BpdTransactionItem.tsx delete mode 100644 ts/features/bonus/bpd/components/transactionItem/__test__/BdpTransactionItem.test.tsx delete mode 100644 ts/features/bonus/bpd/components/walletCardContainer/BpdCardsInWalletComponent.tsx delete mode 100644 ts/features/bonus/bpd/saga/index.ts delete mode 100644 ts/features/bonus/bpd/saga/networking/__test__/loadBpdData.test.ts delete mode 100644 ts/features/bonus/bpd/saga/networking/__test__/loadPeriodsAmount.test.ts delete mode 100644 ts/features/bonus/bpd/saga/networking/amount.ts delete mode 100644 ts/features/bonus/bpd/saga/networking/index.ts delete mode 100644 ts/features/bonus/bpd/saga/networking/loadBpdData.ts delete mode 100644 ts/features/bonus/bpd/saga/networking/loadPeriodsWithInfo.ts delete mode 100644 ts/features/bonus/bpd/saga/networking/patchCitizenIban.ts delete mode 100644 ts/features/bonus/bpd/saga/networking/paymentMethod.ts delete mode 100644 ts/features/bonus/bpd/saga/networking/periods.ts delete mode 100644 ts/features/bonus/bpd/saga/networking/ranking.ts delete mode 100644 ts/features/bonus/bpd/saga/networking/transactions.ts delete mode 100644 ts/features/bonus/bpd/saga/networking/winning-transactions/countByDay.ts delete mode 100644 ts/features/bonus/bpd/saga/networking/winning-transactions/loadTransactionsRequiredData.ts delete mode 100644 ts/features/bonus/bpd/saga/networking/winning-transactions/transactionsPage.ts delete mode 100644 ts/features/bonus/bpd/saga/orchestration/activateBpdOnNewAddedPaymentMethods.ts delete mode 100644 ts/features/bonus/bpd/saga/orchestration/insertIban.ts delete mode 100644 ts/features/bonus/bpd/saga/orchestration/onboarding/enrollToBpd.ts delete mode 100644 ts/features/bonus/bpd/saga/orchestration/onboarding/startOnboarding.ts delete mode 100644 ts/features/bonus/bpd/saga/orchestration/optInPaymentMethods/__tests__/optInDeletionChoiceHandler.test.ts delete mode 100644 ts/features/bonus/bpd/saga/orchestration/optInPaymentMethods/__tests__/optInShouldShowChoiceHandler.test.ts delete mode 100644 ts/features/bonus/bpd/saga/orchestration/optInPaymentMethods/optInDeletionChoiceHandler.ts delete mode 100644 ts/features/bonus/bpd/saga/orchestration/optInPaymentMethods/optInShouldShowChoiceHandler.ts delete mode 100644 ts/features/bonus/bpd/screens/details/BpdDetailsScreen.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/BpdPeriodSelector.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/components/bottomsheet/HowItWorks.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/components/bottomsheet/WhyOtherCards.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/components/iban/BaseIbanInformationComponent.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/components/iban/IbanInformationComponent.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/components/paymentMethod/WalletPaymentMethodBpdList.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/components/summary/BpdSummaryComponent.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/components/summary/TransactionsGraphicalSummary.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/components/summary/base/BpdBaseShadowBoxLayout.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/components/summary/base/ShadowBox.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/components/summary/ranking/RankingNotReadyBottomSheet.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/components/summary/ranking/SuperCashbackRankingSummary.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/components/summary/textualSummary/ActiveTextualSummary.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/components/summary/textualSummary/ClosedTextualSummary.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/components/summary/textualSummary/InactiveTextualSummary.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/components/summary/textualSummary/TextualSummary.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/components/unsubscribe/UnsubscribeComponent.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/components/unsubscribe/UnsubscribeToBpd.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/periods/BpdActivePeriod.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/periods/BpdClosedPeriod.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/periods/BpdInactivePeriod.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/periods/BpdPeriodDetail.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/transaction/BpdAvailableTransactionsScreen.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/transaction/BpdCashbackMilestoneComponent.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/transaction/BpdEmptyTransactionsList.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/transaction/BpdTransactionsScreen.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/transaction/GoToTransactions.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/transaction/LoadTransactions.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/transaction/TransactionsUnavailable.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/transaction/detail/BpdTransactionDetailComponent.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/transaction/detail/BpdTransactionWarning.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/transaction/detail/__test__/BdpTransactionDetailComponent.test.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/transaction/v2/BpdAvailableTransactionsScreenV2.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/transaction/v2/BpdTransactionsRouterScreen.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/transaction/v2/TransactionsSectionList.tsx delete mode 100644 ts/features/bonus/bpd/screens/details/transaction/v2/TransactionsUnavailableV2.tsx delete mode 100644 ts/features/bonus/bpd/screens/iban/IbanCTAEditScreen.tsx delete mode 100644 ts/features/bonus/bpd/screens/iban/IbanLoadingUpsert.tsx delete mode 100644 ts/features/bonus/bpd/screens/iban/MainIbanScreen.tsx delete mode 100644 ts/features/bonus/bpd/screens/iban/insertion/IbanInsertionComponent.tsx delete mode 100644 ts/features/bonus/bpd/screens/iban/insertion/IbanInsertionScreen.tsx delete mode 100644 ts/features/bonus/bpd/screens/iban/ko/IbanKOCannotVerify.tsx delete mode 100644 ts/features/bonus/bpd/screens/iban/ko/IbanKONotOwned.tsx delete mode 100644 ts/features/bonus/bpd/screens/iban/ko/IbanKOWrong.tsx delete mode 100644 ts/features/bonus/bpd/screens/iban/ko/IbanKoBody.tsx delete mode 100644 ts/features/bonus/bpd/screens/onboarding/BpdInformationScreen.tsx delete mode 100644 ts/features/bonus/bpd/screens/onboarding/EnrollPaymentMethodsScreen.tsx delete mode 100644 ts/features/bonus/bpd/screens/onboarding/ErrorPaymentMethodsScreen.tsx delete mode 100644 ts/features/bonus/bpd/screens/onboarding/LoadActivateBpdScreen.tsx delete mode 100644 ts/features/bonus/bpd/screens/onboarding/LoadBpdActivationStatus.tsx delete mode 100644 ts/features/bonus/bpd/screens/onboarding/NoPaymentMethodsAvailableScreen.tsx delete mode 100644 ts/features/bonus/bpd/screens/onboarding/declaration/DeclarationComponent.tsx delete mode 100644 ts/features/bonus/bpd/screens/onboarding/declaration/DeclarationScreen.tsx delete mode 100644 ts/features/bonus/bpd/screens/optInPaymentMethods/OptInPaymentMethodsCashbackUpdateScreen.tsx delete mode 100644 ts/features/bonus/bpd/screens/optInPaymentMethods/OptInPaymentMethodsChoiceScreen.tsx delete mode 100644 ts/features/bonus/bpd/screens/optInPaymentMethods/OptInPaymentMethodsThankYouDeleteMethodsScreen.tsx delete mode 100644 ts/features/bonus/bpd/screens/optInPaymentMethods/OptInPaymentMethodsThankYouKeepMethodsScreen.tsx delete mode 100644 ts/features/bonus/bpd/store/actions/details.ts delete mode 100644 ts/features/bonus/bpd/store/actions/iban.ts delete mode 100644 ts/features/bonus/bpd/store/actions/index.ts delete mode 100644 ts/features/bonus/bpd/store/actions/onboarding.ts delete mode 100644 ts/features/bonus/bpd/store/actions/optInPaymentMethods.ts delete mode 100644 ts/features/bonus/bpd/store/actions/paymentMethods.ts delete mode 100644 ts/features/bonus/bpd/store/actions/periods.ts delete mode 100644 ts/features/bonus/bpd/store/actions/selectedPeriod.ts delete mode 100644 ts/features/bonus/bpd/store/actions/transactions.ts delete mode 100644 ts/features/bonus/bpd/store/reducers/__mock__/amount.ts delete mode 100644 ts/features/bonus/bpd/store/reducers/__mock__/bancomat.ts delete mode 100644 ts/features/bonus/bpd/store/reducers/__mock__/periods.ts delete mode 100644 ts/features/bonus/bpd/store/reducers/__mock__/ranking.ts delete mode 100644 ts/features/bonus/bpd/store/reducers/__test__/combiner.test.ts delete mode 100644 ts/features/bonus/bpd/store/reducers/__test__/payoffInstruments.test.ts delete mode 100644 ts/features/bonus/bpd/store/reducers/details/__test__/paymentMethods.test.ts delete mode 100644 ts/features/bonus/bpd/store/reducers/details/activation/index.ts delete mode 100644 ts/features/bonus/bpd/store/reducers/details/activation/payoffInstrument.ts delete mode 100644 ts/features/bonus/bpd/store/reducers/details/activation/technicalAccount.ts delete mode 100644 ts/features/bonus/bpd/store/reducers/details/activation/ui.ts delete mode 100644 ts/features/bonus/bpd/store/reducers/details/combiner.ts delete mode 100644 ts/features/bonus/bpd/store/reducers/details/index.ts delete mode 100644 ts/features/bonus/bpd/store/reducers/details/lastUpdate.ts delete mode 100644 ts/features/bonus/bpd/store/reducers/details/paymentMethods.ts delete mode 100644 ts/features/bonus/bpd/store/reducers/details/periods.ts delete mode 100644 ts/features/bonus/bpd/store/reducers/details/selectedPeriod.ts delete mode 100644 ts/features/bonus/bpd/store/reducers/details/transactions.ts delete mode 100644 ts/features/bonus/bpd/store/reducers/details/transactionsv2/__mock__/transactions.ts delete mode 100644 ts/features/bonus/bpd/store/reducers/details/transactionsv2/__test__/normalization.test.ts delete mode 100644 ts/features/bonus/bpd/store/reducers/details/transactionsv2/__test__/transactions.test.ts delete mode 100644 ts/features/bonus/bpd/store/reducers/details/transactionsv2/daysInfo.ts delete mode 100644 ts/features/bonus/bpd/store/reducers/details/transactionsv2/entities.ts delete mode 100644 ts/features/bonus/bpd/store/reducers/details/transactionsv2/index.ts delete mode 100644 ts/features/bonus/bpd/store/reducers/details/transactionsv2/ui.ts delete mode 100644 ts/features/bonus/bpd/store/reducers/index.ts delete mode 100644 ts/features/bonus/bpd/store/reducers/onboarding/enroll.ts delete mode 100644 ts/features/bonus/bpd/store/reducers/onboarding/index.ts delete mode 100644 ts/features/bonus/bpd/store/reducers/onboarding/ongoing.ts delete mode 100644 ts/features/bonus/bpd/types/PatchedWinningTransactionResource.ts delete mode 100644 ts/features/bonus/bpd/utils/__test__/dates.test.ts delete mode 100644 ts/features/bonus/bpd/utils/dates.ts rename ts/features/bonus/{bpd/screens/onboarding/declaration => common/components}/DeclarationEntry.tsx (86%) rename ts/features/bonus/{bpd/screens/details/components/summary/base => common/components}/ProgressBar.tsx (100%) delete mode 100644 ts/features/wallet/onboarding/bancomatPay/screens/ActivateBpdOnNewBPayScreen.tsx delete mode 100644 ts/features/wallet/onboarding/cobadge/screens/ActivateBpdOnNewCoBadgeScreen.tsx delete mode 100644 ts/features/wallet/onboarding/common/screens/bpd/ActivateBpdOnNewPaymentMethodScreen.tsx delete mode 100644 ts/features/wallet/onboarding/common/screens/bpd/SuggestBpdActivationScreen.tsx diff --git a/img/bonus/bpd/bonus_bg.png b/img/bonus/bpd/bonus_bg.png deleted file mode 100644 index 3fe6eb3a393ab2fb012e5966ca817d534b044bd6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30683 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 S5TF?V00001mtpu9@!MNL6K73{(u5Q+c{axtUy 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 TXS)0Qr!{$LWvMa=)1dzaf2FP^ diff --git a/img/bonus/bpd/fireworks.png b/img/bonus/bpd/fireworks.png deleted file mode 100644 index 5bec88cd5aae33b214ebfc1513c5f6b72bad8e29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3533 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 YjOYVma%(&Ozn?O|9EC>KU%@2&52E`RQ~&?~ diff --git a/img/bonus/bpd/logo_BonusCashback_White.png b/img/bonus/bpd/logo_BonusCashback_White.png deleted file mode 100644 index b131968a391474dd372f16a91d2d8a1bddf49cdb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36049 zcmdRW1yfv2&@LTJ^Di|0`zv@tKE(e!|L;M3 zR);U)!eBDuBI=&tXF1T_dJ+r$hZpv61yG`wiPCuAy_1<&_Ux*RTEr@MSCW_Vl<K%S%UO|7if<9-<650yg*goDrl_@zN-fFnpb$`kZode)X)D& zp)*^^yYjD$*wwF@d1k_T((t8UsM^55NLbIUtkQck*Cbd7^ay|ZCg=ZKV}ZxtUS^u< z`@&sZROZHSl^^II9IU=IVHI_R(Dr90lnE}dca<k={$@)38Zhr2k{HZQatVxeUm7V4}7G(dc)l$zIheHn3cN0 z(i{Z8EnrjiTNI;t7?LF+hu!NXGoygUS5TzfQY@zp->0k9L87eZ@A=%PX`cKWFyX@k z8Ytv36JTpi7_udM9m6yON_BMWJI-g>fA1MDKUUxnRWKDNFCu=`f~BAXpUCp7hRmb~ z_ej#wQc$Bey1K!9RZa_O*nU*?-ON|qkaiuprz^-gu;*K|}4YYpC; zrutV`D1{ShIw9aY$#jA@aqM5hvM>-~i%vnMh8SzLC^=^~fIE9r?4?{fGuIXbyC`^1 zRn(Qn`radN&^=YYPsyRzqktpwHKwl8zC?U9Hjsv)<)hM7a^a|eY@&=g@Cp}5TIfFH z@ROP?apeN|v%(%cL%;2qfhOgkK=9TdW)`gX-+xsvA2~CCR|z|3^VAGxzGmuls(wE$ zBv}kTbhC0?EWE~@r+d>CERxCR)NxXwi+jN~N&boXTJ@7}bC|(APmuoW)fd~vEXwZI z6RS?0tno896*P{}0Uum8YpkjTN?{0KMvCF*&U>Ax%6k^n@83LP3sV|OY5PK%kkx=l zsxm?N{oaCa_2TXV7{5w!Cb^3Gt4F0xg!3>X6g=}GX^5LLR$qvPl~`oG()~D;SYpw4vtGoL8x9Vt}begFd=+FYfJ(tTNMw5Y|04kj64#5;uV{^4!Q<+r}~i zgGYPGT9Ay+YLteU(rP1{*<-jWpn2{V5$yt&;fCT?p6q3<*G5QXDh!Exe)43#455y7 z+yaBs$s5a&W?iVcs1Eb~rq{&Gwj11uL6xqcUPKPfxcmC}6k&ggEyE4xh4T`giM{?vqE<1E_Vjt{#4lk2$l-Y0kc3u+H8Jjrn;zv4N zlP*~$ti!ql@HEdj-gCnc4X>iuU1L=*44l``v)<0RS2e2u;XMo^RVKjQq42zMOHf5 zzVP@zYt$JuUC`T)kfvcQGtBkEZfw*NF+gfz;be#LEmz)dIzhd7dUt1-TBJjB8OkWg z3~;0oNI^1ep6EDQ>EP5lkNoes2Rl|QPbfv>CJ>--XwVa!72pE_#S-|<6#xz0EAsi7 z)7wow;3vlzSBU#-{8yoT*a8SHD)fd80T)ryfbq7!D_htX8P{LIsn|QhxAn1%o%C@G z`n5R;QW;OqcySrKjacCKY|XHbYPYH-UwwSvv=0E7-cK6f;ON>x6A5c=dVb`rLiRA5 z05&?yAd1c})e)hZh#IY?#zbGFcd$T`!b3B$3xTn7FW>vaM$_4h-oHw?F{RDPmT>nU zl!1zrupu=ay9aE`ry3h^M|R6zIW|o>vpNrDs^ku${h3cVOBDr>2EaZgk?I>?jWNHH zF1=KGRx=%8_I<-E$HyxQX(V(6^@NF!PNpwF3o5BMCe|bY_WA_jUMvoUhk%Sc2DSx^ zETu=*`Qciuiv1CTfA6R=$N)QB5j+6YGh>d;>$# zJE)cf?{V!Q8@=Xs8wLU zR4(V9j-2&z(UQ5>-w-Of@mtMS{a`tU?N!Jm8fv~mkTdwPg%h3jdSmMT;Y>pqw;ax1 zxl+(tD(?`Bd$Zb|)ruSmj1W|N92yFJm#q3(oV?R3wLq6Zb;twN`O~cW1-#_-)6mQ` z`JW7hH{1dRUd-q)W{P+AeyRht=rWp>HmqSBp;oTUXt|CWmG0kcE(h?4p&Y7O-X`uS zg(#jMr8$oXZE(dhJnC7S=@m#)fdsL-jWV_rXpAXWet03HHgHA6a6<+_P=wxUhV(hu z?|&zJAY2tH=YYe(j51A(NY%0i1TCsHvk+rqg)egpY3tj!8uc3WR6eDUkECU7<#N5{ z3Z5gPKDkpaSdj*OrdCpSQ=@ugm7pv8F#$GB)%Lm+4bV)hiQHM}w3B5il9fKM?utic z8+0gs8MsF6$@7Ez6*NdCan>9oBKw&lg~VZx;oKbetZ~UW4iu2lrj#fmfQwV>duQ!) z1!_60LgmD^Kndp@esdK9!S8s{{w? zOpU-|P?j^Ay%%THkjkj)mF~`wE8Ng%#IF5JNM{kdr~uq0A#LPnQVZIjBrvz~A7%tD zRJAMk!-TjQ#eIAmDoXWE_c%TQML#vPp>~+@i%{UaD|i(NCBcPqHluM+$5CJ@Sy2Tq ziwoBZ!m57WPSetUt%_rHkk%fnz6B4ZYackFjnFLSHFD9nm1;Tr3)oh_7NVMK z05U8~6T8;b+aIZ-0{kM42e2*UKKu8{cnsS}>ZD!7PUSvJ~2+P)#bO-twgd zDdX~*j8Cms$1}6~GyI^z*gr8xxCWZ>px@eQvTUVPn&{?3n1CCq#;el0OHE zsdu2g!lKn^C^x9A=iG=y^=NC#KWJ#ei3nR*GXfRyQYsHi^1)UoDU+KmBj6|Eye7$i zBZ|IT9@Ng-Knr?soWlg|^|$Wq2iJ1hRBLzzY}Cg0+MD1 zsUJF3H?xauONF$K8vbta=ie$B=8RcOYSF`GwA6zZ6k3D$_$+w}?ul(batVoZfUSM* zr?d1n<_PHtl|{qKcu|MZ6wsJ5ff{x!gs*G;cm8dg%$zSPAmiKgu58i#cbUI4Yywrn zWP6HU-7DcEzK3iW1`Ght6B5%E46=>_Ez@RJ(VlGY5KaJnAZHR-T9cikpI|VBUAk+d zGQ}h2p-MQn7|%r+GQUP3q$#i{j*{;0f*DO5A)vnz0Z$72oe=7d8RJiewxsQDBbp@_ ztn1XjbQvgljPNGh$5x8tgT&PDB!V5PL{$&PhWar#V;`VD8~o!OIfu>) zhkh7}T8;530V%?&Xyps{Jd;;xvQedf3Y>ZxxQXTbS3MyLzmp(RhM5IKYnWHbS(bR% z!A#ttOOWynULFX>y6~K9%p5fQj{sRT$I+^1Z2}iO#2wc8cU9F{<@imDmILIM*uBnR zPTy+4acadG#b(OKJEeS5SsQAFuCIa6cmhyA?No)JuVJ*>;EaOs6(aOv4V%NsW(l@wGKM3^NvTEA2_B(C#qY zZ9z*X2j@-pna$vj=Okrq!g6~n-7$2lZvtg*Fgah`!iALh(WmLVvElMo;%3MeRH`aF z7`Hv)R+Ini!R24(u7RwE^p?y2TkXahyEhw|(L!(!wD@y=R$aVtLTGaOd}jqE%Djt$ zcNu+THkItQSio%xd`mT!c~2T-XF7ed^ZknJ0J9VUWZEry-?@}cG$X@M5hRLUp){$^ zM!Ltu1`?i;&y(RG`3?cgJj`NFXa7J9nN2SD)G^$-4zh2Ys`TLW;Eb?ixVrmDZk&jv zYnSL<>ll*|&_cO7I8f7A2HLo~C^{Vp5L+wb9krdI7oSnuO~Lil%6E!}Z6`U}qjtMZ zUmLUt=D@fHYxrYWkN0FQNl#!bacJ>RGP5-t{lX?2RD(wDxeui zKKlSS>?g&j{;abegZ7V(tzstfsP9aYHzwKXGtH@+llQ9y0>7sA+E~<7>{}@8!}4gs zADq5X;Kyb2Tw!Lb2FV2uM<#sU#f)=ru*#@J}m_A~X3u)L?hZQMt*o$cGUHB)sXD z=6^WR%45tzQ!DKR_efxG!`=66q@k>7J6)EwjTGdJJzQq-4I+8La`|zRcbeS$~ zm&~37+BQ!AGXBqSDOgAKH%`Ux?nK)X%Vlr35RVGTM4xJK!U_Ke73v>pZ3L=6?gv4R${DMe%#vERmhYBb%28T_SP5 zxIJ({3BQaHBjUAGryi)bE@Ez%+u9!@DB2aSGD^zOk(Djs%Zc>g#}|{qa#qj|TzQJ` zuejI#Zj8aM^UZ4a2qERLJL(z)w%1jZ@Qi*27SB|`q5%hAqOMHRPuqE6X2P^#wZ6(c zWq{cpsIqFN`e#-*Zse3{;0!0h&$|#(HnwBC(;mgw%k}Bi@q*Rgv>w zgnM;v&;N-3(_RCBN)dp@15Qbnl@Ut}8e>;H-^k`X;xQqEB{y%Br8Ua6*;|9P)+(v| z$Oye5*{x+p)Hv1IPn1;D$ys<>T~hV8~trR`G0 zwBI2DnKFu5@T|t`QTvMO1q}Y-nD_lR;j1BCDf^}Oqz|mk42Ij!OZ2^oT^1$Nn>H3> zg{(Yx#VMwx)xFbeG(_NqM5!3CFh?Pg2D--^09{Vi{n1oUSfjH2{}nlad^K?tpo2XX zE%CuBU?u+_yqC^I$buKfs|hYme0UVZLZ*h7C+5#ni?5-AA_W*pJaKO2CLWd^d@3hj z>BpRGTBxBRja#$X_zQ#!aj97}9MqfA5Szom&Um3dOWAC}hxiL>EU}~f(?+R`S5PYg zc>TMP505~s>4%@Z{2aC%ncoruFsitSh$DFInHGQ(b>)9cR5A?LVg^tIHyDh{$@k__ ztMJtsB^&WSpHz<~Ed~I_2hyGxtaRAPA1|#md`zwSH@Ocd(c;>5uVvQnF=X5z+&gAY zKv=~xr_R4laWku_5Ji;~m|F6#4G69Nb&8k9Jm)s{1f4NnNvYhdry-J^`JUi4# zU<$OJL2Os(?PPPBByleuRtYN%r`x&ALY)6~YW{DzfQO@=!e}19nG7HE8^;A!`N~Vv zuHGp`mYuP(g^^Y9%!{MVO6ptJ7X+hd3k) zn!4|=FE7bQ#V+?gJNfZsGvBZBCYjI3QlK#S`c&ndfWmdr&c0-zVb?0rv=??!yth`z{Qj<+)p~>lqY^4;UP(^Ah>0P6b_*1^`=W7kP-6MXn=(<-v?J_x+n%nPo{*U9~{26&6b%DC* z)cdwP*&t>w^U)iEDwPT+1{2D%(n@olgq8%a9vz#t0?ERcA6R|ZtX^I z(rf=IzGUd!k`1DORYER6Sr@$C#LHnjv{Tx^D}J{C@ydXIdSNb^BcW0NOs=BJ`Apcz zDp>$tu<5^uXQ1|~S*`V?o0?Uz^j^`3h`cgl4Toy4?-8V{MnLR9qHH~uy}}#TTtOlS z1C)FB)r2mdnaFEF(;MvJbF89EZQC*2FkxNlj^nTes%rJ%9=(1BOBzo+ zrFD z@3~8>5M$br#hmBiOThK|8w+262<$Z3K}{sv(A&jV$cRDHJ~*NGuxq8UzDczXTaUm? zb;-ckYszpnI<3^hd=!`mqCaEp|euttBb0W0pwNFH1{2MGag}U%&KX@hH zXbyC%I!86+NbEF4e1O{3RGZcl_#*r)2;DSE1pgXa${ z52~2n=~q#`%F%afQWQ@;#7D3T%(Zw&#wvbsqATXo)_lrhA&AoSkTX2XYUp(+ytkw` z3^q49WZ$|NU0?(^{Li@jvQG^ZdFf^1^|tvPQWnVp1vL`+%*#*>fr*Gy6pD~{rU?^z zPFR0DzzM#5Y1FTuv5zv`tc=a)VlSzrPCjj70GUa!HP+&N#tn87YVhukd zS=q^IOUr2#OG(C;iGE6K;Fn;=Bk4amI*tah<#M&2KO}jTF}&do^+-G56~%jFA=G|K zZ|6LW@q+|k#c~Vm_}1C&i~+mqx5K9*2eIFh4{p;4-JoINFf(iG|NJ5Rvp$VmVz4lR z=?VAr(5-d9HE~ked)I8|JuauMKt&kzV;9JLc`quyHhJ@ky3shu=uuS2n+MyCeP2L& zOZ*T?8aOEJh7|qzgOyT+X{mNjK|RvKpnwjON!4jVwp0{mn7(r8B~~>aa?le}wII6v z*0B*_WrUpI{wPZ=_%6AC-c( zR`{nJjC*R56o@8sqsqLP6Tc5e$Q?XiY~^(9Owh%m?rPJJWkbOyfR!>l2xc&!nhEY< z>m?^H!*H`f?I><3#)IN$2VC9A$&nj8Yy{M;|KZMDczAd)io>rpqLI`|4ZS6In?ZY6 zBj1_LlNYUQ?gFbUDk#U$W?~_~Vg*>>lFc9roN2|$lvYtX(X6>@y;0{d)Kz0D&$Sn_ zB+(~5Q@#*Gk!m#72fo8`%1RlsOaMQzq_dfcR;HX^U6B6|qJ@V7v=lql9Eb9F6_T>_ zU$Q>Fg;Nq)J07Ex;(>;pyrHwBo$KU^J6z=PzvbdqMzmYq{gaqvDz>st$h4mx62QFr z$Y?wAJa%&MEGKd^Ay&jxIWfIJ!bTyTki7=%+lWw&(k+#y9ENAqZcu|c2Luj=wT$no zFw=Blfju42Pj*5$LO8H!T^dLb=8 z=}G|j2!x#n-;kw+5_NwdD*NOYR|>Di&v#>D|HVubzmMPuM3yA(JK|CmD3*(w=waiw zINJGA5(Kty#I+WmEKTUz8WcxRW=u>UI8P~js2vyA@r#0NWe;jnZ9z%Fv1|8B-b7v% z+pMcGo%Ef^>Be;C(T`KKlAuYvdwS2JpI^zFKa}7+rRJtfX#>bt`~37gH7%wA_A1qo zU)vG=6QLr3Z0jI-a!=Paaz#}&lC_AIQWndkdJ=*VjzsdPM>c}a_e3~BiilpsQy;W^ z`8(=`v|G*-EmykOQ!i>D>%T|m{?cW2fU_QY&Rw+p)d2o5Dm8W1)}oWD3W;$eUDw=S zrA61!xpGJOaY1KJc>!>hP4$Z$$K1m^@e-r$6-@vLk>|kZ#}Eb)@&{g6`*|2=$2uD5 z@eXwG9s3U_9^f;*)8>n zs6}7GE8{|e9%i^n>go8`C*4aB#TG+dZ+??KC!Es)|AN&CirONYCP{j&hN0!1oLZRg zHNib|YHiLwJ`q8^5T@U=AqxJ=5~VP1;ybA`T+@sTt!E=?%Wt7I8&+7-VPNE)7bzZG zKKqz2M$iq<;Jgl~9kA^_AUE7w5hbV?jA6pXqI$H59uW#eirnXVRs3;mJfLM|ILtK>+$ zy)o1i>;wP!&7NP8N&JQSf?%$R?o#O{wYIlEo=sgr1QthI+p;dLs=L&kGclRHHd4SA zXDvgV;%6T?bc7TCtGJlterj7mSbM{4xpEW9jZ!wj4L8ywuRYmElh+c_6Mp}rqOac6 zWoynJb*x3=FFCJ1W?1T)x!DAlc2l>t6YdTNZ@)F^LA1*?VXiyUjg&2=xYlPIntIhy z@cW5-iyu*B0uxX@t58Bb4Nef5PmN5zA9x0KwTbo>1=>!kqeU>H9 z4SRbLpud^0=c!i?6M;t4MuLDFdlHms5VxGtF$FLo<$wM<&VkiTy?X2A-yA8TX^nU5 zcD}X@In3Q-9!GRLKtCZnl?65tToyU8v84_jfO1B}cKG3Nip=^*T_J)KN3z<_olbYE zZ(u+goA3;Gn%F8deJnCOV#?=s*pW0S_MJMAHA_VvR!ISp1Q=tmrCjjtb1Q6A4Y`%#tBCl=ZKP;SWGDNAv9uVNtHFcXJKc(y1|mvB&ovay_bP5o8=kF6 ztom^LTlekeyAY(K5v|!^F=S8BBuVvMs#TIl*@pwH!+F{1y8B1I0FF~@3LinFzme<+ zsfnZVivI*aT;Ahn1woUhcX;N@-dgG>$OFrnj~3qPe&4*E*Fa>ezY9o9@y`=ujMh$- z!?5lK)`v%hFdgQ6g+k(YVp9eLH2?!>hD9yaAzd6e6MPtyjeQ;wZ4S0js>QZInIzhP(65w=Z*d=SQ*T~XUA#_p z&EoOK?t%xTu6hU2TVIw-H%O8IA5l39%N-GL<18TOP%#C;?1Ka9DS{Q{lF~~sWdK5v zkeI|{RR}dfXt+3euG~-UyG9Sb5j|Xsf2}(4P@|%p z-q_#9UZOvj*`am9or3*9w7M0+)j?@f7vpbq5xj?nqAkPZPxlt*3~EgYXi9&HGLv4) zqDWAYreI4=1Q^{7z+t8w9R-UUOq_u8q>@G+;>(~!3h!RPuym5lb)`l=H|KtqN^qNW zwvkZ(?cwCD=1eV-$ACc)EE*~fZ9%(A5^m`GXUcm^-BnL5e((w)CWlHIJleh80nm+i zgv?JPup2n%uh9q2alnL>p1DPIeU7l1TTZGjx3WYhU};xAby|of-`n!bQqn`+=rxYl z&%YExTIV=F%paHLfDZ;$*T382^ZN7hC7ZLU9-$$K^KlWv>l{P8B|09I1VGMkoA3XSDa< zJE1E;WvX>!1GlOg7{&+EwE1PK#ixRl@TF!bl?e(jBqFC+3|(|R3h{74%1VU4;3ZaD zlSC@bAhL5J4SYX_l2VUHo5W40c!o%jJf0sm^&>k@mo#b%Rf3)&=sx+o_Ox|za?6{o zFRolZXGZNjMCYurRc0B#njasGl3bvp;zT?N9&ZC0hS4JnXcRisYrbUtUFXPy3a&VG zZx%trjELfNw3ymraF>os`ad8^3efiQ1nm>Pc8Q@KI2OZCeeprV&Do0#%*%nE`V+)iECQHTCec2JL(b{9O$dE&M!QN8c0zT_OT?w8g&K@`GpJ@%uysS9 zFi|x!<*kbKElD>7qbf0HH-Ih8XO#9^2~qFPlE^+2(k5<`#B%z_z48N)W6qH5Cqcf> zwczZJ79|L%pz`sfUb!MzvlaSva~6Mdq~?)+$jxl(rk&|y-ktOjmu4aB-zOC*URy+a zZ8+W<4N1_1?oHpcu;@<~q0YVTWUT;GeIX^jls7wy9U8jO(&@<))JAXWoPbM%%(KBy zpR6h{;$TtItW1QDdW_j!0sP8*MNkLMENH!`eFHbImWC_dq4Igf)Bh!xLpi^6Jv^&! zTP2)H_d%3s;SCKxVS?8{?Pe?*AL8kk&yO_xNmj@5g|Fq4ga2L78g77!E|WEI3Z+X} zad+2*TAw+PSXzp&owsF*ehFq4wt@W0*XH?JOB%fKP3{$m?|t0!Imwyg*2PvKQW_fm z67^z`PP6i3whEy`Is3r}G}c;%|2Otq<)@ou1#+z8ZSZ`IB)w3@Z)|Gq-+|I6#FILZ zH1RnRE)19Nj*G=9Zr_?^d?mscw7xB^rxIRzB`8A$tszOPy03xU{FCl7#V5b=y0C)O zvm3Fr^e*TOQ6{8oZev^DH=ws(4#kO5W3dv!EOh0IUX6f3sn6{OO8J28`Sp^2ULQFI zT46+%y`$ONCzN7$`~})yW~`0*Le5q2+dHJfC=(PpYNCIxcK`lWv&djz+g(Eq&Xnn` zo@TSiTN_GSwH$$6E?kM(f-D`#5(^2@2Vc{5SwyO@5y%cx9GRCvpD@$>fhR#$)t~v0 z&z(A)6XAo(Mf6C&*5(v@=)n;yh41u1p-gi`k3#pa`-Fp|la(WIqTr-*9S>E$57*Dv zfT4m<{@NwX0#kYhNMYlyOjL(@$e}%YicKtQ!hJ!phTiY{>y>Ejx}C$R$2-p7wfZM< zLci<@?uv;Wx@}42q7^8v4zRs7Z1(S{O@mxC=CaR`(@%psJrshb-^rgWmp!-tP6?7s1BxRVKh)f2FPrLABMM{7Y)Z zj4Gc=uc!*;EsK}G84%0ucDHq%{CEc#$-KdCIQ z$C{DNhoW@Ek=+~7*wqDnv)9cgXygie+-?`CNky8h3xo`-!nWKkJ2G6PZ5@r0oy0_2ss<%^SC8dfr{M0iR^-W^6(-K zY(l?O30(?uOs}t$SLQF6J0JQZfK$O-Ry8s{{lhtwb6vVi7hjXEiON75xyFc<-R5XRU=*4C-+OT^X z0GfCUBYTgH{WTzNA}c7|R?#=7-_U8N_N(6O5Me zBds30JH1ZJ&n-<;Yr@}ZlP!t!9W-JxUPcKH8in|SMLBmPx2Qruxu#uwc-akIM#(1E zCWA&(p;;2U7@4CoseC84B)MP!2Z_Ig#R~~xM0n3FmBv2Al>Go)Z9ayMTKIOL3!p(h zX15!Ct@3rji{%529tclZnr2wyU{zvLki6b#`EV(QYf6$(-goLW#QoI`q-ik|V7bVO z%rwuaJBXb0f$?9t6Dw>R@Ja)l5IdX9y78yJ?3Ua{-fsXpHvTm2pt-^-<)G+?5r4H6 zy$C;95tpOYo=g3ag84L@n~+|1*@Kcg+=Po-Q8 z_oVjd5qlV~XLU~oJHLxw7bJgot+e(etPcm!{;c3Mk4PFro&DoA`yBStPL$~PXa!p> zRm6s}#B3F=ek{KKDB|UdJ{au8=Gdg>D4-F#ry@^-giS4H5`No@r8!NW zr{YD1%mwyPTz;k9KaiX)%TFJALuuA(=McnziGUGtS|oflanEZv%Y6$!L5E_ptv&1x z0*f*MUVzO}MeQlIA2VhvYH!$Fl3HyeU@Yb&p0KUFKe{G?`(E;(q?+@*)l`U$A*aHW zriJ0>JRC{Qz39R=$3iL-pgTIl>;k*@aX?Z|j4h4G`=nIWHhi0Q$y|Ow(#%_u^m;58 z>dKYIiw+l(x{4HKGI%O7VeQ`2@_&(k8pW$2SJVMCvCNKXyQar)bKS@z>tV0em`O1= zT7-fwW)s#>9s99T8Fh;XRxbo>8fkzE_4$<*abFaiK&{)9c-!i>lz_A^8Wr(wZU4FA zlX3Ub;5))2%7qPccJ|YF!i){H7+`AAU6OD|8il4_vVipYS>24cop>fq85I+_F@$ND4Rp(Vwn3Rw`P1c3(it zfgIS#HQI$Ew|3xT3ip9{)(t^8S%%H5B^*4bsv4}arK=RFixi2O#l5BTiR4 zg(cD*DBUxBxZxF_9^mE+hyK_6&A`?Ue-uzKG{2=9wkndB1n7{XV9e-2?PwfcFvJYO zi&OIy&4)W#Hp5(WGY*!SH6hD>xkI0N_gz1WU*l=-3=dW!ut#yi(qvqkm1M!lxyABY zao#PGb?=}5P^DiFo#e_;_Gd)^4`ZGbje@1s#RsaoIv+K^_^D_G&{HSho;^7{;{eC* z+#1;-O#mBtf;X(eman)DSXI{;b(64=0DbjA1CRUFsP>LS3_FcZ6!hSwM9uPAEz9~X zj)6wfLXsrTGe6J5_q$9?(5=7$>L4720>~l z6O5aA1WK28db}W-LO!x|-Xu`PdZ&vzX zTB_E`vIaoooBqewi76gR9j zx$l&|f5nHbA&l?E6!re9A)-fLv>>wl?r&`T3#ud|U)B~sCrP7(7w(xu&UM&c$5PB; zikg^syUB(3FB{Sa@=F`$#iTz`6nWaR!^(k~<#Ry~!i1D`kH-kl_v^(9i#Pw?ksOb2 zCIU&pnz^Cf#mm#jx#vg6{74`Se1<0R*urnkk|YGhTDqZ_n;|6~;FRS5A^|e*1&;UBNm*X2YSL zG&pmSLQQzsr_I`Z`{l020ONmYO#0EikH>pQ>Mf#?ah3ZA88HCTVUi{P!p!d4u2SKt zgYc~3Uj$GPC+uSwZYvQMrmGo?{oc9~@?TNi|42a-Md>w6(#&DGpbCe&U9!A4_%qiv z=6o{Qo$z*D$GRQsMTCteC+E*sw)V}PRxo$ijk4X|Biyd{2^~OaP$dvf#TnA2TFL|7 zw|eX@!C?`CE4;Mv0`h^d`;e>5n6(BnDN(rW#xtNTCZWljEM8C<>@%c7rTh21$~Qpt zD`orS?FS-LEKhv(-oo593!s8FA)Sq6O)6ywRoY@^E!_-BMY~VR_jA|Iqr!sL6JHY< zR{7)M*RIh9`~!^i=k7L*%AcbjfVI@_%}Br{6vPGl+Hcs^AP;+u0PpLSR(}qt!MA@Wt3it7&!5a5B~ILe`C8;qX`BGKvn}^Q zfoLDZN1ru3SSgcnZP%!_K3gk z$piLuQB(19&Sr0Jlf4jW)#$fdhwNZumt8XcuzJ&IHVJn#Kt59rA|nSj4AfA7S8J+D z@5T!!u>_Y;aKk+=sWWLq|8$pC5hr~mfAXY9NXR0ECy44p^JuP?sRe0eU)fuMA$}@z zupzXuz3FchGNjpuP=DY)5&wRj`^EBF!whcPfvnZ&XiQVv(BSZfC9$W| zr&>G6iE;rUZ^;Toc!BG)z(+dM=qNC=TsA1m1<#uESbrT z0fcGMrcy;YyhRS;un#noD{zyrjFTq*yXL@V#09^Gc{%RWrYyW`Dw{0S7kXy_XT=Zm z2Ho{HVe@IS^F`=7)n)Kw7RSD?NDd6wAdrunSMpU zL=-PM)VjV|-YlC<(Qk!SLtiKJhyE5r)n1&(>z){(X`g-LX;f~n+e3IeB@6BMZ=Hi- z`qCcs!JU%1H;~F`a{s+DQIJ%;Ezz2PNFUsq)0=zH5$d5B_wNN3cJCk`OO#$j_*5|9 z+B{u65s)|bhO;3&{s7vnQ;Vp`e&0_&?dC`l;#1=2C|5P3@|Syznyv?Yqfjykj0@o; zp-3TDZEGaLsS+K8OCNL|A~;KN8O9aZ(&ir*;^LAsp2HXO4C4WJjJ-(=IZ{bdz~b9}5uZU#D4>rjNc4yd;!H$6 z|F)CA`zOEf8?iU^g6*}S2;=d))h|8fTqoa-n7A9tQ3HaxXU>BLpd_Ip{@rdlB=70O zzH7I8WK=Sk-qD%@z==ICq^L|dDlU_Ws+!rvPgbw@L8k&{ zKdLjmZLwY@-9+@7`#_%a+63fDDKt#6h@JcRB1-0xF3GAt4m14ZL|s0@Qu!g7Cae&t zjvLt*W<{ShTE~{EaE3b)6e?x|83b$N9au{B0W+);43srNnymkSt8VgM`QvDCD(jtut=(c<~=|yncd=joPUqdS68pwsQ&T{Wa}~g7VB? zLp<72gsHqwA6j9*=42+Nee2>XD`dlfTB2@ zsInMVnlIgxI)K&0FU@04R-E&@!kr18_WjWNFf)Ip(9ksP3=HPH^GF2c)vAU3VQ)Mm}_+9^|F07O7JL^2>K8NoZyHQWv)59}@dQ>Z@& zG`goRXI_>sPf91Tu0WL*@Swp^y7qC|$Xj^d=`Ii}qw!Qtpp1S6G|OjuTDQlcZ3O4; z2Lo0a7#w>mrsp!foN1Pr(T~4%3rUkXA6zca<2&4u`sND*wP{2<&6@60WQqlr*x98> zPU!Ew0%H)?N%2xGfsOl;B=_tp+_{I3VckEfUPnAPAqao?RH)-g`WE2BlaE}M`-pBW zesz47q3H+Q#YU@)avWNa-X{_t{%zd4qI4rm7t=`Q^m~%DaY5b$@VA*#T6)>)LjfT% z8i!N=jQgVM53Hz>C%BN>fW<%5bjjKSDlFn&)swHi=}x)mAwRrgjk@rEk`revsqEd* z^|de4GPdpu776q2lSqY({z$r@o)}%7XrD6b&P={O~uMUD7tqIFM&R?@$c!Z|5bX8U0PeJC)WLK#e=4OZ?=MtZvHup- z{nk)CUn&S^o&G{|A(KXfEETIJ=PfZW>yLqi{YdReG|e|9KA+y)pQ4$)eM=}wylE{C zd^Y!W(uOfOYCu7JvQk1Z2Lefc*N*1QCbYKa#S&Imd-RU(|9~fc9;fle-0-j>l?1Ex zuZb%zi4piA>%$Q}zIsX~o+LEnYa! z8u`azp(*k+UcJ{k zTI3{8khGEu9PlIl-1p22@~J=?ayYJe@Ax>s|7H2X#{5%X=;pXe^m8SLRHQr{5(@1> z)P&p06T2^u^M(5Eo#k$qb8n>@<9?v@SP&@j!&Uq%+7I~@DbyZ ztE56;LPy=GSs2r`s*}|oVIG1-@7Ob@z1tcbH*V4=sLkOr2Vyr2yh#kr%H(3#+Ct)U zh#A*wjK!6&uYF4m3)+*&KnjN(Uuq7Gz4My<{1}<>xQBQjH$9O0TItt$Z1P4+Oe0yw zTtFA;ve}esO$YZBQ3Kcp1Q$;a*8L6g0yIx!^=>AO~scOW9j?zcJa$nyfo!!lJsvC`2DJMNLkkr zNmlxMSmIx_a8f#U9&xCs3hxrV(q%f;?etel^1Xyz=ba=13Ci7n@YSo|SH@_8Q{{_Q z7)W$C*jZ0pDf!A4Em#FL&vmoxn;MW(^Tl zACVsXp3iPQ0~#R+k-&-bMGM0sK$=Z4gzOlz91|o9JkdOKFj+5W;J3g?cGW3dQ<_Evys|}1iRq3_MM)2 zZl#TN9~2{c@3`G9r^YD-()8H-b z^9ZXHWUW*XHQ;5}66uOEZ|ma>WOABnneL)%^=m#WN9B!aV`D%M_bCuJUB-JgvKZ}X zh}8PxfPW>@^lI%0Vob4MjboAKHAB4?@`4O)(>oy3i*&w2d8y{fJ*+nl$s3>t-Ye|H|k|i@8{$Av7tS!&y8(Eh$nA$nnjQb-sr5aC$+MkuaV>jFvKS2LWX(y*{fya(;ftbyFJ(w~ZGmvz z+f3|8-tbj_Pb2JZ5n^dZ!5DP)az|!2<$@$`f)&1&m}fgQC*3c&rxfi7=ePcTZLan( zbFJ~%{U3kNMImuHrL+J0UjUhq6HTBq{m74Y7b)VP`4VBv@=D9XPl>X5e6)R5Wc)R= zV2wOKty_O?=cA;m3X-vSCAixv8~JO-F9LECMZb4*vE&x=7lc+dEi3gSBF*a*(QWP@ z_rmP*pAk-z?{u>6FAw9ReqxCE+d?#v!(xZ=GmS0xA2JC|?A4IKL@@FzN?bn*AmH#Q z_m<756Ip67-^e6quSw%Q^x{eYL%PWQ7zyXu<9iJzZtFA{Jz)#mPpsH12Km6h7yoaTS0%5oFM0&rIGzj(rr39@aYruO2w7G{nPOZt%eciap#B}9yY{~EA z(y{nf0$EF-JYH=@gETa5ytF|d7yGNf#HC~LgC$TcsEvXldWO;=?_6#DJ5jIR$p?)cg;aE~OC%&{dGB|*p1uoB2p0^EDE z81d~GH%@xFGYchkNnAP>KUe}N51IeiA{eS?XmLRA!9_0x!qD-Fo@fSNuG-81t7TCb zeeA)=EB2A?2w*(-oj=sFg0PJDSqW5UtNp11{bCEiW#iyCJxjn}0*?Bwf1pQ* z<9(p3w*pb>A08z1y!wb43r&82o?yJ8YbTB_p}zJGwZ2vY`IP{Z=LaXj9Vm!Dwv5qyN zQC!n~OD*mdX}kDwY1FpJX^GhBan)U%`SVU}I+lQyK*kcNe5!V#4h)JLZEfkUqsnpV zSp0AakY786L(PStEx&v2SI=Lb3PeDd(7mrGsNNU7^Fn2&wx-jIq}k9_wQmwE6btc( z?s5)SyDwazO!ad=h~HQxk!N|Lfn18@{{p<3O3VD&CvJ>1P%{e!bwyk{e)zSzLH+Ox z?@Ru*v&uAen>^WHe3q95HwatV*3rf@3}KScBBFvOX1%nm}<{Z zGWlan1Q!+hv%_~q^<~e{e0-6<66I}sxH%jrM`HMv%|?zXXD;whlg6uxBh>YLbEcpF zBR57u{9169_JShpD`@??FhZRZM#~F7Nk36^?6KIJ4#Oej`?pMJ*YaPf^9n=MB~F41 zH7aA(vD^W1feBCDahU&G&l#F%fVo0X7jSIV5h<*4n+)1{)~BA4mp?=K4@)%m>p zg!!yvOzp~!)uYOY4#-Kxk?L$ecx34Rl*g(o$&iEmA;Lqbh&z-<`VWD^DdZCZ**t`7 zz7vN#3hgw^S4(pS*|Z=!jCO`H+PRweHVuLc3nRX%G(yKQD!e)jHmh*b)W14b9Zm25 zIDSX2{J#7->OV9{hcF^})eD9*%QmRE>Jj3a$1A+MAM$#H{MjWef4bu&^}9COQ0kZD z*WPp*YNN}zv`Nn=j7Wc08j;LBGbrNc;BU=Xd^zd`dbmb3#7aWFIGw!wZY&;@dZ{!} z?Lg=1-I#Q8jZT?bnfr=f(P5I)cZV|?f87tqcMhb6P*C^yAx8Fh)eeziTLiFkae&q_ z06s~(A*^v?q_a17th`Jm+0=(dWN&CJKx1?j%I{{D@+}w{F(ttA7_}|wdxhb`Z{aPz z@(?~RUEVwQKN&Ur-Q%JM8#?;#4qr=sofXp(uJ>phC zVVl-1u<~FcIF1JK1{#t1l1wJN??HumFw2IqJO8!$hcW6_()Bmew^H}8Bz{A;A9PPf zem9fW{&kv_S?Y4Wht$6#zBjB)Rp&&6jRnT&X#L7;H3l2?*-7Vl55`<|EK2GYWD#hA z=z-JlLE{~N@|?D%I9T1(K-%|H_=WG|nFo5F--%pW)fJhmhLfgyq1&U=(G}j}KS)=J z%+|^?8tv=Dk{#HvGvu8Ofmmy^sR)bHTi7g&kyZ-F?z?wvejW8sKQoKartv$0c+Qtf z6FV+10@72Ts{WdFg^>jSOkO`nx!BE%tt9=3V^%qTkHUj&enUo&qFjFm@9C_l zqD~-_!=muhYB0!dSLEh7=jW9c+WuLEdDUON!n*qfDi8tWTFFo%-3faA*42DoH1gsn z^ATbuiOUZ%@g058*zHO~@|(DEgy9p%4%PAMr(tkOg=4bXgJnX`Air-WMKg>Jm1f)W zH1z;+{w@sGFB}?9wXF8?3yUsY9U>`yu*`U$7=EDs{5Sf)fZtnp$dcvw|~?o z-@+&bg*$9EYLj)?)r#6RD5+7xP#zz#vf&OdSmZQ{#_rG3XnBPv?Z-;86IhNpIBB|# z^iYcXlcJj_$can{H_{LMX4M+Q`;MA(Q;Ki25&nC@}E*5@fOGv?NM%- z`W%-f+LXt`?CTPTjKtwlM#{A%+;QU#!zZ5?u6`Z=bcwKdvJ7W#XB`T#UZ=O9u8YEB zr~)CQWo`LwQHc4$kogO;%r1$^F25);^Jh6_Yz0Cgy#~2cUv{N6fhA@H>;vRGl9dbsSj#-41US;b@IM5cXu2!#@xu?U5=pvr4heSX30P z37ja*XAQ&XU>StFZk%{g9B!$j2Nm<=M*l3nNz+nH5|2zeE>;z7!j`@(l2jg)!ww4f z+weM;-(MxovmZS+R6XJsrsI!HhH&NcL*(@`I(R{_$itOJnSS~hkqu6)29Y1&*NbZR zFPGz+uSol!sROgT7?k69%2QcV8`G>unm&cTlo8pHAE`0y&JPP3^h`e8k#qE${5leO zvo$K(BlQd5NOMy{F_HoyGsw?)o6jIHfF+81L`z0Kp)g;?UpZ7zS9+zc`lb32GFsFt zEXaOQTltp85p&8DU0*~@SnG#Ea?&l!;}K z!=jyKWLR-NwHUGrG@11x?lN!C;t%C*VdSDWigVP>tPcG!YSJSq5Fy4+&8sDoq7wFk zqktUh<=+Z^Of)Y+A>1$yui$^~HFQTV1tzm2n-S_fKyOm5QSGM&b7ag?eD@mJ)M3Nk z@Q_%AkUKyAERjZL>|uskm(VG z*FdEqwr{f35cOU_9e)8OChlSXMOJ@DgD(3>HLPkI5w@L;*= z4KdJbsH`RuQ$nl}Ea`%TI)fE_-{Ay)2^-M_tlv(z~DsS?D~ z>lhvCm241{d9t0o;`Q}oLt(L~zJ+o)kQH5T`@(ewP&Q5qgQV9ziT{#qIr!}AJE)tRUSXW z@Di)jR5RycS0xBeaS?mF_t3r_Y5(Wu5U;X?P; z#^cjb9!Jp79px3Lmmg`GhW`$XHc#buhZjtcA98#uD7-7oiDzx-qSJ&Qm>R{L{Ue>I zk>%SQU3I4pj=*Xu5F#nlQ@vIP}bV_uG+R-QXidDl^p9$do~}`NPPt zhxv+;*~XOp$h}_VFsaBK5{mSGQ!Y?~k>&|W>sO|6G#ifr;!@%aLJ#WbMz59^JlI>h?SE zCyi`JX|Vd2FHpr?fk4}M1i}vbV<@!4eEEtJ;HV=UmY+re4T7n5r~^71ydbdfFjw8# zx$-a^)&l}F+P-+N;}N9eb*wYgJYOUtOg@3Dwd%|;67D^xRu3Gj)<&krb7VA5=&`vg z*)WG)N+)!!I{BbCxbLILw_wSR%@A|Lo;M~1O0214W@kPUX*#9@Pi_!)~2{B5?b8XA>8^H0wO z9e-Uenkx`^Fj64yTWWEX$0{h`F`jXR`(-(Mrk4c6?|;8*s2a|@}_*qO15_dmnG{5 zlSe;{LI)Xq&K%Tt;wbXQcx1UZ{5;#}-~AhTxLh^;aCg7F|CUpoQ6A^%@`;RP^kFzl=Fwewna=9|4Ih=n;@L%$Gc#c4eU)4zPu@~q|-=&RAzGg zwdQ7}?mSX}zoL=&ad$t{-{tXYOL#GsL07!=KI!p3-Sw^cgHe#IELu(ztqYv;G7D47 z(On)fe<0)FqbXeOkq@{lowUTN1H@k&fy<4MG;+}uv*d%vro))xkY$gT2GVfVL*)O& zK$x5bav9cXl>H?&*?Nh&kJ~hHdVm`%5QaUsP|)A+K~49=#oQoEKXdxjdXh?Q6o)-b z2(`)+Kx4juupU8-KqmL_X!u|^`^pVokw58iO^OWM30S*ii9FQB>kdyx^Lw50>TK{f* z%Q9sA)^GHW`VIY=9JO*Qja!Mm339vUyisux#A=aYH2|>)+&~^b>V<;;SD5IJ@e0eU zAES>|;ai{Qf;xfxAxq33V+BIQ9Wea|^QdbTWl(jR-g@OY@#*3+3A#Q7-+1{hGMdP$ zk!M2U?H3Z>6-*-^xH5$JR_OxP{K3k&$Q!ciq!~e;Lg~|+u&X2UruHg4yr&P$D@2kWMal%7|jts)wZePC3OQZZeD&h(yelhnK^MZ0s?9D4ai* zRT;(v(nNIQ4c*DqTO+XdRRowiNSLxQ9)YktTT6j-Uhygu#l{b#k24!LmYEV`VpeW( z>~D%+$77Ut84c>(Mx`DQ;Z0dCf`4oTP+t&aa!JrUmoH*SfHO=@W=>^iMjP|V0hH_| zT7)A&wqSSmTY;!C>j1rk8&Lx5(M}kR%2;f(dLVy>fnu^l9)~a*nU_-?R>CdC=EOA_ z`Nf^vHRQ+)jZxjNP|R8#NAhAuQ6)2d5KDo`A!SsUxTBCIsx#FgX5vd7Yl}EBP)VaG zkjM&{QG&0zPmT$v0b9z1UkcQRLA_8fnz2V8j{2Z5BL1%RF5VKjqnjRi2YD83AMc8= z_YW>++L=5U+B+ng5An|~3{aOvgEoT_<;Xsw@$|C8_I=bNFs=iXtEntTcnT$OgDBTJ zL7g`WH5D^fodupQm@nIJGJKYtJK7&dw&i?SEN-I~NwW}~=0kYr^KWiHML z;7)EL{S_m_6+OsD@}hpki9b>ttq3G{D>3JH9rDp?!^zLL3iGJnCY5Hb$h{(!o(zfj zqgPl|Kk%2z-6&)VCkJcV6}zVw+}pZ;`~9X7)mez(e@uuQ@jeF+Mi@nEnXkyJL*tl> zD8$nX^Cj2QW)0%^bt{sG7{u!;AOBEqMu01ju5X6|1_01SzbGI$^B`>N>mpa9k%KS` z?x&QKLt`=?cP~xM=<|tQnN`~`xSTe3ED&&ps+rn&J_xZDh|p!&|2ry=jxE0zI~2BD zjY4RY*6YQjh6ggaB@(#H387J+pt~eA?@{O;B}y!CU3_;~@~K3laCmn=?ca{N&TnVF zHZ2e^?d@%X*BwU`Iri`eyZh<0}ObtT`Gcpsv>ugq@{JoK;-#7O* z>?(QX7arzMdR^kXFbpngcx`V1$G1m?_k%-z*TNf?K(mJqN8AMk*O|qf9v1m*tU$mW zh)kDBg1bsZZ}nCZapsiIsxx>);1{Rq?5M?L%!_()8F$O*9=cw@W)OVUC?kx@vziO2+mBKB;0PfNrg<$rWZ zBhiuE6T(G1joyCW^mKdmB9Pnee(+)bG+y_F!PLV$C(k#lf1A81Be6~}XpEDI>sUSi zPI}L>I_b?u+EKr`sc-A3uwHQF-*dTJ1@wkm1-|C@;x}tQ>W9|;k*xX4BEH&wkBYk8 zq+Ozp-^7ybC@Vs>laiPMk(e2UoCC9;XmimG0Rf%d?A_^c)}E98ncPqQXtyK<{sdlD zBK;!}sAu5nEZ^&Duo$t4qBCyPDFBgeqSaYy4;mHYNmck9+i!$=tY>n}zvz)|M0puz zPGJrvw*J4yp}``AuKX~eL;Uxe;H8PfCfZ%oq_0U^4s%40^xcPL^B3!G#1v`KzI;%e zR~?y_(OH>+pGTDU$Mff2~X2BB;fSi=6p*n|=yK%1~Id&5^vh+NB&VczlQFfuRsy_vGWzz^y2GPw|oskqu>NXlOo!!qH zRmZBI3p$N@oH>BIqoxzH-Ujr5j29M2JcVksrOi=zazCn_skBEF*dQ44`bo^MI!md0 zkgI&5HlcD4=;5jHL3x=({94PXHOL!*axD1f$Yoh0q((>E z(576%h~Ic+VHCk6u0UYAOr`i)l*s*HD7^jIfONKBc-sCn2*wHofj+FupUfZ8INzj$ zhuP!|D!T08H=1B1pQ@M~uZa>T%3}!EWE!6j)fvb5xVOo5=e7EI?O5>GHhS{wj_!1| ze><^FJ^#^;Gi3xiaEGNFg*JKWWS>(Yh3NkX-OI9+myvr+N+fLHX`3Kwsr@L908-9W;%zTaCqjfa%_!JfenPg)XE`)69QoZ0x_%XgKXvv00|9ZRb8$W2$Qw{XVPee zCNw4havrS4t2JVhX$f@cI5P^AD}VdzGE5s%Vcz5Wl_)VY{yBU9Vc6p`N2L5i?n=hxjHW zY3FC;_0w1k)sbWFz2K(4L+hkW-yCM@RpZ0y8CyiNNrxI>&uSEfffoU?3(1lv=)A;P(HG23L7iG#-X86W15a9%-3^$dS=2MI#XV6TvZC;A%J@8nNqMQbusYBX z*BC8T1tMJ4mh~$2Ut$(1)U5uQXo{maTIXRUVW5!>o5){Wn$LY4O(B; z{StlC2-Enz$PIBb$}`mz?ouAlsjH8}xc0>~QTbvu$a7I)gWxzs?uUer41$RoMt;2@ zmkLFN?+!;Jwg97#y}J7u{{GYuFHtngdg&IIAV@D8>__MDBFl%|AEg! zWwzR0A2Z(=)Ww78W!;2O`k8QwJSTD?*0!eQ*(A<7PU;Fo8dXlF{gJ%uk?Ovs;Lylj zB5z$>ZFtk+qvn}bL43*TC?>*oL1r_8;7r1#km8m>mZ_L38hN6p{{?26HyizRzvUj^h}O}DZ8`&JKszX0J}L|oVUJSLxMMu%SMK(0@D`G@(rlhu6iaaJm)cC$~rL?jUZ%GlTjhP-Xs<716EGm9>jKD!x%xm9*4Ku z>Y$jry&L1F(d^ZzHNtp(SEs1MP-KgFg_-!VEY)bVYZ6x@4sw+{OfGHoPx6~i)o=Xh zF~@hOUs$NWtG<`z(*96>A^=e495&_t#Hp~yBgP0;Ms>^tA4FUF*9g(34w9(?k<@Us z@X-k2gE%Q815U$sE4}i>A9_Zg84>N5o&u2}FKIs0h_7ZNIqGv$7D%(K4vKZtrB~eK zvG#nG%dUKsM=x`X7kv%CrYI0uMTT7EVj`o_c9ir{$N%Ov`l#b##`;VBx9%Ih`&Yk; ze_gI_yr%H9Rm-D19_3I(8A+E+=}>OV;O)G|+UT-<60yB*9 zJ{V2`*w2e@-u%kr)d9>iy-eBJG!Ac+mD$K^_q>ce>S@Vbfk?y%LDqs_U1!4ZJ&L0? z0t=}LT{7Bi1mbdMK?)ChAC82ltx_P}MUuSuk^=f5X0lW08E#r!KrKif?Zho+GT9lY z$Mm)7fo@`AA>F^|Vlu%WC}S-JA~!LDb8><#)JQ=y)keILU*uV;Ojoy{JT_?zOZfX~ z6vR_uG%0W9ofAyi9Q$E(5_K3~Nrbc5XE8Ph-tAV@)yRE;I5erBGu)!ukve%-96D*> z8aEDcnUSN0*%Y)DmB@H$CXL=POiJA=OJ3vhQ^)7hqfZ%X>OUzc5ZOG*1oHX%%yeQD z)UOM!dXKO9u(!{L?G*i>K1b%FBtK&%!e?w#g%D5yLp=UakUgo}- zXa0fEY0LB<@Ej(kuH{IMRcfoe^hxx>fTq!Es!BnPfY>v=2pW3GwZ4^U>J|#2Q6owv zzP>avkzTV={E}>VHyX`hFTMceH4H@;j#iAG#~owT7Np~VaH9GUl(hvpd37`^?X>4) zbYhdeD~x1sB^}%7;2FU>>QxTv{96=UMk(*J5=_i#yF`KS1w+FsBUM(^z0xbJ2_EpW z2_2ur$}Y;|qhg7@V8i1tg-=TyO)+8B6GO&7K|^;&{oCYCrC7EyE4hhQMn=z@@In85 z10y^Sq}nEsUYDoF4=V2r&c)e1v2$_aoXN$e zFAdfeCc6zu_tl{|b0%M}35D|mC`LO2tMX~>h=~>a$Ry#97ThffI$ks|?`~F^$+01> zTGJ~$X+P?5N1>m(QHL(3na7wMenJPQJ1q6(`)8&fe2g+H_nsoWiuFHeNS` zPE*6Z6vpkkdF%Zws3SSr;8i^&e`t8o9z}FfbD|$h&C=3Q%=g?we1S?w$_o=Y2T)$j z%{~jMnO!{k{J`XMEWGUL6P%nkNA%G7*)}AX@#T9FZA0(Oo#E@ySsM3nQf47ZHUyuDa))UPlB?&d5NZIs*# zKVy%kD-cPqGgN&<4}M>198A+sMHxbdUdKCjn&9I}6F%5={c{sM!Jz?N22;a7DU5M? zQ)eW-Mx2SxFHO^gJcg1Vq4R9Ar-z&Ho*?a^E7^d#i2Vgm^PXtR7byx{7*I2#al}Uy z`ji#z2D6baX*ruYuRxv}as0kz&IWVJb2MxxYu5!w{o9XjB!5?38wsufNLFmoA$X|4 zJMt&;TZWUR@0Si(WF$>MWa-E*a+Lk8OpuS~`w-j!O)|dVl%tv4MYIpQ%TM7qNotKJ zc4M1VGG$x7RHQic$k44=cYS6Y8iGrsX{12le{&{)Pt%Q`3coLf@)e4W@x(EO?#h0n z_2UlXl{8HVgYgRow?F&PzvUM${xsIRp!gpn?-mJytA%j|ZE5~FHgEPBObmth1E~7< z%E^oU=*Gx#IiYn!Dmpg&JsJHz=NDB(|J7J9aV>Guj+#A~|5k*EDuq@9v#V0>wjY8mk%~}U+yc%lCgBKqAW|m+y`cvHNs=|UFOpfG}(V`zO z`kj7bSsA!4i(a(Me*yF|3kt0ynQ=ly2=FutqErCmhhFc`sYvmTd|rvWA`DGxECD_p zCGv9$S?I8~vq$qNu(G^zuxX;<^75O-GfHz0roa59Tiu0lvCCYh424xuggo&Fq)7@j`vW04y0Km>4uN1ZYGa9 z46)@lPB|azc2oQs9vv{s+Y89#M3IdtEtK(4XdKUI?y1TQ^*&4B&qXl9Oz8#TA+w81 zgVdU&|K%XKyoQly`Fd*0SEs6H$*>hoIU`f1(eSNJro3iKgTf=Dp%YE<;o%*GdqK6J zUZFLbNag&D{^erjS!#LuwwqH`zlU4ZlzttLcpfPZRy(F;Go?`~(6D_;ei)et;E#4z z9nYq*Ka>6eWu-=7v)%>%_k;HQ^>l>2!T%XO+;h9b(NU1=Keabm!qeNVw%d{>;cfOp zFJCRIU7>R{X$K*d`5ln?}5MBCFdd!*8IIUF!MEuJ}VmJm6 zc{iCnK9J3U_s1a9bX3xjzo#~#+M)hKhDL5+JW?@^$+>k=B2po>pIR@ZswTigu10Cy z6GGBfeiprPBxpT~5S_HrnWp zD~%H-mvyn-kFWb9qw|r`arN(3c$1DR7GCf4D4~T>LQ{2d z8{rw1Z$k&;Z`8ZQ+a}33@@OiQ#ROI2_h9lrmI7|%Kgu_JJWw3SXqwBlqQK`FCYOyP z1#v8yEDa<1Yy6Dl>eg#bu~9q|!3|$hY8pXIJY;hP%H#XIQ&k?qCcXcgVzO(F?xkp8&xWk_#)%5)UUJh8?Rc692bBnkQ=4&>)<2;%YKj2 zK(&>#nD=HzMgT9-nExk{xFQlk4kvnD@$1E zv%d)qdR3pGVsm4bpb&oHaHyIr^)pG2DNfA{=wy5U0Qvq-slVDHtfz)d{$!diZ9GSv z$*AwQet3nE>QeZ*)i1p1{*HPc)7!T?56SGs4l-wo%3$-F6K^AMBO~xJn-QJtg&Nlw z)`H(JmP%@4WWEm_p7&dj|3H>=tw4nH;{2E|;;v8s|Mt!VN~-G2H9}NoT%tk) zVo>pbdTe)D?5eK$-I7LVyOy_AzpDDbbGl#Ed-s0tyT5zue(!GIZL`U4Rc=WVMQ-vo zbO!}`Qjy!$ygZj}Sa)Wf$Ai^;X#2Am(49c#_+k6^SkIolf*XguydBFsJ*1U*FXiL) zSoLwjJ-ZsiJ)zWdC@1;!X1uI;;byLtdPb0Y=(U=zlZzcBgM@?q#&zLocAiOr=#tqIRNAflh<;5${8v_(zZFFPDB@7@EJM!8QOGJK z!*R}RZhKZ@Je(X_iK{6eClZfrj{b0R-3%~{$Jv!gceCM`GIcO{mW(bK&;Ek(--$AO zF=;6~UdC*yrz=Tkdl;aSo&~DpLQ1QmS6)7evbyPtHsuB?gLd@Z0Zh`;wMf0Yy|L8q7Z8rQtGK&V8E(+Znw0R;RUImDCk~2~A`c?ze!v|@kL(J39MJ^`uZ?{xRVD&*-=PI|Kdtn8 zcw>LIOz^U|Sp)SCyr{j3u02Zyc3pdZTW6)@RdSEtUSAed-iB`bRuVqOFn^<*T$dhX zQz<81-a8OuI!lVC?A6{uCGiug*Qu_;2&_E=^LTarw=n&;ZZn{)XUC(&f1t}2esv+9 zYxX|N`Omn)zPm8%%de0w4DZ}Uu<-0WjKB%yDw}pS`LVT-clYB%Y8p-br`oSad3Ku& zu?FZmUzH3SAyFazF14xM72B$Q+@<6`Vh6pv9l@X5jWf_X1Q@QQyuC`(tt18Rcb{8eV>thURPRwqoG8MO=EWH5 z>9kCzhiREcp-WO^z@#=0jL3-;o||1^3Xy`@nJsK&*rsPehi2O`=h-g!cG`s>)z;|N zgTl5cx^?HX+jHD4GHapYY~k}F#x~~QN~5_eet;o(bsClQXR-@zZ_+suW73D3Y%2ox zD5TqC9ZlYr@wWCN74!=>&|W6Z75A}s`oIb=GTyaqTdR#CPlh|4;O<&W8}hjY9X{am zTa=z>nBDd?FA_^C6dOq}m~B9Z-B2p&ohcJL!L2u(zP+Z*Y{KW2=;+vejE{S5wKsp8^Zdm;cOLn8kDqasIJI>9qSIP(hU8FmWd1_5 z2Wfr>x%c4JVmfIK>ohZ3npWMydYE?6j6yXd942qcOC zEvBuosIE(S1jfX@Fzn!0L5w4I+FqwpbmK_RGsEaP=54;@uKShMDB)|`fVfP;h@6QL zxi(CO0pUsJ?;jjj*F_lJ6jua9z&`=HSGQq{1m|#aE4B@Gt*0$818&QJp0w!P?79}L zMGLK|54qlg)6*rHjcu;imu_Vp6%(j=1L9JO5gCUOxvJt+cM_1dj3=18{e!jc!aE70 zOClfwA|L`^6Q~OV;*x_Axd0<_$=6Bq{|O`VczPq&A@nQ1<*9$^wH1Sb=mEI)9?KhA zYidKgrJPHV+De_Y6#)^5fmX8JML+~~&Kq!J9PHa(DxnyUL-0v-QK>MxCjuhS7zFCpfVk|# zh|EgoaaY1h-k{m(x^8IA%wTLKF&j$-O-qIG-mM_q1bCcLK{y2w0THNU0(EUbT)yG6 z<{6C0o*0q0g61P{-6^IBU&EXC@A(|LyM&;6#S*W=eJtQ{9Y*BrSagt35fFizAW-)P z#N~o>e8&2Y-RZdx<;w=+@l&P>Kazi=;Nz+$GT670dHe55<6uOtY;Cs7Dh;o2A|L|w zMWCSuL-WgQW4xhGqg(2SA+xa==V}L4+W}ulohyp&T3Z;&#hm+96oEh#q8pg~b_T6H z(CQEY5oiM_Kznc`7KRiP0TC!kprKkI>ypULv(s^%?G_vcuJovAR|D{zuG$x2-SkBH87584 zJbR7y!~j2XVR0V@dMsRmPoMVlK!p_n5$I9^4QoIg_15{eKl?CD=MB2#&THjzhzj#@ ztk5xTFYIz*Yvn!@1`fiIJmgnjSj@sH<)Ly)>6e_si$LuWXs9l%j@i>R#Qu~{+b&#x zx#K-Q->GO1VA=aCRJw=wiI?QCZa;R6!4$tb!eV6hGqxONw?e;h6B<@wfX4zR8XS_8o$CVVuP7oQ0%Zuu zfUE<^Ef(Yr`f3bGxV0XhEn>g*BkExb%)-Vzt@a#Twq6HSMISMyZUN^Jt~FCp1O*ZS z5vVBw4c&#+F{c;+zLx!|_s6Qf-|?QW?+iY(PxD`y4m+NTx2756*8Uy#3?GRx_|z8z z0+aSlIX%dPg>(JJRd^8)f#efV3#5}g!sBdjoCe-a1-M@)xBXpmd#-o0BfTB$ZGRDy z6}okv?MGpmMeD4vM~GkTi2GmZUUqCfbnW>~yTXe=y%LZCDU!3U<2c2^ zAfue`tK2Gcv$Wh+ub~crx3YloI^M)D#gKd!N>~>~rX?~NgL-0^$74utWY@=EHR7(1 zVYn%-2qcMs42X~1pyE7%x94jyB;9>P>oh18=yGI@<4yeTP6H(PqU+vcf`Hpt>Znj^ zy4h-J#)k&l+)#29Sp-C&O9;q-_{3h=GrSGIPiFi0j9M)C9Oew4hR5KSiUmmG^@0(Z zZ@0qXm@xWL0JC1oCZ-12Wf;|L7+H!d0wPdd0x}>0cvE?LZOwKH=c}kdzZF2ldQqr2 z+qf_p7a-^10_4N>ZYKCk!H6^$y0eatK+U;y>mBJ0wnY)hk_ZtHfhrJ?0SN&)zsR=F zX6<&mqV^6UW^Gh5<3s+=OAWEfYmCa;drAG^j49n&H**d(CgRdZ+EEku$bg zhX{y3k_pIwgv=dRfzlc|o%7|?cpHZl_f>Ru1Ra6zvo+G0! z#i!IGMZ+ZIhI3oOH06oTphTe(LO#TpoKLsb&1;NG6k;Vq1VkVL0+Ia4BhaI+M9>=9 zB+CkR;GRZB%2pu}9iF|2%!%p2HiODEv10^GcRrjH-$B~DMU-+kK;pXfPNiG#uaU`= zAQ2FOPy}Q^Lg6bNnL+k{VvzkNs`~Gi4(ao*XRncQmtjcmP3l{@+wPYxy_p!1UOrP- zH24~$c^A$dFT{|%Su~(-h=2%Gg@6o5%$TV_-J-i=F(MObGW3r0aV*56QoTGq$fj~m z+!~7fV%stMCEad$>mOyhDYS0O+}qO|vUa4#om582MY2So+5}`kszf~hzID%jVCMk* z7Z{a|qI@jIV+ns(rMlalN!v;`ztnoNL*WdJ$X-#V*~xA7R0q6A| z@7X-|*S&?7$z2$emi6vc@eAW38|6MLbZH$|g~v9N-btwx<3ly-KEDc#^av3ML_h{4 z5Vm+CVL;Nkg}6v>vk83OE}o|_`0U0E%pPF>_bHiy_9WlCWKD_N;gBag7bu|}QkG*y z8b9M%cQ8&e2}825TzK6V0TC!qKnA2dqpF9%cr>-mx8tc;&*N{KI2E7(yvI=fgFItq zdLw(n>u0Otd|pS{g)XhE{ekPdb-WkH_kyv^+f!^YJT2Yb?yn{9$8qqGOc4k}Kn5fX zy_n+`W-n#!C=ALkd3zoZ^W)rN!%4GRX0X5I^Skzquj35P>?ruHxT^{1!N{O zxgKO%@ZaNp?wZA2`9G9h8_$?5mBK3#Nf?=teHR+pmu#WN@+9l<5T1r6d&Z!QeaV`= zh_M^8Z`t>eHA{prM9iZ}cu(&?@!p^N$Md=FbDit@e$Vy&e9nCxd_XJ0_{N)GTfANCS@Xx_im+7EToymp@?vHl#LRh-xpW@z3cSlIbUw$=jk0XHv4&_b_4BFCzE?z#gTGU2V?(1%4cUq$0Kv+=$)>f=rFfK3G#4g~U-`E+b0G*3 z)+kL-C!#I}gk8SIHY z>f>MVayc=g!*s@Knf{7MCewr{h=cMVs-Z4?t*K);M90psB`ZwGY_H{|cSa{Icw;L) zTgqnqQsrt8rZetfm{>6&WvG)V?w8!K#?La~UD$eUU7}H&UeyAcpbnzIO=FGXq&bni zcLaHGyAH*e!(4EURZ#w5PmRc$xA^bJ57#PPCw~7E4!g5>!o5hHvX6VOW?iD1KT1P3 zR5{lMPobb!c?sV1;l`|;6|d}D*?xW@ka*8>yz^W+;47DIr6cInp9b0|CiYU?Gb8XfKZ zK(dtvGWVDZ_d|ZY@LLNneU$<+e)@`4*#I;BKMV|C>$d(PyRe~ULByg{R<;VRtc)eu zqK}>{@eHk+ns-^3J+;1Wz`ASZi*~L!uGD0mXMfRflb_z~5M_gcND0HO9sQTw0x#e@ zY7I#xx11fq`!Urg3o)d6sr%*TQEd+ZDVnn@!3g=} zB0G~`T1R1JiQ#*_g{pJh-(a#40vs+`}a)6IerKW0C|DBv~pixR~)vqXj<)cZI)N9G(q2wiL%JStfy z`;%+6?d}-W>PksoPZTl_)I>sv+XaIS&>!E)2wGbUdBLi^h=MdIyma{&i7Vy(wq5OY?#m21HD34S`8#>0wF6su|HIkU*4n$=ciGR*R4Oy$f~2Qr7U>l@pV z`ku7hRoxJ#i5vfekU6Z6z^UKQY31pRqxP+N2oY2E>$Q!|R~fyX$A55jrpYBKYa*e_ z>}|zG7ukyLK=As(Ib!eDt`A#}V_Um%B-&}A|MWxd*k7VN`lcA@1mtB*p%5$*8T7&f z`~h}dlDFl`@nhKqeNV?m|6|HL6sGq+61mJ&QT@l91cas~ub5HG$i>M0O~HvP)_DB+ zZQbrp2_?LzCSWHA6=3BCF7%q{n>=6# z+K{-vjhKx8e8tgJKrA+yR&zYcgRX9rzUD__nqa4kelcP8L~qK7d1=&J_g{lXTt=hs z)to^sLXX?q)0hseBL}C@O%hXNn=)_##Tc$O$w@c0**jF{jX^a>uC)^)zNB|mU+=?C zSS}dMIkK9Z2c*F-7Qk+o@~O?8hZxhey(6$E6PjvE^>BU={cikDVp$1GCnAKG!rJW+InQpHvK$q{)XP)ekAyJS z&sca2{hM97UY|N*t(hy}sh=&lz9^&NVaf4B2(UJiLDPv}9i-xH-K<(oK6j2{V$9nU z(_-xb2b40-KY{K-(-sh`k%W+j3IPqcx;k@xAbI1xax&4wxZqPI%0wxvd!JG}$l#k( zmoK(`&?5dSgb6AP%3iK4fpFO9th=D()tls5F5H*MfV+%hv5e#Qd9zi@VQVQD<}Z1Bo`4GKd; zIX3emuZri_f!11wDn^3zxGT?~8~dWPq= zt={b=iiqRoMD_OL4uct`Vs&{vVvKzf#umB^11lu47@qhh>k*ewHm6GmyvDeSO4%d$ z+&@52|O?x;eqccYy_6)iCm=o^NY4OWL z;w2Vd-Mi0hq(V38o5>!r*Y>dV?hbY1i7&nm7np!$H8X);4Fd*oju^RwzF#rx#0 zJGbY&pZU?hr5m`cmxP8HKm~5(h?nY0>gxs-QwM|G#7yJcu)moZBUiqjkwWAp2tvj{ zJ`4H1jfQ&{dT{G?sQTkSZLUo3ZdB@i)r$mz#U1P;_8N0`p{hsHhOb<`Z&fCN3*yn_ zNsKL??Oi>P@fQ5x6A0v!FdfO0v82CAtC{{ZJ&Cisffu@yi`(&3-dsXl_Bx^z7{fw} zcLjvhq0?!D)s~!>ESpZ~JNKR-oLmYOs)MjY%_%>n`4GASCJq4B#g!Kfsv4d+6n%Ef z7)Y91(4rntd={*P3*{^O7D%KjwBkHSW8FXdN0*DK2Rt=+p43-6i$^!#5lbK{1;06T wzu~eDKUBAj9VsSq=vf2a{eQnNx?#JgZjz_bJRF}*&VWZx%Sf~Au3hMV0XZj|=>Px# diff --git a/img/bonus/bpd/logo_cashback_blue.png b/img/bonus/bpd/logo_cashback_blue.png deleted file mode 100644 index 03ba047b77a893552e8ea86c4078ce4e88ea7c4e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9473 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 diff --git a/ts/features/bonus/bpd/api/award-period/v1.ts b/ts/features/bonus/bpd/api/award-period/v1.ts deleted file mode 100644 index b63dfb1432b..00000000000 --- a/ts/features/bonus/bpd/api/award-period/v1.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { - findAllUsingGETDefaultDecoder, - FindAllUsingGETT -} from "../../../../../../definitions/bpd/award_periods/requestTypes"; -import { bpdHeadersProducers } from "../common"; - -export const awardPeriodsGET: FindAllUsingGETT = { - method: "get", - url: () => `/bpd/io/award-periods`, - query: _ => ({}), - headers: bpdHeadersProducers(), - response_decoder: findAllUsingGETDefaultDecoder() -}; diff --git a/ts/features/bonus/bpd/api/backendBpdClient.ts b/ts/features/bonus/bpd/api/backendBpdClient.ts deleted file mode 100644 index 314f15ecb20..00000000000 --- a/ts/features/bonus/bpd/api/backendBpdClient.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { createFetchRequestForApi } from "@pagopa/ts-commons/lib/requests"; -import { Iban } from "../../../../../definitions/backend/Iban"; -import { InitializedProfile } from "../../../../../definitions/backend/InitializedProfile"; -import { PayoffInstrTypeEnum } from "../../../../../definitions/bpd/citizen/CitizenPatchDTO"; -import { fetchPaymentManagerLongTimeout } from "../../../../config"; -import { defaultRetryingFetch } from "../../../../utils/fetch"; -import { awardPeriodsGET } from "./award-period/v1"; -import { - citizenDELETE, - citizenPaymentMethodPATCH, - PatchOptions -} from "./citizen/v1"; -import { - citizenV2EnrollPUT, - citizenV2FindGET, - citizenV2RankingGET -} from "./citizen/v2"; -import { - paymentInstrumentsDELETE, - paymentInstrumentsEnrollPUT, - paymentInstrumentsFindGET -} from "./payment-instrument/v1"; -import { winningTransactionsTotalCashbackGET } from "./winning-transactions/v1"; -import { - winningTransactionsV2CountByDayGET, - winningTransactionsV2GET -} from "./winning-transactions/v2"; - -const jsonContentType = "application/json; charset=utf-8"; - -export function BackendBpdClient( - baseUrl: string, - token: string, - fetchApi: typeof fetch = defaultRetryingFetch( - fetchPaymentManagerLongTimeout, - 0 - ) -) { - const options: PatchOptions = { - baseUrl, - fetchApi - }; - // withBearerToken injects the header "Bearer" with token - // and "Ocp-Apim-Subscription-Key" with an hard-coded value (perhaps it won't be used) - type extendHeaders = { - readonly apiKeyHeader?: string; - readonly Authorization?: string; - readonly Bearer?: string; - ["Ocp-Apim-Subscription-Key"]?: string; - }; - - const withBearerToken = -

(f: (p: P) => Promise) => - async (po: P): Promise => { - const params = Object.assign({ Bearer: token }, po) as P; - return f(params); - }; - - return { - findV2: withBearerToken( - createFetchRequestForApi(citizenV2FindGET, options) - ), - enrollCitizenV2IO: withBearerToken( - createFetchRequestForApi(citizenV2EnrollPUT, options) - ), - deleteCitizenIO: withBearerToken( - createFetchRequestForApi(citizenDELETE, options) - ), - updatePaymentMethod: (iban: Iban, profile: InitializedProfile) => - withBearerToken( - citizenPaymentMethodPATCH( - options, - token, - { - payoffInstr: iban, - payoffInstrType: PayoffInstrTypeEnum.IBAN, - accountHolderCF: profile.fiscal_code as string, - accountHolderName: profile.name, - accountHolderSurname: profile.family_name - }, - { ["Content-Type"]: jsonContentType } - ) - ), - findPayment: withBearerToken( - createFetchRequestForApi(paymentInstrumentsFindGET, options) - ), - enrollPayment: withBearerToken( - createFetchRequestForApi(paymentInstrumentsEnrollPUT, options) - ), - deletePayment: withBearerToken( - createFetchRequestForApi(paymentInstrumentsDELETE, options) - ), - awardPeriods: withBearerToken( - createFetchRequestForApi(awardPeriodsGET, options) - ), - totalCashback: withBearerToken( - createFetchRequestForApi(winningTransactionsTotalCashbackGET, options) - ), - winningTransactionsV2: withBearerToken( - createFetchRequestForApi(winningTransactionsV2GET, options) - ), - winningTransactionsV2CountByDay: withBearerToken( - createFetchRequestForApi(winningTransactionsV2CountByDayGET, options) - ), - getRankingV2: withBearerToken( - createFetchRequestForApi(citizenV2RankingGET, options) - ) - }; -} diff --git a/ts/features/bonus/bpd/api/citizen/v1.ts b/ts/features/bonus/bpd/api/citizen/v1.ts deleted file mode 100644 index 7e2b7d2835a..00000000000 --- a/ts/features/bonus/bpd/api/citizen/v1.ts +++ /dev/null @@ -1,117 +0,0 @@ -import * as E from "fp-ts/lib/Either"; -import * as t from "io-ts"; -import * as r from "@pagopa/ts-commons/lib/requests"; -import { CitizenPatchDTO } from "../../../../../../definitions/bpd/citizen/CitizenPatchDTO"; -import { - findRankingUsingGETDefaultDecoder, - FindRankingUsingGETT -} from "../../../../../../definitions/bpd/citizen/requestTypes"; -import { bpdHeadersProducers } from "../common"; - -const deleteResponseDecoders = r.composeResponseDecoders( - r.composeResponseDecoders( - r.constantResponseDecoder(204, undefined), - r.constantResponseDecoder(401, undefined) - ), - r.constantResponseDecoder(404, undefined) -); - -// these responses code/codec are built from api usage and not from API spec -type DeleteUsingDELETETExtra = r.IDeleteApiRequestType< - { - readonly Authorization: string; - readonly x_request_id?: string; - }, - never, - never, - | r.IResponseType<204, undefined> - | r.IResponseType<401, undefined> - | r.IResponseType<404, undefined> - | r.IResponseType<500, undefined> ->; - -export const citizenDELETE: DeleteUsingDELETETExtra = { - method: "delete", - url: () => `/bpd/io/citizen`, - query: _ => ({}), - headers: bpdHeadersProducers(), - response_decoder: deleteResponseDecoders -}; - -/** - * @deprecated - * citizen ranking (super cashback) - */ -export const citizenRankingGET: FindRankingUsingGETT = { - method: "get", - url: () => `/bpd/io/citizen/ranking`, - query: (_: { awardPeriodId?: string }) => ({}), - headers: bpdHeadersProducers(), - response_decoder: findRankingUsingGETDefaultDecoder() -}; - -export type PatchOptions = { - baseUrl: string; - fetchApi: typeof fetch; -}; - -/* Patch IBAN */ -const PatchIban = t.interface({ validationStatus: t.string }); -type PatchIban = t.TypeOf; - -type finalType = - | r.IResponseType<200, PatchIban> - | r.IResponseType<401, undefined> - | r.IResponseType<404, undefined> - | r.IResponseType<400, undefined> - | r.IResponseType<500, undefined>; - -// decoders composition to handle updatePaymentMethod response -export function patchIbanDecoders(type: t.Type) { - return r.composeResponseDecoders( - r.composeResponseDecoders( - r.composeResponseDecoders( - r.ioResponseDecoder<200, (typeof type)["_A"], (typeof type)["_O"]>( - 200, - type - ), - r.composeResponseDecoders( - r.constantResponseDecoder(400, undefined), - r.constantResponseDecoder(401, undefined) - ) - ), - r.constantResponseDecoder(404, undefined) - ), - r.constantResponseDecoder(500, undefined) - ); -} - -// custom implementation of patch request -// TODO abstract the usage of fetch -export const citizenPaymentMethodPATCH = - ( - options: PatchOptions, - token: string, - payload: CitizenPatchDTO, - headers: Record - ): (() => Promise>) => - async () => { - const response = await options.fetchApi( - `${options.baseUrl}/bpd/io/citizen`, - { - method: "patch", - headers: { ...headers, Authorization: `Bearer ${token}` }, - body: JSON.stringify(payload) - } - ); - const decode = await patchIbanDecoders(PatchIban)(response); - return ( - decode ?? - E.left([ - { - context: [], - value: response - } - ]) - ); - }; diff --git a/ts/features/bonus/bpd/api/citizen/v2.ts b/ts/features/bonus/bpd/api/citizen/v2.ts deleted file mode 100644 index 8106223ef77..00000000000 --- a/ts/features/bonus/bpd/api/citizen/v2.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { - ApiHeaderJson, - composeHeaderProducers, - MapResponseType -} from "@pagopa/ts-commons/lib/requests"; -import { - enrollmentDecoder, - EnrollmentT as EnrollmentTV2, - findRankingUsingGETDefaultDecoder, - FindRankingUsingGETT, - findUsingGETDecoder, - FindUsingGETT as FindUsingGETTV2 -} from "../../../../../../definitions/bpd/citizen_v2/requestTypes"; -import { bpdHeadersProducers } from "../common"; -import { PatchedCitizenV2Resource } from "../patchedTypes"; - -type FindV2UsingGETTExtra = MapResponseType< - FindUsingGETTV2, - 200, - PatchedCitizenV2Resource ->; - -const findUsingGETCustomDecoder = findUsingGETDecoder({ - 200: PatchedCitizenV2Resource -}); - -export const citizenV2FindGET: FindV2UsingGETTExtra = { - method: "get", - url: () => `/bpd/io/citizen/v2`, - query: _ => ({}), - headers: bpdHeadersProducers(), - response_decoder: findUsingGETCustomDecoder -}; - -type EnrollmentV2TTExtra = MapResponseType< - EnrollmentTV2, - 200, - PatchedCitizenV2Resource ->; - -const enrollmentCustomDecoder = enrollmentDecoder({ - 200: PatchedCitizenV2Resource -}); - -export const citizenV2EnrollPUT: EnrollmentV2TTExtra = { - method: "put", - url: () => `/bpd/io/citizen/v2`, - query: _ => ({}), - body: ({ optInStatus }) => JSON.stringify(optInStatus ? { optInStatus } : {}), - headers: composeHeaderProducers(bpdHeadersProducers(), ApiHeaderJson), - response_decoder: enrollmentCustomDecoder -}; - -/** - * Request the user ranking + milestone information, containing the trxPivot - */ -export const citizenV2RankingGET: FindRankingUsingGETT = { - method: "get", - url: () => `/bpd/io/citizen/v2/ranking`, - query: (_: { awardPeriodId?: string }) => ({}), - headers: bpdHeadersProducers(), - response_decoder: findRankingUsingGETDefaultDecoder() -}; diff --git a/ts/features/bonus/bpd/api/common.ts b/ts/features/bonus/bpd/api/common.ts deleted file mode 100644 index 724ce4d892b..00000000000 --- a/ts/features/bonus/bpd/api/common.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { RequestHeaderProducer } from "@pagopa/ts-commons/lib/requests"; - -/** - * Produce a base header for the BPD API requests - */ -export const bpdHeadersProducers = < - P extends { - readonly Authorization: string; - } ->() => - ((p: P) => ({ - // since these headers are not correctly autogenerated we have to access them as an anonymous object - Authorization: `Bearer ${(p as any).Bearer}` - })) as RequestHeaderProducer; diff --git a/ts/features/bonus/bpd/api/patchedTypes.ts b/ts/features/bonus/bpd/api/patchedTypes.ts deleted file mode 100644 index 55475e84a8c..00000000000 --- a/ts/features/bonus/bpd/api/patchedTypes.ts +++ /dev/null @@ -1,49 +0,0 @@ -import * as t from "io-ts"; -import { enumType } from "@pagopa/ts-commons/lib/types"; -import { CitizenOptInStatusEnum } from "../../../../../definitions/bpd/citizen_v2/CitizenOptInStatus"; - -/** - * patched version of CitizenResource - * - payoffInstr and payoffInstrType must be optional - */ -// required attributes -const PatchedCitizenResourceR = t.interface({ - enabled: t.boolean, - - fiscalCode: t.string -}); - -// optional attributes -const PatchedCitizenResourceO = t.partial({ - payoffInstr: t.string, - payoffInstrType: t.string, - optInStatus: enumType( - CitizenOptInStatusEnum, - "optInStatus" - ) -}); - -export const PatchedCitizenResource = t.intersection( - [PatchedCitizenResourceR, PatchedCitizenResourceO], - "PatchedCitizenResource" -); - -export type PatchedCitizenResource = t.TypeOf; - -/** - * patched version of CitizenV2Resource - * - technicalAccount must be optional - */ -// required attributes -const PatchedCitizenV2ResourceO = t.partial({ - technicalAccount: t.string -}); - -export const PatchedCitizenV2Resource = t.intersection( - [PatchedCitizenResourceR, PatchedCitizenResourceO, PatchedCitizenV2ResourceO], - "PatchedCitizenResourceV2" -); - -export type PatchedCitizenV2Resource = t.TypeOf< - typeof PatchedCitizenV2Resource ->; diff --git a/ts/features/bonus/bpd/api/payment-instrument/v1.ts b/ts/features/bonus/bpd/api/payment-instrument/v1.ts deleted file mode 100644 index baad7fed209..00000000000 --- a/ts/features/bonus/bpd/api/payment-instrument/v1.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* PAYMENT (status, enroll, delete) */ -import * as r from "@pagopa/ts-commons/lib/requests"; -import { - ApiHeaderJson, - composeHeaderProducers -} from "@pagopa/ts-commons/lib/requests"; -import { - DeleteUsingDELETET, - enrollmentPaymentInstrumentIOUsingPUTDefaultDecoder, - EnrollmentPaymentInstrumentIOUsingPUTT, - findUsingGETDefaultDecoder, - FindUsingGETT as FindPaymentUsingGETT -} from "../../../../../../definitions/bpd/payment/requestTypes"; -import { bpdHeadersProducers } from "../common"; - -export const paymentInstrumentsFindGET: FindPaymentUsingGETT = { - method: "get", - url: ({ id }) => `/bpd/io/payment-instruments/${id}`, - query: _ => ({}), - headers: bpdHeadersProducers(), - response_decoder: findUsingGETDefaultDecoder() -}; - -export const paymentInstrumentsEnrollPUT: EnrollmentPaymentInstrumentIOUsingPUTT = - { - method: "put", - url: ({ id }) => `/bpd/io/payment-instruments/${id}`, - query: _ => ({}), - body: () => "", - headers: composeHeaderProducers(ApiHeaderJson, bpdHeadersProducers()), - response_decoder: enrollmentPaymentInstrumentIOUsingPUTDefaultDecoder() - }; - -const deletePaymentResponseDecoders = r.composeResponseDecoders( - r.composeResponseDecoders( - r.constantResponseDecoder(204, undefined), - r.constantResponseDecoder(400, undefined) - ), - r.composeResponseDecoders( - r.constantResponseDecoder(401, undefined), - r.constantResponseDecoder(500, undefined) - ) -); - -export const paymentInstrumentsDELETE: DeleteUsingDELETET = { - method: "delete", - url: ({ id }) => `/bpd/io/payment-instruments/${id}`, - query: _ => ({}), - headers: bpdHeadersProducers(), - response_decoder: deletePaymentResponseDecoders -}; diff --git a/ts/features/bonus/bpd/api/winning-transactions/patchedWinningTransactionPageResource.ts b/ts/features/bonus/bpd/api/winning-transactions/patchedWinningTransactionPageResource.ts deleted file mode 100644 index 1ea68522929..00000000000 --- a/ts/features/bonus/bpd/api/winning-transactions/patchedWinningTransactionPageResource.ts +++ /dev/null @@ -1,86 +0,0 @@ -import * as t from "io-ts"; -import { DateFromString } from "@pagopa/ts-commons/lib/dates"; -import { DateFromISOString } from "../../../../../utils/dates"; - -/** - * We need to use a patched type because the response contains a date-time translated with the codec UTCISODateFromString - * that fails to recognize the received format (used instead {@link DateFromISOString}) - */ - -// required attributes -const WinningTransactionMilestoneResourceR = t.interface({ - amount: t.number, - - awardPeriodId: t.Integer, - - cashback: t.number, - - circuitType: t.string, - - hashPan: t.string, - - idTrx: t.string, - - idTrxAcquirer: t.string, - - idTrxIssuer: t.string, - // replaced from UTCISODateFromString - trxDate: DateFromISOString -}); - -// optional attributes -const WinningTransactionMilestoneResourceO = t.partial({}); - -export const PatchedWinningTransactionMilestoneResource = t.intersection( - [WinningTransactionMilestoneResourceR, WinningTransactionMilestoneResourceO], - "PatchedWinningTransactionMilestoneResource" -); - -export type PatchedWinningTransactionMilestoneResource = t.TypeOf< - typeof PatchedWinningTransactionMilestoneResource ->; - -// required attributes -const WinningTransactionsOfTheDayResourceR = t.interface({ - date: DateFromString, - - transactions: t.readonlyArray( - PatchedWinningTransactionMilestoneResource, - "array of WinningTransactionMilestoneResource" - ) -}); - -// optional attributes -const WinningTransactionsOfTheDayResourceO = t.partial({}); - -export const PatchedWinningTransactionsOfTheDayResource = t.intersection( - [WinningTransactionsOfTheDayResourceR, WinningTransactionsOfTheDayResourceO], - "PatchedWinningTransactionsOfTheDayResource" -); - -export type PatchedWinningTransactionsOfTheDayResource = t.TypeOf< - typeof PatchedWinningTransactionsOfTheDayResource ->; - -const WinningTransactionPageResourceR = t.interface({ - transactions: t.readonlyArray( - PatchedWinningTransactionsOfTheDayResource, - "array of WinningTransactionsOfTheDayResource" - ) -}); - -// optional attributes -const WinningTransactionPageResourceO = t.partial({ - nextCursor: t.Integer, - - prevCursor: t.Integer -}); - -export const PatchedWinningTransactionPageResource = t.intersection( - [WinningTransactionPageResourceR, WinningTransactionPageResourceO], - "PatchedWinningTransactionPageResource" -); - -export type PatchedWinningTransactionPageResource = t.TypeOf< - typeof PatchedWinningTransactionPageResource ->; diff --git a/ts/features/bonus/bpd/api/winning-transactions/v1.ts b/ts/features/bonus/bpd/api/winning-transactions/v1.ts deleted file mode 100644 index 93c1316b7b0..00000000000 --- a/ts/features/bonus/bpd/api/winning-transactions/v1.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* TOTAL CASHBACK */ -import * as O from "fp-ts/lib/Option"; -import * as r from "@pagopa/ts-commons/lib/requests"; -import { pipe } from "fp-ts/lib/function"; -import { - findWinningTransactionsUsingGETDecoder, - getTotalScoreUsingGETDefaultDecoder, - GetTotalScoreUsingGETT -} from "../../../../../../definitions/bpd/winning_transactions/requestTypes"; -import { PatchedBpdWinningTransactions } from "../../types/PatchedWinningTransactionResource"; -import { bpdHeadersProducers } from "../common"; - -export const winningTransactionsTotalCashbackGET: GetTotalScoreUsingGETT = { - method: "get", - url: ({ awardPeriodId }) => - `/bpd/io/winning-transactions/total-cashback?awardPeriodId=${awardPeriodId}`, - query: _ => ({}), - headers: bpdHeadersProducers(), - response_decoder: getTotalScoreUsingGETDefaultDecoder() -}; - -export type FindWinningTransactionsUsingGETTExtra = r.IGetApiRequestType< - { - readonly awardPeriodId: number; - readonly hpan: string; - readonly Authorization: string; - }, - never, - never, - | r.IResponseType<200, PatchedBpdWinningTransactions> - | r.IResponseType<401, undefined> - | r.IResponseType<500, undefined> ->; - -const hPanToQueryString = (hPan: string) => `&hpan=${hPan}`; - -const findWinningTransactionsUsingGETCustomDecoder = - findWinningTransactionsUsingGETDecoder({ - 200: PatchedBpdWinningTransactions - }); -/** - * @deprecated - */ -export const winningTransactionsGET: FindWinningTransactionsUsingGETTExtra = { - method: "get", - url: ({ awardPeriodId, hpan }) => - `/bpd/io/winning-transactions?awardPeriodId=${awardPeriodId}${pipe( - hpan, - O.fromNullable, - O.map(hPanToQueryString), - O.getOrElse(() => "") - )}`, - query: _ => ({}), - headers: bpdHeadersProducers(), - response_decoder: findWinningTransactionsUsingGETCustomDecoder -}; diff --git a/ts/features/bonus/bpd/api/winning-transactions/v2.ts b/ts/features/bonus/bpd/api/winning-transactions/v2.ts deleted file mode 100644 index 6b4bb9f91ac..00000000000 --- a/ts/features/bonus/bpd/api/winning-transactions/v2.ts +++ /dev/null @@ -1,60 +0,0 @@ -import * as r from "@pagopa/ts-commons/lib/requests"; -import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import { - findWinningTransactionsUsingGETDecoder, - getCountByDayGETDefaultDecoder, - GetCountByDayGETT -} from "../../../../../../definitions/bpd/winning_transactions_v2/requestTypes"; -import { bpdHeadersProducers } from "../common"; -import { PatchedWinningTransactionPageResource } from "./patchedWinningTransactionPageResource"; - -export const winningTransactionsV2CountByDayGET: GetCountByDayGETT = { - method: "get", - url: ({ awardPeriodId }) => - `/bpd/io/winning-transactions/v2/countbyday?awardPeriodId=${awardPeriodId}`, - query: _ => ({}), - headers: bpdHeadersProducers(), - response_decoder: getCountByDayGETDefaultDecoder() -}; - -/** - * We need to use a patched type because the response contains a date-time translated with the codec UTCISODateFromString - * that fails to recognize the received format (used instead {@link DateFromISOString}) - */ -export type PatchedFindWinningTransactionsUsingGETT = r.IGetApiRequestType< - { - readonly hpan?: string; - readonly awardPeriodId: number; - readonly limit?: number; - readonly nextCursor?: number; - readonly Authorization: string; - }, - never, - never, - | r.IResponseType<200, PatchedWinningTransactionPageResource> - | r.IResponseType<401, undefined> - | r.IResponseType<500, undefined> ->; - -const cursorToQueryString = (cursor: number) => `&nextCursor=${cursor}`; - -const findWinningTransactionsUsingGETCustomDecoder = - findWinningTransactionsUsingGETDecoder({ - 200: PatchedWinningTransactionPageResource - }); - -export const winningTransactionsV2GET: PatchedFindWinningTransactionsUsingGETT = - { - method: "get", - url: ({ awardPeriodId, nextCursor }) => - `/bpd/io/winning-transactions/v2?awardPeriodId=${awardPeriodId}${pipe( - nextCursor, - O.fromNullable, - O.map(cursorToQueryString), - O.getOrElse(() => "") - )}`, - query: _ => ({}), - headers: bpdHeadersProducers(), - response_decoder: findWinningTransactionsUsingGETCustomDecoder - }; diff --git a/ts/features/bonus/bpd/components/BaseDailyTransactionHeader.tsx b/ts/features/bonus/bpd/components/BaseDailyTransactionHeader.tsx deleted file mode 100644 index c6decd1a018..00000000000 --- a/ts/features/bonus/bpd/components/BaseDailyTransactionHeader.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import * as React from "react"; -import { View, StyleSheet } from "react-native"; -import { IOColors, Icon } from "@pagopa/io-app-design-system"; -import I18n from "../../../../i18n"; -import { H3 } from "../../../../components/core/typography/H3"; -import { H5 } from "../../../../components/core/typography/H5"; -import { formatIntegerNumber } from "../../../../utils/stringBuilder"; - -type Props = { - date: string; - transactionsNumber: number; -}; - -const styles = StyleSheet.create({ - row: { - flexDirection: "row", - alignItems: "center" - }, - container: { - justifyContent: "space-between" - }, - whiteBg: { - backgroundColor: IOColors.white - } -}); - -const BaseDailyTransactionHeader: React.FunctionComponent = ( - props: Props -) => ( - -

- {props.date} -

- - - -
- {` ${I18n.t( - "bonus.bpd.details.components.transactionsCountOverview.label", - { - defaultValue: I18n.t( - "bonus.bpd.details.components.transactionsCountOverview.label.other", - { - transactions: formatIntegerNumber(props.transactionsNumber) - } - ), - count: props.transactionsNumber, - transactions: formatIntegerNumber(props.transactionsNumber) - } - )}`} -
-
-
{`${I18n.t("bonus.bpd.name")} (€)`}
-
- -); - -export default BaseDailyTransactionHeader; diff --git a/ts/features/bonus/bpd/components/BpdLastUpdateComponent.tsx b/ts/features/bonus/bpd/components/BpdLastUpdateComponent.tsx deleted file mode 100644 index ecc7ae592df..00000000000 --- a/ts/features/bonus/bpd/components/BpdLastUpdateComponent.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import * as React from "react"; -import { useEffect, useState } from "react"; -import { View, StyleSheet } from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { H4 } from "../../../../components/core/typography/H4"; -import { IOStyles } from "../../../../components/core/variables/IOStyles"; -import I18n from "../../../../i18n"; -import { GlobalState } from "../../../../store/reducers/types"; -import { format, formatDateAsLocal } from "../../../../utils/dates"; -import { showToast } from "../../../../utils/showToast"; -import { bpdAllData } from "../store/actions/details"; -import { bpdLastUpdateSelector } from "../store/reducers/details/lastUpdate"; - -type Props = ReturnType & - ReturnType; - -const styles = StyleSheet.create({ - row: { - flexDirection: "row", - justifyContent: "center" - } -}); - -/** - * This component show the last time in which the bpd periods are loaded correctly - * and allow to request a refresh of the bpd periods data. - * @param props - */ -const BpdLastUpdateComponent: React.FunctionComponent = ( - props: Props -) => { - const [isFirstRender, setIsFirstRender] = useState(true); - const { potLastUpdate } = props; - useEffect(() => { - if (!isFirstRender) { - if (pot.isError(potLastUpdate)) { - showToast(I18n.t("global.genericError"), "danger"); - } - } else { - setIsFirstRender(false); - } - }, [potLastUpdate, isFirstRender]); - - return ( - - {!pot.isNone(props.potLastUpdate) && ( -

- {I18n.t("bonus.bpd.details.lastUpdate", { - hour: format(props.potLastUpdate.value, "HH:mm"), - date: formatDateAsLocal(props.potLastUpdate.value, true, true) - })} -

- )} -
- ); -}; - -const mapStateToProps = (state: GlobalState) => ({ - potLastUpdate: bpdLastUpdateSelector(state) -}); - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - loadBonus: () => dispatch(bpdAllData.request()) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(BpdLastUpdateComponent); diff --git a/ts/features/bonus/bpd/components/BpdTestOverlay.tsx b/ts/features/bonus/bpd/components/BpdTestOverlay.tsx deleted file mode 100644 index 7db3b49f45c..00000000000 --- a/ts/features/bonus/bpd/components/BpdTestOverlay.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import * as React from "react"; -import { useState } from "react"; -import { View, Platform, StyleSheet } from "react-native"; - -import { getStatusBarHeight, isIphoneX } from "react-native-iphone-x-helper"; -import { IOColors, hexToRgba } from "@pagopa/io-app-design-system"; -import { Body } from "../../../../components/core/typography/Body"; -import { Label } from "../../../../components/core/typography/Label"; -import { - bpdApiSitUrlPrefix, - bpdApiUatUrlPrefix, - bpdApiUrlPrefix, - pagoPaApiUrlPrefix, - pagoPaApiUrlPrefixTest -} from "../../../../config"; -import { getAppVersion } from "../../../../utils/appVersion"; - -const opaqueBgColor = hexToRgba(IOColors.white, 0.67); - -const styles = StyleSheet.create({ - versionContainer: { - position: "absolute", - top: Platform.select({ - ios: 20 + (isIphoneX() ? getStatusBarHeight() : 0), - android: 0 - }), - left: 0, - right: 0, - bottom: 0, - justifyContent: "flex-start", - alignItems: "center", - zIndex: 1000 - }, - versionText: { - padding: 2, - backgroundColor: opaqueBgColor - } -}); - -/** - * Temp overlay created to avoid ambiguity when test bpd versions are released. - * TODO: remove after the release of bpd - * @constructor - */ -export const BpdTestOverlay: React.FunctionComponent = () => { - const [enabled, setEnabled] = useState(true); - const bpdEndpointStr = - bpdApiUrlPrefix === bpdApiSitUrlPrefix - ? "SIT" - : bpdApiUrlPrefix === bpdApiUatUrlPrefix - ? "UAT" - : "PROD"; - - const pmEndpointStr = - pagoPaApiUrlPrefix === pagoPaApiUrlPrefixTest ? "UAT" : "PROD"; - - return ( - - {enabled ? ( - <> - - setEnabled(!enabled)} - >{`${getAppVersion()} - bpd: ${bpdEndpointStr} - PM: ${pmEndpointStr}`} - - ) : null} - - ); -}; diff --git a/ts/features/bonus/bpd/components/BpdTransactionSummaryComponent.tsx b/ts/features/bonus/bpd/components/BpdTransactionSummaryComponent.tsx deleted file mode 100644 index 9fbe8b8a9e5..00000000000 --- a/ts/features/bonus/bpd/components/BpdTransactionSummaryComponent.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import * as React from "react"; -import { View, StyleSheet } from "react-native"; -import { TouchableWithoutFeedback } from "@gorhom/bottom-sheet"; -import { IOColors, VSpacer } from "@pagopa/io-app-design-system"; -import { Body } from "../../../../components/core/typography/Body"; -import { H4 } from "../../../../components/core/typography/H4"; -import { InfoBox } from "../../../../components/box/InfoBox"; -import { useLegacyIOBottomSheetModal } from "../../../../utils/hooks/bottomSheet"; -import { Link } from "../../../../components/core/typography/Link"; -import { openWebUrl } from "../../../../utils/url"; -import Markdown from "../../../../components/ui/Markdown"; -import I18n from "../../../../i18n"; -import { localeDateFormat } from "../../../../utils/locale"; -import { - formatIntegerNumber, - formatNumberAmount -} from "../../../../utils/stringBuilder"; -import { BpdAmount } from "../saga/networking/amount"; -import { BpdPeriod } from "../store/actions/periods"; - -type Props = { - lastUpdateDate: string; - period: BpdPeriod; - totalAmount: BpdAmount; -}; - -const styles = StyleSheet.create({ - row: { - flexDirection: "row", - alignItems: "center" - }, - readMore: { marginLeft: 31, marginBottom: 24 } -}); - -const readMoreLink = "https://io.italia.it/cashback/acquirer/"; - -const CSS_STYLE = ` -body { - font-size: 16; - color: ${IOColors.black} -} -`; - -export const BottomSheetBpdTransactionsBody: React.FunctionComponent = () => { - const [CTAVisibility, setCTAVisibility] = React.useState(false); - - const setCTAVisible = () => setCTAVisibility(true); - - return ( - <> - - {I18n.t( - "bonus.bpd.details.transaction.detail.summary.bottomSheet.body" - )} - - {CTAVisibility && ( - openWebUrl(readMoreLink)}> - - {I18n.t( - "bonus.bpd.details.transaction.detail.summary.bottomSheet.readMore" - )} - - - )} - - ); -}; - -const BpdTransactionSummaryComponent: React.FunctionComponent = ( - props: Props -) => { - const { present, bottomSheet } = useLegacyIOBottomSheetModal( - <> - - -

- {I18n.t( - "bonus.bpd.details.transaction.detail.summary.calendarBlock.text1" - )} -

- {" "} - {I18n.t( - "bonus.bpd.details.transaction.detail.summary.calendarBlock.text2" - )} -

- {I18n.t( - "bonus.bpd.details.transaction.detail.summary.calendarBlock.text3" - )} - -
- - - - , - I18n.t("bonus.bpd.details.transaction.detail.summary.bottomSheet.title"), - 600 - ); - - return ( - <> - - -

- {I18n.t("bonus.bpd.details.transaction.detail.summary.lastUpdated")} -

{props.lastUpdateDate}

- - - {I18n.t("bonus.bpd.details.transaction.detail.summary.link")} - -
-
- - - - - {I18n.t("bonus.bpd.details.transaction.detail.summary.body.text1")} -

{`${localeDateFormat( - props.period.startDate, - I18n.t("global.dateFormats.fullFormatFullMonthLiteral") - )} - ${localeDateFormat( - props.period.endDate, - I18n.t("global.dateFormats.fullFormatFullMonthLiteral") - )} `}

- {I18n.t("bonus.bpd.details.transaction.detail.summary.body.text2")} -

- {I18n.t("bonus.bpd.details.transaction.detail.summary.body.text3", { - defaultValue: I18n.t( - "bonus.bpd.details.transaction.detail.summary.body.text3.other", - { - transactions: formatIntegerNumber( - props.totalAmount.transactionNumber - ) - } - ), - count: props.totalAmount.transactionNumber, - transactions: formatIntegerNumber( - props.totalAmount.transactionNumber - ) - })} -

- {I18n.t("bonus.bpd.details.transaction.detail.summary.body.text4")} -

{`${I18n.t( - "bonus.bpd.details.transaction.detail.summary.body.text5" - )}${formatNumberAmount(props.totalAmount.totalCashback)} euro.`}

- - {bottomSheet} - - ); -}; - -export default BpdTransactionSummaryComponent; diff --git a/ts/features/bonus/bpd/components/__test__/BottomSheetMethodsToDelete.test.tsx b/ts/features/bonus/bpd/components/__test__/BottomSheetMethodsToDelete.test.tsx deleted file mode 100644 index 83fbf71e329..00000000000 --- a/ts/features/bonus/bpd/components/__test__/BottomSheetMethodsToDelete.test.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { render } from "@testing-library/react-native"; -import React from "react"; -import { BottomSheetMethodsToDelete } from "../optInStatus/BottomSheetMethodsToDelete"; -import { mockCreditCardPaymentMethod } from "../../../../../store/reducers/wallet/__mocks__/wallets"; - -describe("BottomSheetMethodsToDelete", () => { - jest.useFakeTimers(); - it(`component should be defined`, () => { - const renderComponent = render( - - ); - expect( - renderComponent.queryByTestId("BottomSheetMethodsToDeleteTestID") - ).not.toBeNull(); - }); - - describe("when some methods are available", () => { - it(`should shown all these items`, () => { - const paymentMethods = [ - { ...mockCreditCardPaymentMethod, idWallet: 1 }, - { ...mockCreditCardPaymentMethod, idWallet: 2 }, - { ...mockCreditCardPaymentMethod, idWallet: 3 } - ]; - const renderComponent = render( - - ); - expect( - renderComponent.queryByTestId("BottomSheetMethodsToDeleteTestID") - ).not.toBeNull(); - paymentMethods.forEach(pm => { - expect( - renderComponent.queryByTestId(`payment_method_${pm.idWallet}`) - ).not.toBeNull(); - }); - }); - }); -}); diff --git a/ts/features/bonus/bpd/components/bpdCardComponent/BpdCardComponent.tsx b/ts/features/bonus/bpd/components/bpdCardComponent/BpdCardComponent.tsx deleted file mode 100644 index 1e3977c604f..00000000000 --- a/ts/features/bonus/bpd/components/bpdCardComponent/BpdCardComponent.tsx +++ /dev/null @@ -1,440 +0,0 @@ -import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import * as React from "react"; -import { - Text, - View, - Image, - ImageBackground, - Platform, - StyleSheet -} from "react-native"; -import { widthPercentageToDP } from "react-native-responsive-screen"; -import { - Icon, - hexToRgba, - IOColors, - HSpacer -} from "@pagopa/io-app-design-system"; -import bpdCardBgFull from "../../../../../../img/bonus/bpd/bonus_bg.png"; -import bpdCardBgPreview from "../../../../../../img/bonus/bpd/bonus_preview_bg.png"; -import bpdBonusLogo from "../../../../../../img/bonus/bpd/logo_BonusCashback_White.png"; -import { H2 } from "../../../../../components/core/typography/H2"; -import { H4 } from "../../../../../components/core/typography/H4"; -import { H5 } from "../../../../../components/core/typography/H5"; -import TouchableDefaultOpacity from "../../../../../components/TouchableDefaultOpacity"; -import I18n from "../../../../../i18n"; -import { localeDateFormat } from "../../../../../utils/locale"; -import { formatNumberAmount } from "../../../../../utils/stringBuilder"; -import { BpdAmount } from "../../saga/networking/amount"; -import { BpdPeriod, BpdPeriodStatus } from "../../store/actions/periods"; -import { makeFontStyleObject } from "../../../../../components/core/fonts"; -import { IOBadge } from "../../../../../components/core/IOBadge"; - -type Props = { - period: BpdPeriod; - totalAmount: BpdAmount; - preview?: boolean; - onPress?: () => void; -}; - -const opaqueBorderColor = hexToRgba(IOColors.black, 0.1); - -const styles = StyleSheet.create({ - flex1: { - flex: 1 - }, - flex2: { - flex: 2 - }, - container: { - flex: 1, - height: 192 - }, - paddedContentFull: { - paddingLeft: 16, - paddingTop: 24, - paddingRight: 20, - paddingBottom: 16 - }, - paddedContentPreview: { - paddingLeft: 18, - paddingTop: 8, - paddingRight: 22 - }, - row: { - flexDirection: "row" - }, - column: { - flexDirection: "column" - }, - spaced: { - justifyContent: "space-between" - }, - fullLogo: { - resizeMode: "contain", - height: 56, - width: 56, - alignSelf: "flex-end" - }, - previewLogo: { - resizeMode: "contain", - height: 40, - width: 40, - alignSelf: "center" - }, - preview: { - marginBottom: -20, - height: 88 - }, - imageFull: { - resizeMode: "stretch", - height: "100%" - }, - imagePreview: { - resizeMode: "stretch", - height: 88, - width: "100%" - }, - amountTextBaseFull: { - color: IOColors.white, - fontSize: 24, - lineHeight: 35, - // solution taken from https://github.com/facebook/react-native/issues/7687#issuecomment-309168661 - paddingTop: Platform.select({ - ios: 0, - android: 10 - }), - marginBottom: -8, - ...makeFontStyleObject("Bold") - }, - amountTextUpperFull: { - color: IOColors.white, - fontSize: 32, - ...makeFontStyleObject("Bold") - }, - amountTextBasePreview: { - fontSize: 16, - lineHeight: 32, - color: IOColors.white, - ...makeFontStyleObject("Bold") - }, - amountTextUpperPreview: { - color: IOColors.white, - fontSize: 24, - ...makeFontStyleObject("Bold") - }, - alignItemsCenter: { - alignItems: "center" - }, - justifyContentCenter: { - justifyContent: "center" - }, - upperShadowBox: { - marginBottom: -13, - borderRadius: 8, - borderTopWidth: 13, - borderTopColor: opaqueBorderColor, - height: 17, - width: "100%" - }, - bottomShadowBox: { - marginBottom: 6, - borderRadius: 8, - borderBottomWidth: 15, - borderBottomColor: opaqueBorderColor, - width: "100%" - } -}); - -type BadgeDefinition = { - label: string; -}; - -type IconType = "locked" | "unlocked" | "ok"; - -type GraphicalState = { - amount: ReadonlyArray; - isInGracePeriod: boolean; - iconName: IconType; - statusBadge: BadgeDefinition; -}; - -const initialGraphicalState: GraphicalState = { - amount: ["0", "00"], - isInGracePeriod: false, - iconName: "locked", - statusBadge: { - label: "-" - } -}; - -/** - * Closed lock must be shown if period is Inactive or the transactionNumber didn't reach the minimum target - * Open lock must be shown if period is Closed or Active and the transactionNumber reach the minimum target - * "Ok" (was Fireworks) must be shown if period is Closed or Active and the totalCashback reach the maxAmount - * - * @param period - * @param totalAmount - */ -const iconHandler = (period: BpdPeriod, totalAmount: BpdAmount): IconType => { - const reachMinTransaction = - totalAmount.transactionNumber >= period.minTransactionNumber; - const reachMaxAmount = totalAmount.totalCashback >= period.maxPeriodCashback; - switch (period.status) { - case "Active": - case "Closed": - return reachMinTransaction && reachMaxAmount - ? "ok" - : reachMinTransaction - ? "unlocked" - : "locked"; - default: - return "locked"; - } -}; - -const statusClosedHandler = (props: Props): GraphicalState => { - const { period, totalAmount } = props; - - const actualDate = new Date(); - const endDate = new Date(period.endDate.getTime()); - endDate.setDate(endDate.getDate() + period.gracePeriod); - - const isInGracePeriod = - actualDate.getTime() >= period.endDate.getTime() && - actualDate.getTime() <= endDate.getTime(); - - return { - ...initialGraphicalState, - amount: - totalAmount.transactionNumber < period.minTransactionNumber && - !isInGracePeriod - ? ["0", "00"] - : formatNumberAmount(props.totalAmount.totalCashback).split( - I18n.t("global.localization.decimalSeparator") - ), - isInGracePeriod, - iconName: iconHandler(props.period, props.totalAmount), - // TODO: Add supercashback business logic - statusBadge: isInGracePeriod - ? { - label: I18n.t("profile.preferences.list.wip") - } - : { - label: I18n.t("bonus.bpd.details.card.status.closed") - } - }; -}; - -const statusActiveHandler = (props: Props): GraphicalState => ({ - ...initialGraphicalState, - statusBadge: { - label: I18n.t("bonus.bpd.details.card.status.active") - }, - amount: formatNumberAmount(props.totalAmount.totalCashback).split( - I18n.t("global.localization.decimalSeparator") - ), - iconName: iconHandler(props.period, props.totalAmount) -}); - -const statusInactiveHandler = (props: Props): GraphicalState => ({ - ...initialGraphicalState, - statusBadge: { - label: I18n.t("bonus.bpd.details.card.status.inactive") - }, - amount: formatNumberAmount(props.totalAmount.totalCashback).split( - I18n.t("global.localization.decimalSeparator") - ) -}); - -const statusHandlersMap = new Map< - BpdPeriodStatus, - (props: Props) => GraphicalState ->([ - ["Closed", statusClosedHandler], - ["Active", statusActiveHandler], - ["Inactive", statusInactiveHandler] -]); - -/** - * if the period is Closed we must check if minimum number of transactions has been reached - * Unless we'll show a Zero amount value - * - * GracePeriod: check if we are in the grace period to show an alert instead of the value - * grace period is given adding the gracePeriod value of days to period.endDate - */ -const calculateGraphicalState = (props: Props) => - pipe( - statusHandlersMap.get(props.period.status), - O.fromNullable, - O.fold( - () => initialGraphicalState, - handler => handler(props) - ) - ); - -export const BpdCardComponent: React.FunctionComponent = ( - props: Props -) => { - const { amount, isInGracePeriod, iconName, statusBadge } = - calculateGraphicalState(props); - - const isPeriodClosed = props.period.status === "Closed" && !isInGracePeriod; - const isPeriodInactive = props.period.status === "Inactive"; - - const FullCard = () => ( - - - -

- {I18n.t("bonus.bpd.title")} -

-

- {`${localeDateFormat( - props.period.startDate, - I18n.t("global.dateFormats.fullFormatShortMonthLiteral") - )} - ${localeDateFormat( - props.period.endDate, - I18n.t("global.dateFormats.fullFormatShortMonthLiteral") - )}`} -

-
- - - {/* NBCard Text component */} - - {"€ "} - - {`${amount[0]}${I18n.t( - "global.localization.decimalSeparator" - )}`} - - {amount[1]} - - - - -
- {I18n.t("bonus.bpd.earned")} -
-
-
- - - - -
- ); - - const PreviewCard = () => ( - - - -
- {`${localeDateFormat( - props.period.startDate, - I18n.t("global.dateFormats.dayFullMonth") - )} - ${localeDateFormat( - props.period.endDate, - I18n.t("global.dateFormats.fullFormatFullMonthLiteral") - )}`} -
- - {isPeriodClosed && } -
- -

- {I18n.t("bonus.bpd.name")} -

- - - - {isInGracePeriod || isPeriodInactive ? ( - - ) : ( - - {"€ "} - - {`${amount[0]}${I18n.t( - "global.localization.decimalSeparator" - )}`} - - {amount[1]} - - )} - -
-
- -
- ); - - return ( - <> - {Platform.OS === "android" && ( - - )} - - {props.preview ? : } - - {Platform.OS === "android" && !props.preview && ( - - )} - - ); -}; diff --git a/ts/features/bonus/bpd/components/optInPaymentMethods/BpdOptInPaymentMethodsContainer.tsx b/ts/features/bonus/bpd/components/optInPaymentMethods/BpdOptInPaymentMethodsContainer.tsx deleted file mode 100644 index 811bfaa47ae..00000000000 --- a/ts/features/bonus/bpd/components/optInPaymentMethods/BpdOptInPaymentMethodsContainer.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { useNavigation } from "@react-navigation/native"; -import * as React from "react"; -import { useContext, useEffect, useState } from "react"; -import { useDispatch } from "react-redux"; -import { isError, isReady } from "../../../../../common/model/RemoteValue"; -import LoadingSpinnerOverlay from "../../../../../components/LoadingSpinnerOverlay"; -import { LightModalContext } from "../../../../../components/ui/LightModal"; -import I18n from "../../../../../i18n"; -import { useIOSelector } from "../../../../../store/hooks"; -import { optInPaymentMethodsShowChoice } from "../../store/actions/optInPaymentMethods"; -import { showOptInChoiceSelector } from "../../store/reducers/details/activation/ui"; -import { bpdLastUpdateSelector } from "../../store/reducers/details/lastUpdate"; - -const BpdOptInPaymentMethodsContainer = () => { - const dispatch = useDispatch(); - const navigation = useNavigation(); - const { showModal, hideModal } = useContext(LightModalContext); - const [showOptInChecked, setShowOptInChecked] = useState(false); - const showOptInChoice = useIOSelector(showOptInChoiceSelector); - const bpdLastUpdate = useIOSelector(bpdLastUpdateSelector); - - useEffect(() => { - if (!showOptInChecked && !isReady(showOptInChoice)) { - setShowOptInChecked(true); - // Starts the optInShouldShowChoiceHandler saga - dispatch(optInPaymentMethodsShowChoice.request()); - showModal( - - ); - } - }, [dispatch, showOptInChecked, bpdLastUpdate, showModal, showOptInChoice]); - - useEffect(() => { - if (isReady(showOptInChoice) || isError(showOptInChoice)) { - hideModal(); - } - }, [hideModal, showOptInChoice, navigation]); - - return <>; -}; - -export default BpdOptInPaymentMethodsContainer; diff --git a/ts/features/bonus/bpd/components/optInPaymentMethods/RetryAfterDeletionFailsComponent.tsx b/ts/features/bonus/bpd/components/optInPaymentMethods/RetryAfterDeletionFailsComponent.tsx deleted file mode 100644 index 1ce4d767b83..00000000000 --- a/ts/features/bonus/bpd/components/optInPaymentMethods/RetryAfterDeletionFailsComponent.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import React from "react"; -import { SafeAreaView } from "react-native"; -import { useDispatch } from "react-redux"; -import { InfoScreenComponent } from "../../../../../components/infoScreen/InfoScreenComponent"; -import Error from "../../../../../../img/wallet/errors/generic-error-icon.svg"; -import { IOStyles } from "../../../../../components/core/variables/IOStyles"; -import { - cancelButtonProps, - confirmButtonProps -} from "../../../../../components/buttons/ButtonConfigurations"; -import { - optInPaymentMethodsCompleted, - optInPaymentMethodsDeletionChoice -} from "../../store/actions/optInPaymentMethods"; -import FooterWithButtons from "../../../../../components/ui/FooterWithButtons"; -import I18n from "../../../../../i18n"; - -const RetryAfterDeletionFailsComponent = () => { - const dispatch = useDispatch(); - - return ( - - } - title={I18n.t( - "bonus.bpd.optInPaymentMethods.thankYouPage.retryAfterDeletion.title" - )} - body={I18n.t( - "bonus.bpd.optInPaymentMethods.thankYouPage.retryAfterDeletion.body" - )} - /> - dispatch(optInPaymentMethodsDeletionChoice()), - I18n.t("bonus.bpd.optInPaymentMethods.thankYouPage.cta.retry"), - undefined, - "retryButton" - )} - rightButton={cancelButtonProps( - () => dispatch(optInPaymentMethodsCompleted()), - I18n.t("bonus.bpd.optInPaymentMethods.thankYouPage.cta.goToWallet"), - undefined, - "goToWalletButton" - )} - /> - - ); -}; - -export default RetryAfterDeletionFailsComponent; diff --git a/ts/features/bonus/bpd/components/optInPaymentMethods/ThankYouSuccessComponent.tsx b/ts/features/bonus/bpd/components/optInPaymentMethods/ThankYouSuccessComponent.tsx deleted file mode 100644 index a0fe882e4ca..00000000000 --- a/ts/features/bonus/bpd/components/optInPaymentMethods/ThankYouSuccessComponent.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { useNavigation } from "@react-navigation/native"; -import React from "react"; -import { SafeAreaView } from "react-native"; -import Completed from "../../../../../../img/pictograms/payment-completed.svg"; -import { IOStyles } from "../../../../../components/core/variables/IOStyles"; -import { InfoScreenComponent } from "../../../../../components/infoScreen/InfoScreenComponent"; -import FooterWithButtons from "../../../../../components/ui/FooterWithButtons"; -import I18n from "../../../../../i18n"; -import ROUTES from "../../../../../navigation/routes"; -import { useIODispatch } from "../../../../../store/hooks"; -import { cancelButtonProps } from "../../../../../components/buttons/ButtonConfigurations"; -import { - optInPaymentMethodsCompleted, - optInPaymentMethodsShowChoice -} from "../../store/actions/optInPaymentMethods"; - -const ThankYouSuccessComponent = () => { - const navigation = useNavigation(); - const dispatch = useIODispatch(); - return ( - - } - title={I18n.t( - "bonus.bpd.optInPaymentMethods.thankYouPage.success.title" - )} - body={I18n.t("bonus.bpd.optInPaymentMethods.thankYouPage.success.body")} - /> - { - dispatch(optInPaymentMethodsCompleted()); - dispatch(optInPaymentMethodsShowChoice.success(false)); - navigation.navigate(ROUTES.WALLET_HOME); - }, - I18n.t("bonus.bpd.optInPaymentMethods.thankYouPage.cta.goToWallet"), - undefined, - "goToWalletButton" - )} - /> - - ); -}; - -export default ThankYouSuccessComponent; diff --git a/ts/features/bonus/bpd/components/optInPaymentMethods/__tests__/RetryAfterDeletionFailsComponent.test.ts b/ts/features/bonus/bpd/components/optInPaymentMethods/__tests__/RetryAfterDeletionFailsComponent.test.ts deleted file mode 100644 index 61d07b36bf4..00000000000 --- a/ts/features/bonus/bpd/components/optInPaymentMethods/__tests__/RetryAfterDeletionFailsComponent.test.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { createStore, Store } from "redux"; - -import { fireEvent, RenderAPI } from "@testing-library/react-native"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { renderScreenWithNavigationStoreContext } from "../../../../../../utils/testWrapper"; -import ROUTES from "../../../../../../navigation/routes"; -import RetryAfterDeletionFailsComponent from "../RetryAfterDeletionFailsComponent"; -import { appReducer } from "../../../../../../store/reducers"; -import { applicationChangeState } from "../../../../../../store/actions/application"; -import * as optInPaymentMethodsActions from "../../../store/actions/optInPaymentMethods"; - -jest.useFakeTimers(); - -describe("the RetryAfterDeletionFailsComponent screen", () => { - const globalState = appReducer(undefined, applicationChangeState("active")); - - it("Should call the optInPaymentMethodsCompleted functions when the goToWalletButton is pressed", () => { - const optInPaymentMethodsCompletedSpy = jest.spyOn( - optInPaymentMethodsActions, - "optInPaymentMethodsCompleted" - ); - const store: Store = createStore( - appReducer, - globalState as any - ); - const component: RenderAPI = renderComponent(store); - fireEvent.press(component.getByTestId("goToWalletButton")); - expect(optInPaymentMethodsCompletedSpy).toBeCalledTimes(1); - }); - it("Should call the optInPaymentMethodsDeletionChoice functions when the retryButton is pressed", () => { - const optInPaymentMethodsDeletionChoiceSpy = jest.spyOn( - optInPaymentMethodsActions, - "optInPaymentMethodsDeletionChoice" - ); - const store: Store = createStore( - appReducer, - globalState as any - ); - const component: RenderAPI = renderComponent(store); - fireEvent.press(component.getByTestId("retryButton")); - expect(optInPaymentMethodsDeletionChoiceSpy).toBeCalledTimes(1); - }); -}); - -function renderComponent(store: Store) { - return renderScreenWithNavigationStoreContext( - RetryAfterDeletionFailsComponent, - ROUTES.MAIN, - {}, - store - ); -} diff --git a/ts/features/bonus/bpd/components/optInPaymentMethods/__tests__/ThankYouSuccessComponent.test.ts b/ts/features/bonus/bpd/components/optInPaymentMethods/__tests__/ThankYouSuccessComponent.test.ts deleted file mode 100644 index 01aafce5dc8..00000000000 --- a/ts/features/bonus/bpd/components/optInPaymentMethods/__tests__/ThankYouSuccessComponent.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { createStore, Store } from "redux"; - -import { fireEvent, RenderAPI } from "@testing-library/react-native"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { renderScreenWithNavigationStoreContext } from "../../../../../../utils/testWrapper"; -import ROUTES from "../../../../../../navigation/routes"; -import { appReducer } from "../../../../../../store/reducers"; -import { applicationChangeState } from "../../../../../../store/actions/application"; -import * as optInPaymentMethodsActions from "../../../store/actions/optInPaymentMethods"; -import ThankYouSuccessComponent from "../ThankYouSuccessComponent"; - -jest.useFakeTimers(); - -describe("the ThankYouSuccessComponent screen", () => { - const globalState = appReducer(undefined, applicationChangeState("active")); - - it("Should call the optInPaymentMethodsCompleted functions when the goToWalletButton is pressed", () => { - const optInPaymentMethodsCompletedSpy = jest.spyOn( - optInPaymentMethodsActions, - "optInPaymentMethodsCompleted" - ); - const store: Store = createStore( - appReducer, - globalState as any - ); - const component: RenderAPI = renderComponent(store); - fireEvent.press(component.getByTestId("goToWalletButton")); - expect(optInPaymentMethodsCompletedSpy).toBeCalledTimes(1); - }); -}); - -function renderComponent(store: Store) { - return renderScreenWithNavigationStoreContext( - ThankYouSuccessComponent, - ROUTES.MAIN, - {}, - store - ); -} diff --git a/ts/features/bonus/bpd/components/optInStatus/BottomSheetMethodsToDelete.tsx b/ts/features/bonus/bpd/components/optInStatus/BottomSheetMethodsToDelete.tsx deleted file mode 100644 index 5c8d037a7ea..00000000000 --- a/ts/features/bonus/bpd/components/optInStatus/BottomSheetMethodsToDelete.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { ListItem } from "native-base"; -import * as React from "react"; -import { View, Dimensions } from "react-native"; -import { useSelector } from "react-redux"; -import { IOColors, VSpacer } from "@pagopa/io-app-design-system"; -import { H3 } from "../../../../../components/core/typography/H3"; -import { Label } from "../../../../../components/core/typography/Label"; -import { IOStyles } from "../../../../../components/core/variables/IOStyles"; -import FooterWithButtons from "../../../../../components/ui/FooterWithButtons"; -import I18n from "../../../../../i18n"; -import { getBPDMethodsVisibleInWalletSelector } from "../../../../../store/reducers/wallet/wallets"; -import { PaymentMethod } from "../../../../../types/pagopa"; -import { useLegacyIOBottomSheetModal } from "../../../../../utils/hooks/bottomSheet"; -import { PaymentMethodRepresentationComponent } from "../paymentMethodActivationToggle/base/PaymentMethodRepresentationComponent"; - -type Props = { - paymentMethods: ReadonlyArray; -}; - -export const BottomSheetMethodsToDelete = (props: Props) => ( - - - - {props.paymentMethods.map(pm => ( - - - - ))} - -); - -type BottomSheetReturnType = { - presentBottomSheet: () => void; - bottomSheet: React.ReactNode; -}; - -/** - * return an hook that exposes presentBottomSheet function to imperative show a bottomsheet - * containing the list of those payment methods that have BPD as function enabled - * @param props - */ -export const useBottomSheetMethodsToDelete = (props: { - onDeletePress: () => void; -}): BottomSheetReturnType => { - const paymentMethods = useSelector(getBPDMethodsVisibleInWalletSelector); - const snapPoint = Math.min( - Dimensions.get("window").height * 0.8, - // (subtitle + footer) + items - 280 + paymentMethods.length * 58 - ); - const { present, bottomSheet, dismiss } = useLegacyIOBottomSheetModal( - , - -

- {I18n.t( - "bonus.bpd.optInPaymentMethods.deletePaymentMethodsBottomSheet.title" - )} -

- -
, - snapPoint, - { - props.onDeletePress(); - dismiss(); - }, - title: I18n.t("global.buttons.delete") - }} - rightButton={{ - testID: "cancelButtonTestID", - bordered: true, - onPressWithGestureHandler: true, - onPress: () => dismiss(), - title: I18n.t("global.buttons.cancel") - }} - /> - ); - - return { - presentBottomSheet: present, - bottomSheet - }; -}; diff --git a/ts/features/bonus/bpd/components/paymentMethodActivationToggle/BpdPaymentMethodToggleFactory.tsx b/ts/features/bonus/bpd/components/paymentMethodActivationToggle/BpdPaymentMethodToggleFactory.tsx deleted file mode 100644 index 5c677805e53..00000000000 --- a/ts/features/bonus/bpd/components/paymentMethodActivationToggle/BpdPaymentMethodToggleFactory.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import * as React from "react"; -import { PaymentMethod } from "../../../../../types/pagopa"; -import { hasFunctionEnabled } from "../../../../../utils/walletv2"; -import { HPan } from "../../store/actions/paymentMethods"; -import { getPaymentMethodHash } from "../../../../../utils/paymentMethod"; -import { EnableableFunctionsEnum } from "../../../../../../definitions/pagopa/EnableableFunctions"; -import PaymentMethodBpdToggle from "./base/PaymentMethodBpdToggle"; - -/** - * Return a specific toggle based on the WalletTypeEnum - * @param paymentMethod - */ -export const bpdToggleFactory = (paymentMethod: PaymentMethod) => { - const hash = getPaymentMethodHash(paymentMethod); - return hash ? ( - - ) : null; -}; diff --git a/ts/features/bonus/bpd/components/paymentMethodActivationToggle/base/BpdToggle.tsx b/ts/features/bonus/bpd/components/paymentMethodActivationToggle/base/BpdToggle.tsx deleted file mode 100644 index ed23b0c70c8..00000000000 --- a/ts/features/bonus/bpd/components/paymentMethodActivationToggle/base/BpdToggle.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import * as React from "react"; -import { ActivityIndicator } from "react-native"; -import { Icon } from "@pagopa/io-app-design-system"; -import Switch from "../../../../../../components/ui/Switch"; -import TouchableDefaultOpacity from "../../../../../../components/TouchableDefaultOpacity"; -import { GraphicalValue } from "./PaymentMethodBpdToggle"; - -type Props = { - graphicalValue: GraphicalValue; - // The use choose to change the value of the toggle - onValueChanged?: (b: boolean) => void; - // The user tap the "notice" icon when the graphical state is "notActivable" - onPress?: () => void; -}; - -const iconSize = 24; - -/** - * The toggle used in {@link PaymentMethodBpdToggle}. - * @param props - * @constructor - */ -export const BpdToggle: React.FunctionComponent = props => { - switch (props.graphicalValue.state) { - case "loading": - return ( - - ); - case "ready": - case "update": - return props.graphicalValue.value === "notActivable" ? ( - - - - ) : ( - - ); - } -}; diff --git a/ts/features/bonus/bpd/components/paymentMethodActivationToggle/base/PaymentMethodBpdToggle.tsx b/ts/features/bonus/bpd/components/paymentMethodActivationToggle/base/PaymentMethodBpdToggle.tsx deleted file mode 100644 index 50a54b535ab..00000000000 --- a/ts/features/bonus/bpd/components/paymentMethodActivationToggle/base/PaymentMethodBpdToggle.tsx +++ /dev/null @@ -1,217 +0,0 @@ -/* eslint-disable functional/immutable-data */ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { useNavigation } from "@react-navigation/native"; -import * as React from "react"; -import { useCallback, useEffect, useRef, useState } from "react"; -import { View, ImageSourcePropType, StyleSheet } from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { fetchPaymentManagerLongTimeout } from "../../../../../../config"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { - bpdPaymentMethodActivation, - BpdPaymentMethodActivation, - BpdPmActivationStatus, - bpdUpdatePaymentMethodActivation, - HPan -} from "../../../store/actions/paymentMethods"; -import { bpdPaymentMethodValueSelector } from "../../../store/reducers/details/paymentMethods"; -import { useChangeActivationConfirmationBottomSheet } from "../bottomsheet/BpdChangeActivationConfirmationScreen"; -import { - NotActivableType, - useNotActivableInformationBottomSheet -} from "../bottomsheet/BpdNotActivableInformation"; -import { BpdToggle } from "./BpdToggle"; -import { PaymentMethodRepresentationComponent } from "./PaymentMethodRepresentationComponent"; - -// TODO: accept only hpan, read all the other information with a selector from payment methods -export type BpdToggleProps = { - hPan: HPan; - icon: ImageSourcePropType; - caption: string; - hasBpdCapability: boolean; -}; - -export type Props = ReturnType & - ReturnType & - BpdToggleProps; - -type GraphicalState = "loading" | "ready" | "update"; - -export type GraphicalValue = { - state: GraphicalState; - value: BpdPmActivationStatus | undefined; -}; - -const styles = StyleSheet.create({ - row: { - flex: 1, - flexDirection: "row", - alignItems: "center", - height: 48 - } -}); - -/** - * This custom hook handles the load of the initial state and the retry in case of error. - * TODO: refactor with {@link useLoadPotValue} - * @deprecated - * @param props - */ -const useInitialValue = (props: Props) => { - const timerRetry = useRef(undefined); - const navigation = useNavigation(); - const isFocused = navigation.isFocused(); - const { bpdPotActivation, hPan, loadActualValue, hasBpdCapability } = props; - - const retry = useCallback(() => { - timerRetry.current = undefined; - if (hasBpdCapability) { - loadActualValue(hPan); - } - }, [loadActualValue, hPan, hasBpdCapability]); - - /** - * When the focus change, clear the timer (if any) and reset the value to undefined - * focus: true -> a new schedule is allowed - * focus: false -> clear all the pending schedule - */ - - useEffect(() => { - clearTimeout(timerRetry.current); - timerRetry.current = undefined; - }, [isFocused]); - - useEffect(() => { - if (!hasBpdCapability) { - return; - } - // Initial state, request the state - if (bpdPotActivation === pot.none) { - loadActualValue(hPan); - } else if ( - pot.isNone(bpdPotActivation) && - pot.isError(bpdPotActivation) && - timerRetry.current === undefined && - isFocused - ) { - // If the pot is NoneError, the navigation focus is on the element - // and no other retry are scheduled - timerRetry.current = setTimeout(retry, fetchPaymentManagerLongTimeout); - } - }, [ - bpdPotActivation, - hPan, - loadActualValue, - retry, - isFocused, - hasBpdCapability - ]); - - // Component unmount, clear scheduled - useEffect( - () => () => { - clearTimeout(timerRetry.current); - }, - [] - ); -}; - -const loading: GraphicalValue = { state: "loading", value: undefined }; - -/** - * Calculate the graphical state based on the pot possible states - * @param potBpdActivation - */ -export const calculateBpdToggleGraphicalState = ( - potBpdActivation: pot.Pot -): GraphicalValue => - pot.fold( - potBpdActivation, - () => loading, - () => loading, - _ => loading, - _ => loading, - value => ({ state: "ready", value: value.activationStatus }), - value => ({ state: "loading", value: value.activationStatus }), - (_, newValue) => ({ - state: "update", - value: newValue.activationStatus - }), - value => ({ state: "ready", value: value.activationStatus }) - ); - -/** - * This component represents the activation state of bpd on a payment method. - * - Load the initial value (is bpd active on the payment method) - * - The toggle allows the user to enable or disable bpd on the payment method - * - Sync the remote communication with the graphical states - * Bpd can also be "not activable" on the payment method: - * - The payment method doesn't have the capability - * - Bpd is already activate on the payment method by another user - * @constructor - */ -const PaymentMethodActivationToggle: React.FunctionComponent = props => { - // Calculate the graphical state based on the potActivation and capability - const graphicalState: GraphicalValue = props.hasBpdCapability - ? calculateBpdToggleGraphicalState(props.bpdPotActivation) - : { state: "ready", value: "notActivable" }; - - const [toggleValue, setToggleValue] = useState( - graphicalState.value === "active" - ); - // trigger the initial loading / retry - useInitialValue(props); - - // a simplification because the onPress is dispatched only when is not activable / compatible - const notActivableType: NotActivableType = !props.hasBpdCapability - ? "NotCompatible" - : "NotActivable"; - - const { - present: askConfirmation, - bottomSheet: changeActivationConfirmationBS - } = useChangeActivationConfirmationBottomSheet(props, toggleValue, () => - props.updateValue(props.hPan, toggleValue) - ); - - const { present: showExplanation, bottomSheet: notAvailableInformationBS } = - useNotActivableInformationBottomSheet(props, notActivableType); - - return ( - <> - - - { - setToggleValue(newVal); - askConfirmation(); - }} - onPress={showExplanation} - /> - - {changeActivationConfirmationBS} - {notAvailableInformationBS} - - ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - loadActualValue: (hPan: HPan) => - dispatch(bpdPaymentMethodActivation.request(hPan)), - updateValue: (hPan: HPan, value: boolean) => - dispatch(bpdUpdatePaymentMethodActivation.request({ hPan, value })) -}); - -const mapStateToProps = (state: GlobalState, props: BpdToggleProps) => ({ - bpdPotActivation: bpdPaymentMethodValueSelector(state, props.hPan) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(PaymentMethodActivationToggle); diff --git a/ts/features/bonus/bpd/components/paymentMethodActivationToggle/base/PaymentMethodRepresentationComponent.tsx b/ts/features/bonus/bpd/components/paymentMethodActivationToggle/base/PaymentMethodRepresentationComponent.tsx deleted file mode 100644 index 15606135b8f..00000000000 --- a/ts/features/bonus/bpd/components/paymentMethodActivationToggle/base/PaymentMethodRepresentationComponent.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import * as React from "react"; -import { View, Image, StyleSheet } from "react-native"; -import { HSpacer } from "@pagopa/io-app-design-system"; -import { Body } from "../../../../../../components/core/typography/Body"; -import { PaymentMethodRepresentation } from "../../../../../../types/pagopa"; - -const styles = StyleSheet.create({ - row: { - flex: 1, - flexDirection: "row" - }, - cardIcon: { - width: 40, - height: 25, - overflow: "hidden", - resizeMode: "contain" - }, - text: { - flex: 1, - paddingRight: 16 - } -}); - -/** - * Icon + text representation of a payment method - * @param props - * @constructor - */ -export const PaymentMethodRepresentationComponent: React.FunctionComponent< - PaymentMethodRepresentation -> = props => ( - - - - - {props.caption} - - -); diff --git a/ts/features/bonus/bpd/components/paymentMethodActivationToggle/bottomsheet/BpdChangeActivationConfirmationScreen.tsx b/ts/features/bonus/bpd/components/paymentMethodActivationToggle/bottomsheet/BpdChangeActivationConfirmationScreen.tsx deleted file mode 100644 index 8ea2b5df5fb..00000000000 --- a/ts/features/bonus/bpd/components/paymentMethodActivationToggle/bottomsheet/BpdChangeActivationConfirmationScreen.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import { View } from "react-native"; -import * as React from "react"; -import { VSpacer } from "@pagopa/io-app-design-system"; -import { InfoBox } from "../../../../../../components/box/InfoBox"; -import { Body } from "../../../../../../components/core/typography/Body"; -import FooterWithButtons from "../../../../../../components/ui/FooterWithButtons"; -import Markdown from "../../../../../../components/ui/Markdown"; -import I18n from "../../../../../../i18n"; -import { PaymentMethodRepresentation } from "../../../../../../types/pagopa"; -import { useLegacyIOBottomSheetModal } from "../../../../../../utils/hooks/bottomSheet"; -import { - cancelButtonProps, - confirmButtonProps -} from "../../../../../../components/buttons/ButtonConfigurations"; -import { PaymentMethodRepresentationComponent } from "../base/PaymentMethodRepresentationComponent"; - -export type ConfirmationType = "Activation" | "Deactivation"; - -type Props = { - onCancel: () => void; - onConfirm: () => void; - type: ConfirmationType; - representation: PaymentMethodRepresentation; -}; - -const getText = (confirmationType: ConfirmationType) => ({ - cta: - confirmationType === "Activation" - ? I18n.t("global.buttons.activate") - : I18n.t("global.buttons.deactivate"), - body: - confirmationType === "Activation" - ? I18n.t("bonus.bpd.details.paymentMethods.activate.body") - : I18n.t("bonus.bpd.details.paymentMethods.deactivate.body") -}); - -/** - * Confirmation bottom sheet that informs the user about the consequences about the activation / deactivation - * @param props - * @constructor - */ -export const BpdChangeActivationConfirmationScreen: React.FunctionComponent< - Props -> = props => { - const { body } = getText(props.type); - - return ( - - - - - {body} - {props.type === "Activation" && ( - <> - - - - {I18n.t("bonus.bpd.details.paymentMethods.activate.disclaimer", { - activate: I18n.t("global.buttons.activate") - })} - - - - )} - - ); -}; - -export const useChangeActivationConfirmationBottomSheet = ( - representation: PaymentMethodRepresentation, - newVal: boolean, - onConfirm: () => void -) => { - const { cta } = getText(newVal ? "Activation" : "Deactivation"); - - const { present, bottomSheet, dismiss } = useLegacyIOBottomSheetModal( - dismiss()} - onConfirm={() => { - dismiss(); - onConfirm(); - }} - type={newVal ? "Activation" : "Deactivation"} - representation={{ - caption: representation.caption, - icon: representation.icon - }} - />, - newVal - ? I18n.t("bonus.bpd.details.paymentMethods.activate.title") - : I18n.t("bonus.bpd.details.paymentMethods.deactivate.title"), - 466, - dismiss()), - onPressWithGestureHandler: true - }} - rightButton={{ - ...confirmButtonProps(() => { - dismiss(); - onConfirm(); - }, cta), - onPressWithGestureHandler: true - }} - /> - ); - - return { present, bottomSheet, dismiss }; -}; diff --git a/ts/features/bonus/bpd/components/paymentMethodActivationToggle/bottomsheet/BpdNotActivableInformation.tsx b/ts/features/bonus/bpd/components/paymentMethodActivationToggle/bottomsheet/BpdNotActivableInformation.tsx deleted file mode 100644 index d3384635993..00000000000 --- a/ts/features/bonus/bpd/components/paymentMethodActivationToggle/bottomsheet/BpdNotActivableInformation.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import * as React from "react"; -import { View, StyleSheet } from "react-native"; -import { IOColors, VSpacer } from "@pagopa/io-app-design-system"; -import Markdown from "../../../../../../components/ui/Markdown"; -import I18n from "../../../../../../i18n"; -import { PaymentMethodRepresentation } from "../../../../../../types/pagopa"; -import { PaymentMethodRepresentationComponent } from "../base/PaymentMethodRepresentationComponent"; -import { useLegacyIOBottomSheetModal } from "../../../../../../utils/hooks/bottomSheet"; - -// NotActivable: already activated by someone else -// NotCompatible: missing bpd capability -export type NotActivableType = "NotActivable" | "NotCompatible"; - -type Props = { - type: NotActivableType; - representation: PaymentMethodRepresentation; -}; - -const styles = StyleSheet.create({ - body: { - flex: 1, - backgroundColor: IOColors.white - } -}); - -const getBody = (type: NotActivableType) => { - switch (type) { - case "NotActivable": - return I18n.t("bonus.bpd.details.paymentMethods.notActivable.body"); - case "NotCompatible": - return I18n.t("bonus.bpd.details.paymentMethods.notCompatible.body"); - } -}; - -const getTitle = (type: NotActivableType) => { - switch (type) { - case "NotActivable": - return I18n.t("bonus.bpd.details.paymentMethods.notActivable.title"); - case "NotCompatible": - return I18n.t("bonus.bpd.details.paymentMethods.notCompatible.title"); - } -}; - -export const BpdNotActivableInformation: React.FunctionComponent< - Props -> = props => ( - - - - - {getBody(props.type)} - -); - -export const useNotActivableInformationBottomSheet = ( - representation: PaymentMethodRepresentation, - type: NotActivableType -) => { - const { present, bottomSheet, dismiss } = useLegacyIOBottomSheetModal( - , - getTitle(type), - 310 - ); - - return { present, bottomSheet, dismiss }; -}; diff --git a/ts/features/bonus/bpd/components/paymentMethodActivationToggle/bottomsheet/OtherChannelInformation.tsx b/ts/features/bonus/bpd/components/paymentMethodActivationToggle/bottomsheet/OtherChannelInformation.tsx deleted file mode 100644 index 8931210b3d3..00000000000 --- a/ts/features/bonus/bpd/components/paymentMethodActivationToggle/bottomsheet/OtherChannelInformation.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { none } from "fp-ts/lib/Option"; -import { View } from "react-native"; -import * as React from "react"; -import { IOColors, VSpacer } from "@pagopa/io-app-design-system"; -import BlockButtons from "../../../../../../components/ui/BlockButtons"; -import Markdown from "../../../../../../components/ui/Markdown"; -import I18n from "../../../../../../i18n"; -import { navigateToWalletAddPaymentMethod } from "../../../../../../store/actions/navigation"; -import { useLegacyIOBottomSheetModal } from "../../../../../../utils/hooks/bottomSheet"; - -// NotActivable: already activated by someone else -// NotCompatible: missing bpd capability -export type NotActivableType = "NotActivable" | "NotCompatible"; - -type Props = { onAddPayment: () => void }; - -const addPaymentMethodButton = (onPress: () => void) => ( - -); - -export const OtherChannelInformation: React.FunctionComponent< - Props -> = props => ( - - - - {I18n.t("bonus.bpd.details.paymentMethods.activateOnOthersChannel.body")} - - - {addPaymentMethodButton(props.onAddPayment)} - -); - -export const useOtherChannelInformationBottomSheet = () => { - const { present, bottomSheet, dismiss } = useLegacyIOBottomSheetModal( - { - dismiss(); - navigateToWalletAddPaymentMethod({ inPayment: none }); - }} - />, - I18n.t("bonus.bpd.details.paymentMethods.activateOnOthersChannel.title"), - 350 - ); - return { present, bottomSheet, dismiss }; -}; diff --git a/ts/features/bonus/bpd/components/paymentMethodActivationToggle/list/PaymentMethodGroupedList.tsx b/ts/features/bonus/bpd/components/paymentMethodActivationToggle/list/PaymentMethodGroupedList.tsx deleted file mode 100644 index adabe6ae390..00000000000 --- a/ts/features/bonus/bpd/components/paymentMethodActivationToggle/list/PaymentMethodGroupedList.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import * as React from "react"; -import { View, StyleSheet } from "react-native"; -import { VSpacer } from "@pagopa/io-app-design-system"; -import { Body } from "../../../../../../components/core/typography/Body"; -import { H4 } from "../../../../../../components/core/typography/H4"; -import { Link } from "../../../../../../components/core/typography/Link"; -import I18n from "../../../../../../i18n"; -import { hasFunctionEnabled } from "../../../../../../utils/walletv2"; -import { useWhyOtherCardsBottomSheet } from "../../../screens/details/components/bottomsheet/WhyOtherCards"; -import { PaymentMethodWithActivation } from "../../../store/reducers/details/combiner"; -import { useOtherChannelInformationBottomSheet } from "../bottomsheet/OtherChannelInformation"; -import { EnableableFunctionsEnum } from "../../../../../../../definitions/pagopa/EnableableFunctions"; -import { PaymentMethodRawList } from "./PaymentMethodRawList"; - -type Props = { paymentList: ReadonlyArray }; - -const styles = StyleSheet.create({ - row: { flexDirection: "row", justifyContent: "space-between" } -}); - -const isOtherChannel = (w: PaymentMethodWithActivation) => - w.onboardingChannel === "EXT"; - -const isNotActivable = (w: PaymentMethodWithActivation) => - w.activationStatus === "notActivable" || - !hasFunctionEnabled(w, EnableableFunctionsEnum.BPD); - -/** - * A quick solution, not the best but the cardinality of the entities - * involved doesn't requires more efficient solutions - * @param paymentList - */ -const clusterizePaymentMethods = ( - paymentList: ReadonlyArray -) => ({ - otherChannels: paymentList.filter(isOtherChannel), - notActivable: paymentList.filter(isNotActivable), - activables: paymentList.filter(v => !isNotActivable(v) && !isOtherChannel(v)) -}); - -const OtherChannelsSection = (props: { - paymentMethods: ReadonlyArray; -}) => { - const { - present: presentOtherChannel, - bottomSheet: otherChannelInformationBottomSheet - } = useOtherChannelInformationBottomSheet(); - const { - present: presentWhyOthersCard, - bottomSheet: whyOtherCardsBottomSheet - } = useWhyOtherCardsBottomSheet(); - - return ( - - - - - {I18n.t( - "bonus.bpd.details.paymentMethods.activateOnOthersChannel.text1" - )} -

- {I18n.t( - "bonus.bpd.details.paymentMethods.activateOnOthersChannel.text2" - )} -

- - - {I18n.t("global.buttons.info").toLowerCase()} - -
- - - - - {I18n.t( - "bonus.bpd.details.paymentMethods.activateOnOthersChannel.whyOtherCards.title" - )} - - {otherChannelInformationBottomSheet} - {whyOtherCardsBottomSheet} -
- ); -}; - -const NotActivablesSection = (props: { - paymentMethods: ReadonlyArray; -}) => ( - - - - - {I18n.t("bonus.bpd.details.paymentMethods.notActivable.header")} - - - - - -); - -/** - * Render an array of WalletV2WithActivation, grouping different category of payment methods. - * @param props - * @constructor - */ -export const PaymentMethodGroupedList: React.FunctionComponent< - Props -> = props => { - const { activables, otherChannels, notActivable } = clusterizePaymentMethods( - props.paymentList - ); - return ( - - - {otherChannels.length > 0 && ( - - )} - {notActivable.length > 0 && ( - - )} - - ); -}; diff --git a/ts/features/bonus/bpd/components/paymentMethodActivationToggle/list/PaymentMethodRawList.tsx b/ts/features/bonus/bpd/components/paymentMethodActivationToggle/list/PaymentMethodRawList.tsx deleted file mode 100644 index 68a6ebce03e..00000000000 --- a/ts/features/bonus/bpd/components/paymentMethodActivationToggle/list/PaymentMethodRawList.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import * as React from "react"; -import { View } from "react-native"; -import { IOStyles } from "../../../../../../components/core/variables/IOStyles"; -import { PaymentMethod } from "../../../../../../types/pagopa"; -import { bpdToggleFactory } from "../BpdPaymentMethodToggleFactory"; - -type Props = { - paymentList: ReadonlyArray; -}; - -/** - * Render a {@link ReadOnlyArray} of {@link EnhancedPaymentMethod} using {@link PaymentMethodBpdToggle} - * @param props - * @constructor - */ -export const PaymentMethodRawList: React.FunctionComponent = props => ( - - {props.paymentList.map(pm => bpdToggleFactory(pm))} - -); diff --git a/ts/features/bonus/bpd/components/superCashbackRanking/LastPositionItem.tsx b/ts/features/bonus/bpd/components/superCashbackRanking/LastPositionItem.tsx deleted file mode 100644 index dea180116a5..00000000000 --- a/ts/features/bonus/bpd/components/superCashbackRanking/LastPositionItem.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import * as React from "react"; -import { formatNumberWithNoDigits } from "../../../../../utils/stringBuilder"; -import I18n from "../../../../../i18n"; -import RankPositionItem from "./RankPositionItem"; - -type Props = { - transactionsNumber: number; - superCashbackAmount: number; - lastAvailablePosition: number; -}; - -const THOUSAND_UNIT = 1000; - -const retrieveBoxedLabel = (lastAvailablePosition: number) => - lastAvailablePosition >= THOUSAND_UNIT - ? `${Math.floor(lastAvailablePosition / THOUSAND_UNIT)}K` - : `${lastAvailablePosition}`; - -export const LastPositionItem: React.FunctionComponent = ( - props: Props -) => ( - -); diff --git a/ts/features/bonus/bpd/components/superCashbackRanking/RankPositionItem.tsx b/ts/features/bonus/bpd/components/superCashbackRanking/RankPositionItem.tsx deleted file mode 100644 index 7bc8bf1fb63..00000000000 --- a/ts/features/bonus/bpd/components/superCashbackRanking/RankPositionItem.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import * as React from "react"; -import { View, StyleSheet } from "react-native"; -import { IOColors, HSpacer, VSpacer } from "@pagopa/io-app-design-system"; -import { IOStyles } from "../../../../../components/core/variables/IOStyles"; -import { H5 } from "../../../../../components/core/typography/H5"; -import { H4 } from "../../../../../components/core/typography/H4"; -import { - formatIntegerNumber, - formatNumberWithNoDigits -} from "../../../../../utils/stringBuilder"; -import I18n from "../../../../../i18n"; -import { IOBadge } from "../../../../../components/core/IOBadge"; - -type Props = { - transactionsNumber: number; - superCashbackAmount: number; - boxedLabel: string; - rankingLabel: string; - currentUserPosition?: boolean; - hideBadge?: boolean; -}; - -const style = StyleSheet.create({ - positionBox: { - paddingVertical: 8, - width: 48, - textAlign: "center" - } -}); - -const RankPositionItem = (props: Props): React.ReactElement => ( - <> - - -

- {props.boxedLabel} -

-
- - - -

{props.rankingLabel}

- {!props.hideBadge && ( - - - - )} -
-
- {I18n.t("bonus.bpd.details.transaction.label", { - defaultValue: I18n.t("bonus.bpd.details.transaction.label.other", { - transactions: formatIntegerNumber(props.transactionsNumber) - }), - count: props.transactionsNumber, - transactions: formatIntegerNumber(props.transactionsNumber) - })} -
-
-
- - -); - -export default RankPositionItem; diff --git a/ts/features/bonus/bpd/components/superCashbackRanking/SuperCashbackHeader.tsx b/ts/features/bonus/bpd/components/superCashbackRanking/SuperCashbackHeader.tsx deleted file mode 100644 index 3bee88f4be4..00000000000 --- a/ts/features/bonus/bpd/components/superCashbackRanking/SuperCashbackHeader.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import * as React from "react"; -import { View } from "react-native"; -import { connect } from "react-redux"; -import { HSpacer, VSpacer } from "@pagopa/io-app-design-system"; -import { IOBadge } from "../../../../../components/core/IOBadge"; -import { H3 } from "../../../../../components/core/typography/H3"; -import { IOStyles } from "../../../../../components/core/variables/IOStyles"; -import I18n from "../../../../../i18n"; -import { GlobalState } from "../../../../../store/reducers/types"; -import { BpdPeriodWithInfo } from "../../store/reducers/details/periods"; -import { bpdSelectedPeriodSelector } from "../../store/reducers/details/selectedPeriod"; - -type Props = ReturnType; - -const isPeriodOnGoing = (period: BpdPeriodWithInfo | undefined) => - pipe( - period, - O.fromNullable, - O.fold( - () => false, - p => { - if (p.status !== "Closed") { - return true; - } - - const actualDate = new Date(); - const endDate = new Date(p.endDate.getTime()); - endDate.setDate(endDate.getDate() + p.gracePeriod); - - return ( - actualDate.getTime() >= p.endDate.getTime() && - actualDate.getTime() <= endDate.getTime() - ); - } - ) - ); - -const SuperCashbackHeader: React.FunctionComponent = (props: Props) => ( - - {isPeriodOnGoing(props.selectedPeriod) && ( - <> - - - - )} - -

{I18n.t("bonus.bpd.details.superCashback.rankTitle")}

-
-); - -const mapStateToProps = (state: GlobalState) => ({ - selectedPeriod: bpdSelectedPeriodSelector(state) -}); - -export default connect(mapStateToProps)(SuperCashbackHeader); diff --git a/ts/features/bonus/bpd/components/superCashbackRanking/SuperCashbackRanking.tsx b/ts/features/bonus/bpd/components/superCashbackRanking/SuperCashbackRanking.tsx deleted file mode 100644 index 110da22ce20..00000000000 --- a/ts/features/bonus/bpd/components/superCashbackRanking/SuperCashbackRanking.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import * as React from "react"; -import { connect } from "react-redux"; -import { IOColors, VSpacer } from "@pagopa/io-app-design-system"; -import { H3 } from "../../../../../components/core/typography/H3"; -import { H4 } from "../../../../../components/core/typography/H4"; -import ItemSeparatorComponent from "../../../../../components/ItemSeparatorComponent"; -import Markdown from "../../../../../components/ui/Markdown"; -import I18n from "../../../../../i18n"; -import { GlobalState } from "../../../../../store/reducers/types"; -import { useLegacyIOBottomSheetModal } from "../../../../../utils/hooks/bottomSheet"; -import { - formatIntegerNumber, - formatNumberWithNoDigits -} from "../../../../../utils/stringBuilder"; -import { isBpdRankingReady } from "../../store/reducers/details/periods"; -import { bpdSelectedPeriodSelector } from "../../store/reducers/details/selectedPeriod"; -import { isInGracePeriod } from "../../utils/dates"; -import { LastPositionItem } from "./LastPositionItem"; -import SuperCashbackHeader from "./SuperCashbackHeader"; -import UserPositionItem from "./UserPositionItem"; - -type Props = ReturnType; - -const RankingItems: React.FunctionComponent = (props: Props) => { - if (props.selectedPeriod && isBpdRankingReady(props.selectedPeriod.ranking)) { - const mapRankingItems: Map = new Map< - number, - React.ReactNode - >([ - [ - props.selectedPeriod.minPosition, - - ], - [ - props.selectedPeriod.ranking.ranking, - - props.selectedPeriod.minPosition - } - userPosition={props.selectedPeriod.ranking.ranking} - /> - ] - ]); - - const key = [...mapRankingItems.keys()].sort((a, b) => a - b); - - return <>{key.map(k => mapRankingItems.get(k))}; - } - - return null; -}; - -const CSS_STYLE = ` -body { - color: ${IOColors.bluegreyDark} -} -`; - -const SuperCashbackBottomSheet: React.FunctionComponent = ( - props: Props -) => ( - <> - - - - - -

{I18n.t("bonus.bpd.details.superCashback.howItWorks.title")}

- - {props.selectedPeriod && ( - <> - - {I18n.t("bonus.bpd.details.superCashback.howItWorks.body", { - citizens: formatIntegerNumber(props.selectedPeriod.minPosition), - amount: formatNumberWithNoDigits( - props.selectedPeriod.superCashbackAmount - ) - })} - - - {props.selectedPeriod.status === "Active" || - isInGracePeriod( - props.selectedPeriod.endDate, - props.selectedPeriod.gracePeriod - ) ? ( - - {I18n.t("bonus.bpd.details.superCashback.howItWorks.status.active")} - - ) : ( -

- {I18n.t("bonus.bpd.details.superCashback.howItWorks.status.closed")} -

- )} - - )} - -); - -const mapStateToProps = (state: GlobalState) => ({ - selectedPeriod: bpdSelectedPeriodSelector(state) -}); - -const SuperCashbackRanking = connect(mapStateToProps)(SuperCashbackBottomSheet); - -export default SuperCashbackRanking; - -export const useSuperCashbackRankingBottomSheet = () => - useLegacyIOBottomSheetModal( - , - , - 470 - ); diff --git a/ts/features/bonus/bpd/components/superCashbackRanking/UserPositionItem.tsx b/ts/features/bonus/bpd/components/superCashbackRanking/UserPositionItem.tsx deleted file mode 100644 index 2dd1e69ecf5..00000000000 --- a/ts/features/bonus/bpd/components/superCashbackRanking/UserPositionItem.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import * as React from "react"; -import { connect } from "react-redux"; -import { GlobalState } from "../../../../../store/reducers/types"; -import { profileNameSelector } from "../../../../../store/reducers/profile"; -import { formatNumberWithNoDigits } from "../../../../../utils/stringBuilder"; -import I18n from "../../../../../i18n"; -import RankPositionItem from "./RankPositionItem"; - -type Props = { - transactionsNumber: number; - superCashbackAmount: number; - userPosition: number; - hideBadge?: boolean; -} & ReturnType; - -const UserPositionItem: React.FunctionComponent = (props: Props) => ( - -); - -const mapStateToProps = (state: GlobalState) => ({ - currentUser: profileNameSelector(state) -}); - -export default connect(mapStateToProps)(UserPositionItem); diff --git a/ts/features/bonus/bpd/components/superCashbackRanking/__test__/RankPositionItem.test.tsx b/ts/features/bonus/bpd/components/superCashbackRanking/__test__/RankPositionItem.test.tsx deleted file mode 100644 index 3a4b89ac6ac..00000000000 --- a/ts/features/bonus/bpd/components/superCashbackRanking/__test__/RankPositionItem.test.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { render } from "@testing-library/react-native"; -import * as React from "react"; -import { IOColors } from "@pagopa/io-app-design-system"; -import RankPositionItem from "../RankPositionItem"; -import I18n from "../../../../../../i18n"; -import { formatNumberWithNoDigits } from "../../../../../../utils/stringBuilder"; - -describe("RankingPositionItem", () => { - it("Expect an item representing user position with Super Cashback amount badge", () => { - const userLabel = I18n.t("global.you"); - const rankingLabel = `${formatNumberWithNoDigits(5000)}º: Maria`; - - const component = render( - - ); - - expect(component).not.toBeNull(); - expect(component.queryByTestId("PositionBoxContainer")).toHaveStyle({ - backgroundColor: IOColors.blue - }); - expect(component.queryByTestId("PositionBoxedLabel")).toHaveTextContent( - userLabel - ); - expect(component.queryByTestId("RankingLabel")).toHaveTextContent( - rankingLabel - ); - expect(component.queryByTestId("SuperCashbackAmountBadge")).toBeDefined(); - }); - - it("Expect an item representing user position without Super Cashback amount badge", () => { - const userLabel = I18n.t("global.you"); - const rankingLabel = `${formatNumberWithNoDigits(5000)}º: Maria`; - - const component = render( - - ); - - expect(component).not.toBeNull(); - expect(component.queryByTestId("PositionBoxContainer")).toHaveStyle({ - backgroundColor: IOColors.blue - }); - expect(component.queryByTestId("PositionBoxedLabel")).toHaveTextContent( - userLabel - ); - expect(component.queryByTestId("RankingLabel")).toHaveTextContent( - rankingLabel - ); - expect(component.queryByTestId("SuperCashbackAmountBadge")).toBeNull(); - }); - - it("Expect an item representing generic position", () => { - const boxedLabel = "100K"; - - const rankingLabel = I18n.t("bonus.bpd.details.superCashback.rankLabel", { - position: formatNumberWithNoDigits(1000) - }); - - const component = render( - - ); - - expect(component.queryByTestId("PositionBoxedLabel")).toHaveTextContent( - boxedLabel - ); - expect(component.queryByTestId("RankingLabel")).toHaveTextContent( - rankingLabel - ); - expect(component.queryByTestId("SuperCashbackAmountBadge")).toBeNull(); - }); -}); diff --git a/ts/features/bonus/bpd/components/transactionItem/BaseBpdTransactionItem.tsx b/ts/features/bonus/bpd/components/transactionItem/BaseBpdTransactionItem.tsx deleted file mode 100644 index 3e8467db04b..00000000000 --- a/ts/features/bonus/bpd/components/transactionItem/BaseBpdTransactionItem.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import * as React from "react"; -import { - View, - Image, - ImageSourcePropType, - StyleSheet, - TouchableOpacity, - TouchableWithoutFeedbackProps -} from "react-native"; -import { IOColors, HSpacer } from "@pagopa/io-app-design-system"; -import { H4 } from "../../../../../components/core/typography/H4"; -import { H5 } from "../../../../../components/core/typography/H5"; -import { IOStyles } from "../../../../../components/core/variables/IOStyles"; -import { ShadowBox } from "../../screens/details/components/summary/base/ShadowBox"; - -type Props = { - image: ImageSourcePropType; - title: string; - subtitle: string; - rightText: string; -} & Pick; - -const styles = StyleSheet.create({ - baseRow: { - flex: 1, - flexDirection: "row", - justifyContent: "space-between", - backgroundColor: IOColors.white - }, - leftRow: { - flex: 1, - flexDirection: "row", - paddingRight: 16 - }, - cardIcon: { - width: 40, - height: 25, - overflow: "hidden", - resizeMode: "contain", - alignSelf: "center" - }, - rightText: { alignSelf: "center" } -}); - -/** - * Graphical settings and layout for the BpdTransactionItem - * @param props - * @constructor - */ -export const BaseBpdTransactionItem: React.FunctionComponent = props => ( - - - {/* The base row */} - - {/* The left side of the row (icon, space, top text, bottom text */} - - - - -

- {props.title} -

-
{props.subtitle}
-
-
- {/* The right side of the row */} -

- {props.rightText} -

-
-
-
-); diff --git a/ts/features/bonus/bpd/components/transactionItem/BpdTransactionItem.tsx b/ts/features/bonus/bpd/components/transactionItem/BpdTransactionItem.tsx deleted file mode 100644 index d0cd2f803d4..00000000000 --- a/ts/features/bonus/bpd/components/transactionItem/BpdTransactionItem.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import * as React from "react"; -import { ImageSourcePropType } from "react-native"; -import I18n from "../../../../../i18n"; -import { useLegacyIOBottomSheetModal } from "../../../../../utils/hooks/bottomSheet"; -import { localeDateFormat } from "../../../../../utils/locale"; -import { formatNumberAmount } from "../../../../../utils/stringBuilder"; -import { - BpdTransactionDetailComponent, - BpdTransactionDetailRepresentation -} from "../../screens/details/transaction/detail/BpdTransactionDetailComponent"; -import { BpdTransaction } from "../../store/actions/transactions"; -import { BaseBpdTransactionItem } from "./BaseBpdTransactionItem"; - -export type EnhancedBpdTransaction = { - image: ImageSourcePropType; - title: string; - keyId: string; - maxCashbackForTransactionAmount: number | undefined; -} & BpdTransaction; - -type Props = { - transaction: BpdTransactionDetailRepresentation; -}; - -/** - * Create the subtitle for the transaction item, based on the trx date and trx amount - * TODO: move the get subtitle in the combiner and remove this component? - * @param transaction - */ -export const getSubtitle = (transaction: BpdTransaction) => { - const isMidNight = - transaction.trxDate.getHours() + - transaction.trxDate.getMinutes() + - transaction.trxDate.getSeconds() === - 0; - return isMidNight - ? `€ ${formatNumberAmount(transaction.amount)} · ${localeDateFormat( - transaction.trxDate, - I18n.t("global.dateFormats.dayMonthWithoutTime") - )} ` - : `€ ${formatNumberAmount(transaction.amount)} · ${localeDateFormat( - transaction.trxDate, - I18n.t("global.dateFormats.dayMonthWithTime") - )} `; -}; - -export const BpdTransactionItem: React.FunctionComponent = props => { - const { present: openBottomSheet, bottomSheet } = useLegacyIOBottomSheetModal( - , - I18n.t("bonus.bpd.details.transaction.detail.title"), - 522 - ); - - return ( - <> - - {bottomSheet} - - ); -}; diff --git a/ts/features/bonus/bpd/components/transactionItem/__test__/BdpTransactionItem.test.tsx b/ts/features/bonus/bpd/components/transactionItem/__test__/BdpTransactionItem.test.tsx deleted file mode 100644 index 18d354b89c8..00000000000 --- a/ts/features/bonus/bpd/components/transactionItem/__test__/BdpTransactionItem.test.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { getSubtitle } from "../BpdTransactionItem"; -import { EnhancedBpdTransaction } from "../../../components/transactionItem/BpdTransactionItem"; -import { HPan } from "../../../store/actions/paymentMethods"; -import { AwardPeriodId } from "../../../store/actions/periods"; - -describe("Test how the transaction subtitle changes with different timestamps", () => { - it("Subtitle shouldn't contain hours and minutes, when the transaction has a timestamp 00:00", () => { - const myTransaction: EnhancedBpdTransaction = { - hashPan: - "0d4194712c5d820fcbbb2e7ba199e15f73cfd43f8fe49f0aa62e7901253506df" as HPan, - idTrxAcquirer: "10126487773E", - idTrxIssuer: "R64692", - amount: 87.79, - awardPeriodId: 2 as AwardPeriodId, - image: 29, - maxCashbackForTransactionAmount: 15, - title: "xxxxxxx", - trxDate: new Date("2100-12-17T00:00"), - keyId: "xxxxxxxxx", - cashback: 8.779, - circuitType: "Mastercard / Maestro" - }; - - expect(getSubtitle(myTransaction)).toMatch("€ 87.79 · 17 Dec "); - }); - - it("Subtitle should contain hours and minutes when the transaction has a timestamp 00:00", () => { - const myTransaction: EnhancedBpdTransaction = { - hashPan: - "0d4194712c5d820fcbbb2e7ba199e15f73cfd43f8fe49f0aa62e7901253506df" as HPan, - idTrxAcquirer: "10126487773E", - idTrxIssuer: "R64692", - amount: 100000.79, - awardPeriodId: 2 as AwardPeriodId, - image: 29, - maxCashbackForTransactionAmount: 15, - title: "xxxxxxx", - trxDate: new Date("2100-12-19T12:39"), - keyId: "xxxxxxxxx", - cashback: 8.779, - circuitType: "Mastercard / Maestro" - }; - - expect(getSubtitle(myTransaction)).toMatch("€ 100,000.79 · 19 Dec, 12:39 "); - }); -}); diff --git a/ts/features/bonus/bpd/components/walletCardContainer/BpdCardsInWalletComponent.tsx b/ts/features/bonus/bpd/components/walletCardContainer/BpdCardsInWalletComponent.tsx deleted file mode 100644 index 5bf2e4268ae..00000000000 --- a/ts/features/bonus/bpd/components/walletCardContainer/BpdCardsInWalletComponent.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import _ from "lodash"; -import { View } from "react-native"; -import * as React from "react"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { GlobalState } from "../../../../../store/reducers/types"; -import { bpdSelectPeriod } from "../../store/actions/selectedPeriod"; -import { bpdPeriodsAmountWalletVisibleSelector } from "../../store/reducers/details/combiner"; -import { BpdPeriodWithInfo } from "../../store/reducers/details/periods"; -import { BpdCardComponent } from "../bpdCardComponent/BpdCardComponent"; - -type Props = ReturnType & - ReturnType; - -const BpdCardList = (props: Props) => ( - - {pot.isSome(props.periodsWithAmount) && - props.periodsWithAmount.value.map(pa => ( - { - props.navigateToCashbackDetails(pa); - }} - /> - ))} - -); - -const BpdCardListMemo = React.memo( - BpdCardList, - (prev: Props, curr: Props) => - pot.isSome(prev.periodsWithAmount) && - pot.isSome(curr.periodsWithAmount) && - _.isEqual(curr.periodsWithAmount.value, prev.periodsWithAmount.value) -); - -/** - * Render the bpd card in the wallet - * //TODO: handle error, render only if some - * @constructor - */ -const BpdCardsInWalletContainer = (props: Props) => ( - -); - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - navigateToCashbackDetails: (period: BpdPeriodWithInfo) => { - dispatch(bpdSelectPeriod(period)); - } -}); - -const mapStateToProps = (state: GlobalState) => ({ - periodsWithAmount: bpdPeriodsAmountWalletVisibleSelector(state) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(BpdCardsInWalletContainer); diff --git a/ts/features/bonus/bpd/saga/index.ts b/ts/features/bonus/bpd/saga/index.ts deleted file mode 100644 index f2ce5a2cdca..00000000000 --- a/ts/features/bonus/bpd/saga/index.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { SagaIterator } from "redux-saga"; -import { takeEvery, takeLatest } from "typed-redux-saga/macro"; -import { getType } from "typesafe-actions"; -import { bpdApiUrlPrefix } from "../../../../config"; -import { BackendBpdClient } from "../api/backendBpdClient"; -import { bpdAllData, bpdLoadActivationStatus } from "../store/actions/details"; -import { bpdIbanInsertionStart, bpdUpsertIban } from "../store/actions/iban"; -import { - bpdDeleteUserFromProgram, - bpdEnrollUserToProgram, - bpdOnboardingAcceptDeclaration, - bpdOnboardingStart -} from "../store/actions/onboarding"; -import { - bpdPaymentMethodActivation, - bpdUpdatePaymentMethodActivation -} from "../store/actions/paymentMethods"; -import { bpdPeriodsAmountLoad } from "../store/actions/periods"; -import { - bpdTransactionsLoadCountByDay, - bpdTransactionsLoadMilestone, - bpdTransactionsLoadPage, - bpdTransactionsLoadRequiredData -} from "../store/actions/transactions"; -import { deleteCitizen, getCitizenV2, putEnrollCitizenV2 } from "./networking"; -import { loadBpdData } from "./networking/loadBpdData"; -import { loadPeriodsWithInfo } from "./networking/loadPeriodsWithInfo"; -import { patchCitizenIban } from "./networking/patchCitizenIban"; -import { - bpdLoadPaymentMethodActivationSaga, - bpdUpdatePaymentMethodActivationSaga -} from "./networking/paymentMethod"; -import { handleLoadMilestone } from "./networking/ranking"; -import { handleCountByDay } from "./networking/winning-transactions/countByDay"; -import { handleTransactionsLoadRequiredData } from "./networking/winning-transactions/loadTransactionsRequiredData"; -import { handleTransactionsPage } from "./networking/winning-transactions/transactionsPage"; -import { handleBpdIbanInsertion } from "./orchestration/insertIban"; -import { handleBpdEnroll } from "./orchestration/onboarding/enrollToBpd"; -import { handleBpdStartOnboardingSaga } from "./orchestration/onboarding/startOnboarding"; - -// watch all events about bpd -export function* watchBonusBpdSaga(bpdBearerToken: string): SagaIterator { - const bpdBackendClient = BackendBpdClient(bpdApiUrlPrefix, bpdBearerToken); - - // load citizen details - yield* takeLatest( - bpdLoadActivationStatus.request, - getCitizenV2, - bpdBackendClient.findV2 - ); - // enroll citizen to the bpd - yield* takeLatest( - bpdEnrollUserToProgram.request, - putEnrollCitizenV2, - bpdBackendClient.enrollCitizenV2IO - ); - - // delete citizen from the bpd - yield* takeLatest( - bpdDeleteUserFromProgram.request, - deleteCitizen, - bpdBackendClient.deleteCitizenIO - ); - - // upsert citizen iban - yield* takeLatest( - bpdUpsertIban.request, - patchCitizenIban, - bpdBackendClient.updatePaymentMethod - ); - - // load bpd activation status for a specific payment method - yield* takeEvery( - bpdPaymentMethodActivation.request, - bpdLoadPaymentMethodActivationSaga, - bpdBackendClient.findPayment - ); - - // update bpd activation status for a specific payment method - yield* takeEvery( - bpdUpdatePaymentMethodActivation.request, - bpdUpdatePaymentMethodActivationSaga, - bpdBackendClient.enrollPayment, - bpdBackendClient.deletePayment - ); - - // prefetch all the bpd data - yield* takeEvery(bpdAllData.request, loadBpdData); - - // Load bpd periods with amount - yield* takeEvery( - bpdPeriodsAmountLoad.request, - loadPeriodsWithInfo, - bpdBackendClient - ); - - // Load count by day info for a period - yield* takeEvery( - bpdTransactionsLoadCountByDay.request, - handleCountByDay, - bpdBackendClient.winningTransactionsV2CountByDay - ); - - // Load the milestone (pivot) information for a period - yield* takeEvery( - bpdTransactionsLoadMilestone.request, - handleLoadMilestone, - bpdBackendClient.getRankingV2 - ); - - // Load a transactions page for a period - yield* takeEvery( - bpdTransactionsLoadPage.request, - handleTransactionsPage, - bpdBackendClient.winningTransactionsV2 - ); - - // Load all the required transactions data, for a period - yield* takeEvery( - bpdTransactionsLoadRequiredData.request, - handleTransactionsLoadRequiredData - ); - - // First step of the onboarding workflow; check if the user is enrolled to the bpd program - yield* takeLatest(getType(bpdOnboardingStart), handleBpdStartOnboardingSaga); - - // The user accepts the declaration, enroll the user to the bpd program - yield* takeLatest(getType(bpdOnboardingAcceptDeclaration), handleBpdEnroll); - - // The user start the insertion / modification of the IBAN associated with bpd program - yield* takeLatest(getType(bpdIbanInsertionStart), handleBpdIbanInsertion); -} diff --git a/ts/features/bonus/bpd/saga/networking/__test__/loadBpdData.test.ts b/ts/features/bonus/bpd/saga/networking/__test__/loadBpdData.test.ts deleted file mode 100644 index 428152d161d..00000000000 --- a/ts/features/bonus/bpd/saga/networking/__test__/loadBpdData.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { testSaga } from "redux-saga-test-plan"; -import { abiSelector } from "../../../../../wallet/onboarding/store/abi"; -import { remoteReady } from "../../../../../../common/model/RemoteValue"; -import { - bpdAllData, - bpdLoadActivationStatus -} from "../../../store/actions/details"; -import { bpdPeriodsAmountLoad } from "../../../store/actions/periods"; -import { loadBpdData, testableFunctions } from "../loadBpdData"; - -// TODO: tested only two base case, add more if needed -describe("loadBpdData", () => { - it("Dispatch bpdAllData.success if the user is not enrolled", () => { - testSaga(loadBpdData) - .next() - .select(abiSelector) - .next(remoteReady({})) - .call(testableFunctions.checkPreviousFailures!) - .next() - .put(bpdLoadActivationStatus.request()) - .next() - .take([bpdLoadActivationStatus.success, bpdLoadActivationStatus.failure]) - .next( - bpdLoadActivationStatus.success({ - enabled: false, - activationStatus: "never", - payoffInstr: undefined - }) - ) - .put(bpdAllData.success()) - .next() - .isDone(); - }); - it("Dispatch bpdAllData.success if the user is enrolled and all subsequent requests are successful", () => { - testSaga(loadBpdData) - .next() - .select(abiSelector) - .next(remoteReady({})) - .call(testableFunctions.checkPreviousFailures!) - .next() - .put(bpdLoadActivationStatus.request()) - .next() - .take([bpdLoadActivationStatus.success, bpdLoadActivationStatus.failure]) - .next( - bpdLoadActivationStatus.success({ - enabled: true, - activationStatus: "subscribed", - payoffInstr: undefined - }) - ) - .put(bpdPeriodsAmountLoad.request()) - .next() - .take([bpdPeriodsAmountLoad.success, bpdPeriodsAmountLoad.failure]) - .next(bpdPeriodsAmountLoad.success([])) - .put(bpdAllData.success()) - .next() - .isDone(); - }); -}); diff --git a/ts/features/bonus/bpd/saga/networking/__test__/loadPeriodsAmount.test.ts b/ts/features/bonus/bpd/saga/networking/__test__/loadPeriodsAmount.test.ts deleted file mode 100644 index 940eed1c7a5..00000000000 --- a/ts/features/bonus/bpd/saga/networking/__test__/loadPeriodsAmount.test.ts +++ /dev/null @@ -1,249 +0,0 @@ -import * as E from "fp-ts/lib/Either"; -import { expectSaga } from "redux-saga-test-plan"; -import { call } from "redux-saga-test-plan/matchers"; -import { - AwardPeriodId, - BpdPeriod, - bpdPeriodsAmountLoad -} from "../../../store/actions/periods"; -import { - notEligibleAmount, - zeroAmount -} from "../../../store/reducers/__mock__/amount"; -import { - activePeriod, - closedPeriod, - inactivePeriod, - withAwardPeriodId -} from "../../../store/reducers/__mock__/periods"; -import { - notReadyRanking, - readyRanking -} from "../../../store/reducers/__mock__/ranking"; -import { BpdAmount, bpdLoadAmountSaga } from "../amount"; -import { loadPeriodsWithInfo } from "../loadPeriodsWithInfo"; -import { bpdLoadPeriodsSaga } from "../periods"; -import { bpdLoadRakingV2 } from "../ranking"; - -describe("loadPeriodsAmount, mock networking saga", () => { - it("Dispatch failure if awardsPeriods fails", async () => { - const awardPeriodFailure = new Error("Error while loading periods"); - const backendClient = { - totalCashback: jest.fn(), - awardPeriods: jest.fn(), - getRankingV2: jest.fn() - }; - await expectSaga(loadPeriodsWithInfo, backendClient) - .provide([ - [ - call(bpdLoadPeriodsSaga, backendClient.awardPeriods), - E.left(awardPeriodFailure) - ] - ]) - .put(bpdPeriodsAmountLoad.failure(awardPeriodFailure)) - .run(); - }); - - it("Dispatch failure if a single totalCashback is left", async () => { - const totalCashbackFailure = new Error("Error for a single amount"); - const backendClient = { - totalCashback: jest.fn(), - awardPeriods: jest.fn(), - getRankingV2: jest.fn() - }; - await expectSaga(loadPeriodsWithInfo, backendClient) - .provide([ - [ - call(bpdLoadPeriodsSaga, backendClient.awardPeriods), - E.right>([activePeriod, closedPeriod]) - ], - [ - call(bpdLoadAmountSaga, backendClient.totalCashback, 0), - E.right(zeroAmount) - ], - [ - call(bpdLoadAmountSaga, backendClient.totalCashback, 1), - E.left(totalCashbackFailure) - ], - [ - call(bpdLoadRakingV2, backendClient.getRankingV2), - E.right([readyRanking]) - ] - ]) - .put( - bpdPeriodsAmountLoad.failure(new Error("Error while loading amounts")) - ) - .run(); - }); - it("Dispatch failure if all the totalCashback are E.left", async () => { - const totalCashbackFailure = new Error("Error for a single amount"); - const backendClient = { - totalCashback: jest.fn(), - awardPeriods: jest.fn(), - getRankingV2: jest.fn() - }; - await expectSaga(loadPeriodsWithInfo, backendClient) - .provide([ - [ - call(bpdLoadPeriodsSaga, backendClient.awardPeriods), - E.right>([activePeriod, closedPeriod]) - ], - [ - call(bpdLoadAmountSaga, backendClient.totalCashback, 0), - E.left(totalCashbackFailure) - ], - [ - call(bpdLoadAmountSaga, backendClient.totalCashback, 1), - E.left(totalCashbackFailure) - ], - [ - call(bpdLoadRakingV2, backendClient.getRankingV2), - E.right([readyRanking]) - ] - ]) - .put( - bpdPeriodsAmountLoad.failure(new Error("Error while loading amounts")) - ) - .run(); - }); - - it("Dispatch failure if load ranking is E.left", async () => { - const totalCashbackFailure = new Error("Error for a single amount"); - const backendClient = { - totalCashback: jest.fn(), - awardPeriods: jest.fn(), - getRankingV2: jest.fn() - }; - await expectSaga(loadPeriodsWithInfo, backendClient) - .provide([ - [ - call(bpdLoadPeriodsSaga, backendClient.awardPeriods), - E.right>([activePeriod, closedPeriod]) - ], - [ - call(bpdLoadAmountSaga, backendClient.totalCashback, 0), - E.left(totalCashbackFailure) - ], - [ - call(bpdLoadAmountSaga, backendClient.totalCashback, 1), - E.left(totalCashbackFailure) - ], - [ - call(bpdLoadRakingV2, backendClient.getRankingV2), - E.left(new Error("error")) - ] - ]) - .put( - bpdPeriodsAmountLoad.failure(new Error("Error while loading rankings")) - ) - .run(); - }); - - it("Dispatch success if all the totalCashback are E.right", async () => { - const amountForPeriod0: BpdAmount = { - ...zeroAmount, - awardPeriodId: 0 as AwardPeriodId - }; - const amountForPeriod1: BpdAmount = { - ...notEligibleAmount, - awardPeriodId: 1 as AwardPeriodId - }; - const backendClient = { - totalCashback: jest.fn(), - awardPeriods: jest.fn(), - getRankingV2: jest.fn() - }; - await expectSaga(loadPeriodsWithInfo, backendClient) - .provide([ - [ - call(bpdLoadPeriodsSaga, backendClient.awardPeriods), - E.right>([activePeriod, closedPeriod]) - ], - [ - call(bpdLoadAmountSaga, backendClient.totalCashback, 0), - E.right(amountForPeriod0) - ], - [ - call(bpdLoadAmountSaga, backendClient.totalCashback, 1), - E.right(amountForPeriod1) - ], - [ - call(bpdLoadRakingV2, backendClient.getRankingV2), - E.right([readyRanking]) - ] - ]) - .put( - bpdPeriodsAmountLoad.success([ - { ...activePeriod, amount: amountForPeriod1, ranking: readyRanking }, - { - ...closedPeriod, - amount: amountForPeriod0, - ranking: notReadyRanking - } - ]) - ) - .run(); - }); - it("Dispatch success if all the totalCashback are E.right, do not request inactive periods", async () => { - const amountForPeriod0: BpdAmount = { - ...zeroAmount, - awardPeriodId: 0 as AwardPeriodId - }; - const amountForPeriod1: BpdAmount = { - ...notEligibleAmount, - awardPeriodId: 1 as AwardPeriodId - }; - const amountForPeriod2: BpdAmount = { - ...notEligibleAmount, - awardPeriodId: 2 as AwardPeriodId - }; - const backendClient = { - totalCashback: jest.fn(), - awardPeriods: jest.fn(), - getRankingV2: jest.fn() - }; - await expectSaga(loadPeriodsWithInfo, backendClient) - .provide([ - [ - call(bpdLoadPeriodsSaga, backendClient.awardPeriods), - E.right>([ - activePeriod, - closedPeriod, - inactivePeriod - ]) - ], - [ - call(bpdLoadAmountSaga, backendClient.totalCashback, 0), - E.right(amountForPeriod0) - ], - [ - call(bpdLoadAmountSaga, backendClient.totalCashback, 1), - E.right(amountForPeriod1) - ], - [ - call(bpdLoadAmountSaga, backendClient.totalCashback, 2), - E.right(amountForPeriod2) - ], - [ - call(bpdLoadRakingV2, backendClient.getRankingV2), - E.right([readyRanking]) - ] - ]) - .put( - bpdPeriodsAmountLoad.success([ - { - ...inactivePeriod, - amount: { ...zeroAmount, awardPeriodId: 2 as AwardPeriodId }, - ranking: withAwardPeriodId(notReadyRanking, 2 as AwardPeriodId) - }, - { ...activePeriod, amount: amountForPeriod1, ranking: readyRanking }, - { - ...closedPeriod, - amount: amountForPeriod0, - ranking: notReadyRanking - } - ]) - ) - .run(); - }); -}); diff --git a/ts/features/bonus/bpd/saga/networking/amount.ts b/ts/features/bonus/bpd/saga/networking/amount.ts deleted file mode 100644 index 3df0552cfc5..00000000000 --- a/ts/features/bonus/bpd/saga/networking/amount.ts +++ /dev/null @@ -1,78 +0,0 @@ -import * as E from "fp-ts/lib/Either"; -import { readableReport } from "@pagopa/ts-commons/lib/reporters"; -import { call } from "typed-redux-saga/macro"; -import { TotalCashbackResource } from "../../../../../../definitions/bpd/winning_transactions/TotalCashbackResource"; -import { mixpanelTrack } from "../../../../../mixpanel"; -import { - ReduxSagaEffect, - SagaCallReturnType -} from "../../../../../types/utils"; -import { convertUnknownToError, getError } from "../../../../../utils/errors"; -import { BackendBpdClient } from "../../api/backendBpdClient"; -import { AwardPeriodId, WithAwardPeriodId } from "../../store/actions/periods"; - -export type BpdAmount = WithAwardPeriodId & { - // The total cashback amount gained by the user in the period (without super cashback) - totalCashback: number; - // The total transaction number in the period for the user - transactionNumber: number; -}; - -export type BpdAmountError = WithAwardPeriodId & { - error: Error; -}; - -// convert a network payload amount into the relative app domain model -const convertAmount = ( - networkAmount: TotalCashbackResource, - awardPeriodId: AwardPeriodId -): BpdAmount => ({ - totalCashback: networkAmount.totalCashback, - transactionNumber: networkAmount.transactionNumber, - awardPeriodId -}); - -const mixpanelActionRequest = "BPD_AMOUNT_REQUEST"; -const mixpanelActionSuccess = "BPD_AMOUNT_SUCCESS"; -const mixpanelActionFailure = "BPD_AMOUNT_FAILURE"; - -/** - * Networking code to request the amount for a specified period. - * @param totalCashback - * @param awardPeriodId - */ -export function* bpdLoadAmountSaga( - totalCashback: ReturnType["totalCashback"], - awardPeriodId: AwardPeriodId -): Generator< - ReduxSagaEffect, - E.Either, - SagaCallReturnType -> { - void mixpanelTrack(mixpanelActionRequest, { awardPeriodId }); - try { - const totalCashbackResult: SagaCallReturnType = - yield* call(totalCashback, { awardPeriodId } as any); - if (E.isRight(totalCashbackResult)) { - if (totalCashbackResult.right.status === 200) { - void mixpanelTrack(mixpanelActionSuccess, { awardPeriodId }); - return E.right( - convertAmount(totalCashbackResult.right.value, awardPeriodId) - ); - } else { - throw new Error(`response status ${totalCashbackResult.right.status}`); - } - } else { - throw new Error(readableReport(totalCashbackResult.left)); - } - } catch (e) { - void mixpanelTrack(mixpanelActionFailure, { - awardPeriodId, - reason: convertUnknownToError(e).message - }); - return E.left({ - error: getError(e), - awardPeriodId - }); - } -} diff --git a/ts/features/bonus/bpd/saga/networking/index.ts b/ts/features/bonus/bpd/saga/networking/index.ts deleted file mode 100644 index af53a972b07..00000000000 --- a/ts/features/bonus/bpd/saga/networking/index.ts +++ /dev/null @@ -1,146 +0,0 @@ -import { readableReport } from "@pagopa/ts-commons/lib/reporters"; -import { SagaIterator } from "@redux-saga/core"; -import * as E from "fp-ts/lib/Either"; -import { call, put } from "typed-redux-saga/macro"; -import { ActionType } from "typesafe-actions"; -import { SagaCallReturnType } from "../../../../../types/utils"; -import { convertUnknownToError, getError } from "../../../../../utils/errors"; -import { BackendBpdClient } from "../../api/backendBpdClient"; -import { bpdLoadActivationStatus } from "../../store/actions/details"; -import { - bpdDeleteUserFromProgram, - bpdEnrollUserToProgram, - bpdUpdateOptInStatusMethod -} from "../../store/actions/onboarding"; - -export function* executeAndDispatchV2( - remoteCall: - | ReturnType["enrollCitizenV2IO"] - | ReturnType["findV2"], - action: typeof bpdEnrollUserToProgram | typeof bpdLoadActivationStatus -) { - try { - const enrollCitizenIOResult: SagaCallReturnType = - yield* call( - remoteCall, - // due to avoid required headers coming from code autogenerate - // (note the required header will be injected automatically) - {} as any - ); - if (E.isRight(enrollCitizenIOResult)) { - if (enrollCitizenIOResult.right.status === 200) { - const { enabled, payoffInstr, technicalAccount, optInStatus } = - enrollCitizenIOResult.right.value; - yield* put( - action.success({ - enabled, - activationStatus: enabled ? "subscribed" : "unsubscribed", - payoffInstr, - technicalAccount, - optInStatus - }) - ); - return; - } else if (enrollCitizenIOResult.right.status === 404) { - yield* put( - action.success({ - enabled: false, - activationStatus: "never", - payoffInstr: undefined, - technicalAccount: undefined - }) - ); - return; - } - throw new Error(`response status ${enrollCitizenIOResult.right.status}`); - } else { - throw new Error(readableReport(enrollCitizenIOResult.left)); - } - } catch (e) { - yield* put(action.failure(convertUnknownToError(e))); - } -} - -export function* getCitizenV2( - findCitizen: ReturnType["findV2"] -): SagaIterator { - yield* call(executeAndDispatchV2, findCitizen, bpdLoadActivationStatus); -} - -export function* putEnrollCitizenV2( - enrollCitizenIO: ReturnType["enrollCitizenV2IO"] -): SagaIterator { - yield* call(executeAndDispatchV2, enrollCitizenIO, bpdEnrollUserToProgram); -} - -/** - * update the citizen OptInStatus - * @param updateCitizenIO - * @param action - */ -export function* putOptInStatusCitizenV2( - updateCitizenIO: ReturnType["enrollCitizenV2IO"], - action: ActionType -) { - try { - const updateCitizenIOResult: SagaCallReturnType = - yield* call( - updateCitizenIO, - // due to avoid required headers coming from code autogenerate - // (note the required header will be injected automatically) - { optInStatus: action.payload } as any - ); - - if (E.isRight(updateCitizenIOResult)) { - if (updateCitizenIOResult.right.status === 200) { - if (updateCitizenIOResult.right.value.optInStatus) { - const { optInStatus } = updateCitizenIOResult.right.value; - yield* put(bpdUpdateOptInStatusMethod.success(optInStatus)); - return; - } else { - // it should never happen - bpdUpdateOptInStatusMethod.failure( - new Error(`optInStatus is undefined`) - ); - } - } else { - yield* put( - bpdUpdateOptInStatusMethod.failure( - new Error(`response status ${updateCitizenIOResult.right.status}`) - ) - ); - } - } else { - yield* put( - bpdUpdateOptInStatusMethod.failure( - new Error(readableReport(updateCitizenIOResult.left)) - ) - ); - } - } catch (e) { - yield* put(bpdUpdateOptInStatusMethod.failure(getError(e))); - } -} - -/** - * make a request to delete citizen from bpd program - */ -export function* deleteCitizen( - deleteCitizenIO: ReturnType["deleteCitizenIO"] -): SagaIterator { - try { - const deleteCitizenIOResult: SagaCallReturnType = - yield* call(deleteCitizenIO, {} as any); - if (E.isRight(deleteCitizenIOResult)) { - if (deleteCitizenIOResult.right.status === 204) { - yield* put(bpdDeleteUserFromProgram.success()); - return; - } - throw new Error(`response status ${deleteCitizenIOResult.right.status}`); - } else { - throw new Error(readableReport(deleteCitizenIOResult.left)); - } - } catch (e) { - yield* put(bpdDeleteUserFromProgram.failure(convertUnknownToError(e))); - } -} diff --git a/ts/features/bonus/bpd/saga/networking/loadBpdData.ts b/ts/features/bonus/bpd/saga/networking/loadBpdData.ts deleted file mode 100644 index a0e9907905f..00000000000 --- a/ts/features/bonus/bpd/saga/networking/loadBpdData.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { call, delay, put, select, take } from "typed-redux-saga/macro"; -import { ActionType, getType } from "typesafe-actions"; -import { SagaCallReturnType } from "../../../../../types/utils"; -import { getBackoffTime } from "../../../../../utils/backoffError"; -import { isTestEnv } from "../../../../../utils/environment"; -import { loadAbi } from "../../../../wallet/onboarding/bancomat/store/actions"; -import { abiSelector } from "../../../../wallet/onboarding/store/abi"; -import { isReady } from "../../../../../common/model/RemoteValue"; -import { - bpdAllData, - bpdLoadActivationStatus -} from "../../store/actions/details"; -import { bpdPeriodsAmountLoad } from "../../store/actions/periods"; - -/** - * retrieve possible backoff waiting time and if there is, wait that time - */ -function* checkPreviousFailures() { - // wait if some previous errors occurred - const loadActivationBackOff: SagaCallReturnType = - yield* call(getBackoffTime, bpdLoadActivationStatus.failure); - const loadPeriodsBackOff: SagaCallReturnType = - yield* call(getBackoffTime, bpdPeriodsAmountLoad.failure); - const waitingTime = Math.max(loadActivationBackOff, loadPeriodsBackOff); - if (waitingTime > 0) { - yield* delay(waitingTime); - } -} - -/** - * Load all the BPD details data: - * - Activation Status - * - Abi list - * - Periods - * - Amount foreach period !== "Inactive" - * - Transactions foreach period !== "Inactive" - */ -export function* loadBpdData() { - // TODO: move from here - const abiList: ReturnType = yield* select(abiSelector); - - // The volatility of the bank list is extremely low. - // There is no need to refresh it every time. - // A further refinement could be to insert an expiring cache - if (!isReady(abiList)) { - yield* put(loadAbi.request()); - } - yield* call(checkPreviousFailures); - // First request the bpd activation status - yield* put(bpdLoadActivationStatus.request()); - - const activationStatus = yield* take< - ActionType< - | typeof bpdLoadActivationStatus.success - | typeof bpdLoadActivationStatus.failure - > - >([bpdLoadActivationStatus.success, bpdLoadActivationStatus.failure]); - - if (activationStatus.type === getType(bpdLoadActivationStatus.success)) { - // if the user is not registered with bpd, - // there is no need to request other data as it is never allowed to view closed periods - if (!activationStatus.payload.enabled) { - yield* put(bpdAllData.success()); - return; - } - - // In case of success, request the periods, amounts and ranking foreach required period - yield* put(bpdPeriodsAmountLoad.request()); - - const periods = yield* take< - ActionType< - | typeof bpdPeriodsAmountLoad.success - | typeof bpdPeriodsAmountLoad.failure - > - >([bpdPeriodsAmountLoad.success, bpdPeriodsAmountLoad.failure]); - - if (periods.type === getType(bpdPeriodsAmountLoad.success)) { - // The load of all the required data for bpd is now completed with success - yield* put(bpdAllData.success()); - } else { - // The load of all the required bpd data is failed - yield* put(bpdAllData.failure(periods.payload)); - } - } else { - // The load of all the required bpd data is failed - yield* put(bpdAllData.failure(activationStatus.payload)); - } -} - -// to keep solid code encapsulation -export const testableFunctions = { - checkPreviousFailures: isTestEnv ? checkPreviousFailures : undefined -}; diff --git a/ts/features/bonus/bpd/saga/networking/loadPeriodsWithInfo.ts b/ts/features/bonus/bpd/saga/networking/loadPeriodsWithInfo.ts deleted file mode 100644 index 62aa95d5945..00000000000 --- a/ts/features/bonus/bpd/saga/networking/loadPeriodsWithInfo.ts +++ /dev/null @@ -1,123 +0,0 @@ -import * as AR from "fp-ts/lib/Array"; -import * as E from "fp-ts/lib/Either"; -import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import { all, call, put } from "typed-redux-saga/macro"; -import { SagaCallReturnType } from "../../../../../types/utils"; -import { BackendBpdClient } from "../../api/backendBpdClient"; -import { bpdPeriodsAmountLoad } from "../../store/actions/periods"; -import { - BpdPeriodWithInfo, - BpdRanking, - bpdRankingNotReady, - BpdRankingReady -} from "../../store/reducers/details/periods"; -import { BpdAmount, BpdAmountError, bpdLoadAmountSaga } from "./amount"; -import { bpdLoadPeriodsSaga } from "./periods"; -import { bpdLoadRakingV2 } from "./ranking"; - -/** - * Load the periods information list and adds the amount and ranking information - * foreach period (active or closed) - * @param bpdClient - */ -export function* loadPeriodsWithInfo( - bpdClient: Pick< - ReturnType, - "awardPeriods" | "totalCashback" | "getRankingV2" - > -) { - // Request the period list - const maybePeriods: SagaCallReturnType = - yield* call(bpdLoadPeriodsSaga, bpdClient.awardPeriods); - - if (E.isLeft(maybePeriods)) { - // Error while receiving the period list - yield* put(bpdPeriodsAmountLoad.failure(maybePeriods.left)); - } else { - const periods = maybePeriods.right; - - const rankings: E.Either< - Error, - ReadonlyArray - > = yield* call(bpdLoadRakingV2, bpdClient.getRankingV2); - - if (E.isLeft(rankings)) { - yield* put( - bpdPeriodsAmountLoad.failure(new Error("Error while loading rankings")) - ); - return; - } - - // request the amounts for all the required periods - const amounts: ReadonlyArray> = - yield* all( - periods - // no need to request the inactive period, the amount and transaction number is always 0 - .filter(p => p.status !== "Inactive") - .map(period => - call( - bpdLoadAmountSaga, - bpdClient.totalCashback, - period.awardPeriodId - ) - ) - ); - - // Check if the required period amount are without error - // With a single error, we can't display the periods list - if (amounts.some(E.isLeft)) { - yield* put( - bpdPeriodsAmountLoad.failure(new Error("Error while loading amounts")) - ); - return; - } - - // the transactionNumber and totalCashback for inactive (future) period is 0, no need to request - // creating the static response - const amountWithInactivePeriod = [ - ...periods - .filter(p => p.status === "Inactive") - .map>(p => - E.right({ - awardPeriodId: p.awardPeriodId, - transactionNumber: 0, - totalCashback: 0 - }) - ), - ...amounts - ]; - - // compose the period with the amount information - const periodsWithAmount = amountWithInactivePeriod - .filter(E.isRight) - .reduce>( - (acc, curr) => - pipe( - [...periods], - AR.findFirst(p => p.awardPeriodId === curr.right.awardPeriodId), - O.foldW( - () => acc, - period => [ - ...acc, - { - ...period, - amount: curr.right, - ranking: pipe( - [...rankings.right], - AR.findFirst( - r => r.awardPeriodId === curr.right.awardPeriodId - ), - O.getOrElseW(() => - bpdRankingNotReady(curr.right.awardPeriodId) - ) - ) - } - ] - ) - ), - [] - ); - yield* put(bpdPeriodsAmountLoad.success(periodsWithAmount)); - } -} diff --git a/ts/features/bonus/bpd/saga/networking/patchCitizenIban.ts b/ts/features/bonus/bpd/saga/networking/patchCitizenIban.ts deleted file mode 100644 index cb6c6884d7f..00000000000 --- a/ts/features/bonus/bpd/saga/networking/patchCitizenIban.ts +++ /dev/null @@ -1,103 +0,0 @@ -import * as O from "fp-ts/lib/Option"; -import * as E from "fp-ts/lib/Either"; -import { call, put, select } from "typed-redux-saga/macro"; -import { ActionType } from "typesafe-actions"; -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { pipe } from "fp-ts/lib/function"; -import { BackendBpdClient } from "../../api/backendBpdClient"; -import { SagaCallReturnType } from "../../../../../types/utils"; -import { Iban } from "../../../../../../definitions/backend/Iban"; -import { bpdUpsertIban, IbanUpsertResult } from "../../store/actions/iban"; -import { profileSelector } from "../../../../../store/reducers/profile"; -import { readablePrivacyReport } from "../../../../../utils/reporters"; -import { convertUnknownToError } from "../../../../../utils/errors"; -// representation of iban status -export enum IbanStatus { - "OK" = "OK", - "NOT_OWNED" = "NOT_OWNED", - "CANT_VERIFY" = "CANT_VERIFY", - "NOT_VALID" = "NOT_VALID", - "UNKNOWN" = "UNKNOWN" -} - -const ibanStatusMapping: Map = new Map([ - ["OK", IbanStatus.OK], - ["KO", IbanStatus.NOT_OWNED], - ["UNKNOWN_PSP", IbanStatus.CANT_VERIFY] -]); - -const successCases = new Set([ - IbanStatus.OK, - IbanStatus.CANT_VERIFY, - IbanStatus.NOT_OWNED -]); - -// convert the validation code coming from response to an internal logic status -const fromValidationToStatus = (ibanOutcome: string): IbanStatus => - pipe( - ibanStatusMapping.get(ibanOutcome), - O.fromNullable, - O.getOrElseW(() => IbanStatus.UNKNOWN) - ); - -const transformIbanOutCome = ( - ibanStatus: IbanStatus, - iban: Iban -): IbanUpsertResult => ({ - // don't store iban if the outcome is a failure - payoffInstr: successCases.has(ibanStatus) ? iban : undefined, - status: ibanStatus -}); - -/** - * upsert the citizen iban - */ -export function* patchCitizenIban( - updatePaymentMethod: ReturnType< - typeof BackendBpdClient - >["updatePaymentMethod"], - action: ActionType -) { - try { - const profileState: ReturnType = yield* select( - profileSelector - ); - if (pot.isNone(profileState)) { - // it should never happen - throw new Error(`profile is None`); - } - const iban: Iban = action.payload; - const updatePaymentMethodResult: SagaCallReturnType< - ReturnType - > = yield* call(updatePaymentMethod(iban, profileState.value), {}); - if (E.isRight(updatePaymentMethodResult)) { - const statusCode = updatePaymentMethodResult.right.status; - if (statusCode === 200 && updatePaymentMethodResult.right.value) { - const validationStatus = - updatePaymentMethodResult.right.value.validationStatus; - yield* put( - bpdUpsertIban.success( - transformIbanOutCome(fromValidationToStatus(validationStatus), iban) - ) - ); - return; - } - // iban not valid - else if (statusCode === 400) { - yield* put( - bpdUpsertIban.success( - transformIbanOutCome(IbanStatus.NOT_VALID, iban) - ) - ); - return; - } - throw new Error( - `response status ${updatePaymentMethodResult.right.status}` - ); - } else { - throw new Error(readablePrivacyReport(updatePaymentMethodResult.left)); - } - } catch (e) { - yield* put(bpdUpsertIban.failure(convertUnknownToError(e))); - } -} diff --git a/ts/features/bonus/bpd/saga/networking/paymentMethod.ts b/ts/features/bonus/bpd/saga/networking/paymentMethod.ts deleted file mode 100644 index 36c0be12c73..00000000000 --- a/ts/features/bonus/bpd/saga/networking/paymentMethod.ts +++ /dev/null @@ -1,194 +0,0 @@ -import { call, put } from "typed-redux-saga/macro"; -import { ActionType } from "typesafe-actions"; -import { readableReport } from "@pagopa/ts-commons/lib/reporters"; -import * as O from "fp-ts/lib/Option"; -import * as E from "fp-ts/lib/Either"; -import { pipe } from "fp-ts/lib/function"; -import { - BpdPaymentMethodActivation, - bpdPaymentMethodActivation, - BpdPmActivationStatus, - bpdUpdatePaymentMethodActivation, - HPan -} from "../../store/actions/paymentMethods"; -import { SagaCallReturnType } from "../../../../../types/utils"; -import { BackendBpdClient } from "../../api/backendBpdClient"; -import { getError } from "../../../../../utils/errors"; -import { - PaymentInstrumentResource, - StatusEnum -} from "../../../../../../definitions/bpd/payment/PaymentInstrumentResource"; -import { getPaymentStatus } from "../../store/reducers/details/paymentMethods"; - -const mapStatus: Map = new Map< - StatusEnum, - BpdPmActivationStatus ->([ - [StatusEnum.ACTIVE, "active"], - [StatusEnum.INACTIVE, "inactive"] -]); - -// convert the network payload to the logical app representation of it -const convertNetworkPayload = ( - networkPayload: PaymentInstrumentResource -): BpdPaymentMethodActivation => ({ - hPan: networkPayload.hpan as HPan, - activationStatus: pipe( - mapStatus.get(networkPayload.Status), - O.fromNullable, - O.getOrElseW(() => "notActivable" as const) - ), - activationDate: networkPayload.activationDate, - deactivationDate: networkPayload.deactivationDate -}); - -/** - * return {@link BpdPaymentMethodActivation} when network response is conflict (409) - */ -const whenConflict = (hPan: HPan): BpdPaymentMethodActivation => ({ - hPan, - activationStatus: "notActivable" -}); - -/** - * Request the actual state of bpd on a payment method - */ -export function* bpdLoadPaymentMethodActivationSaga( - findPaymentMethod: ReturnType["findPayment"], - action: ActionType -) { - try { - const findPaymentMethodResult: SagaCallReturnType< - typeof findPaymentMethod - > = yield* call(findPaymentMethod, { id: action.payload } as any); - if (E.isRight(findPaymentMethodResult)) { - if (findPaymentMethodResult.right.status === 200) { - yield* put( - bpdPaymentMethodActivation.success( - convertNetworkPayload(findPaymentMethodResult.right.value) - ) - ); - return; - } else if (findPaymentMethodResult.right.status === 409) { - // conflict means not activable - yield* put( - bpdPaymentMethodActivation.success(whenConflict(action.payload)) - ); - return; - } else if (findPaymentMethodResult.right.status === 404) { - yield* put( - bpdPaymentMethodActivation.success({ - hPan: action.payload, - activationStatus: "inactive" - }) - ); - return; - } - throw new Error( - `response status ${findPaymentMethodResult.right.status}` - ); - } else { - throw new Error(readableReport(findPaymentMethodResult.left)); - } - } catch (e) { - yield* put( - bpdPaymentMethodActivation.failure({ - hPan: action.payload, - error: getError(e) - }) - ); - } -} - -/** - * Change the activation state on a payment method - * action.payload.value: if true it enrolls the method otherwise it deletes it - */ -export function* bpdUpdatePaymentMethodActivationSaga( - enrollPayment: ReturnType["enrollPayment"], - deletePayment: ReturnType["deletePayment"], - action: ActionType -) { - if (action.payload.value) { - yield* call(enrollPaymentMethod, enrollPayment, action); - } else { - yield* call(deletePaymentMethod, deletePayment, action); - } -} - -function* enrollPaymentMethod( - enrollPaymentMethod: ReturnType["enrollPayment"], - action: ActionType -) { - try { - const enrollPaymentMethodResult: SagaCallReturnType< - typeof enrollPaymentMethod - > = yield* call(enrollPaymentMethod, { id: action.payload.hPan } as any); - if (E.isRight(enrollPaymentMethodResult)) { - if (enrollPaymentMethodResult.right.status === 200) { - const responsePayload = enrollPaymentMethodResult.right.value; - yield* put( - bpdUpdatePaymentMethodActivation.success({ - hPan: action.payload.hPan, - activationDate: responsePayload.activationDate, - activationStatus: getPaymentStatus(action.payload.value) - }) - ); - return; - } else if (enrollPaymentMethodResult.right.status === 409) { - yield* put( - bpdUpdatePaymentMethodActivation.success( - whenConflict(action.payload.hPan) - ) - ); - return; - } - throw new Error( - `response status ${enrollPaymentMethodResult.right.status}` - ); - } else { - throw new Error(readableReport(enrollPaymentMethodResult.left)); - } - } catch (e) { - yield* put( - bpdUpdatePaymentMethodActivation.failure({ - hPan: action.payload.hPan, - error: getError(e) - }) - ); - } -} - -function* deletePaymentMethod( - deletePaymentMethod: ReturnType["deletePayment"], - action: ActionType -) { - try { - const deletePaymentMethodResult: SagaCallReturnType< - typeof deletePaymentMethod - > = yield* call(deletePaymentMethod, { id: action.payload.hPan } as any); - if (E.isRight(deletePaymentMethodResult)) { - if (deletePaymentMethodResult.right.status === 204) { - yield* put( - bpdUpdatePaymentMethodActivation.success({ - hPan: action.payload.hPan, - activationStatus: getPaymentStatus(action.payload.value) - }) - ); - return; - } - throw new Error( - `response status ${deletePaymentMethodResult.right.status}` - ); - } else { - throw new Error(readableReport(deletePaymentMethodResult.left)); - } - } catch (e) { - yield* put( - bpdUpdatePaymentMethodActivation.failure({ - hPan: action.payload.hPan, - error: getError(e) - }) - ); - } -} diff --git a/ts/features/bonus/bpd/saga/networking/periods.ts b/ts/features/bonus/bpd/saga/networking/periods.ts deleted file mode 100644 index 8ec66a13d36..00000000000 --- a/ts/features/bonus/bpd/saga/networking/periods.ts +++ /dev/null @@ -1,86 +0,0 @@ -import * as E from "fp-ts/lib/Either"; -import * as O from "fp-ts/lib/Option"; -import { readableReport } from "@pagopa/ts-commons/lib/reporters"; -import { call } from "typed-redux-saga/macro"; -import { pipe } from "fp-ts/lib/function"; -import { AwardPeriodResource } from "../../../../../../definitions/bpd/award_periods/AwardPeriodResource"; -import { mixpanelTrack } from "../../../../../mixpanel"; -import { - ReduxSagaEffect, - SagaCallReturnType -} from "../../../../../types/utils"; -import { convertUnknownToError, getError } from "../../../../../utils/errors"; -import { BackendBpdClient } from "../../api/backendBpdClient"; -import { - AwardPeriodId, - BpdPeriod, - BpdPeriodStatus -} from "../../store/actions/periods"; - -// mapping between network payload status and app domain model status -const periodStatusMap: Map = new Map< - string, - BpdPeriodStatus ->([ - ["ACTIVE", "Active"], - ["INACTIVE", "Inactive"], - ["CLOSED", "Closed"] -]); - -// convert a network payload award period into the relative app domain model -const convertPeriod = ( - networkPeriod: AwardPeriodResource, - statusFallback: BpdPeriodStatus = "Inactive" -): BpdPeriod => ({ - ...networkPeriod, - awardPeriodId: networkPeriod.awardPeriodId as AwardPeriodId, - superCashbackAmount: networkPeriod.maxAmount, - status: pipe( - periodStatusMap.get(networkPeriod.status), - O.fromNullable, - O.getOrElse(() => statusFallback) - ) -}); - -const mixpanelActionRequest = "BPD_PERIODS_REQUEST"; -const mixpanelActionSuccess = "BPD_PERIODS_SUCCESS"; -const mixpanelActionFailure = "BPD_PERIODS_FAILURE"; - -/** - * Networking logic to request the periods list - * @param awardPeriods - */ -export function* bpdLoadPeriodsSaga( - awardPeriods: ReturnType["awardPeriods"] -): Generator< - ReduxSagaEffect, - E.Either>, - SagaCallReturnType -> { - void mixpanelTrack(mixpanelActionRequest); - try { - const awardPeriodsResult: SagaCallReturnType = - yield* call(awardPeriods, {} as any); - if (E.isRight(awardPeriodsResult)) { - if (awardPeriodsResult.right.status === 200) { - const periods = awardPeriodsResult.right.value; - void mixpanelTrack(mixpanelActionSuccess, { - count: periods.length - }); - // convert data into app domain model - return E.right>( - periods.map(p => convertPeriod(p)) - ); - } else { - throw new Error(`response status ${awardPeriodsResult.right.status}`); - } - } else { - throw new Error(readableReport(awardPeriodsResult.left)); - } - } catch (e) { - void mixpanelTrack(mixpanelActionFailure, { - reason: convertUnknownToError(e).message - }); - return E.left>(getError(e)); - } -} diff --git a/ts/features/bonus/bpd/saga/networking/ranking.ts b/ts/features/bonus/bpd/saga/networking/ranking.ts deleted file mode 100644 index 372deb4e8a1..00000000000 --- a/ts/features/bonus/bpd/saga/networking/ranking.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { readableReport } from "@pagopa/ts-commons/lib/reporters"; -import * as E from "fp-ts/lib/Either"; -import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import * as RA from "fp-ts/lib/ReadonlyArray"; -import { call, put } from "typed-redux-saga/macro"; -import { ActionType } from "typesafe-actions"; -import { CitizenRankingMilestoneResourceArray } from "../../../../../../definitions/bpd/citizen_v2/CitizenRankingMilestoneResourceArray"; -import { MilestoneResource } from "../../../../../../definitions/bpd/citizen_v2/MilestoneResource"; -import { mixpanelTrack } from "../../../../../mixpanel"; -import { - ReduxSagaEffect, - SagaCallReturnType -} from "../../../../../types/utils"; -import { getError } from "../../../../../utils/errors"; -import { BackendBpdClient } from "../../api/backendBpdClient"; -import { AwardPeriodId } from "../../store/actions/periods"; -import { - BpdTransactionId, - bpdTransactionsLoadMilestone -} from "../../store/actions/transactions"; -import { BpdRankingReady } from "../../store/reducers/details/periods"; -import { BpdPivotTransaction } from "../../store/reducers/details/transactionsv2/entities"; - -const version = "_V2"; - -const mixpanelActionRequest = `BPD_RANKING${version}_REQUEST`; -const mixpanelActionSuccess = `BPD_RANKING${version}_SUCCESS`; -const mixpanelActionFailure = `BPD_RANKING${version}_FAILURE`; - -// convert the network payload ranking into the relative app domain model -const convertRankingArrayV2 = ( - rankings: CitizenRankingMilestoneResourceArray -): ReadonlyArray => - rankings.map(rr => ({ - ...rr, - awardPeriodId: rr.awardPeriodId as AwardPeriodId, - pivot: extractPivot(rr.milestoneResource), - kind: "ready" - })); - -const extractPivot = ( - mr: MilestoneResource | undefined -): BpdPivotTransaction | undefined => { - if (mr?.idTrxPivot !== undefined && mr?.cashbackNorm !== undefined) { - return { - amount: mr.cashbackNorm, - idTrx: mr.idTrxPivot as BpdTransactionId - }; - } - return undefined; -}; - -/** - * Load the ranking for all the periods - * @param getRanking - */ -export function* bpdLoadRakingV2( - getRanking: ReturnType["getRankingV2"] -): Generator< - ReduxSagaEffect, - E.Either>, - any -> { - try { - void mixpanelTrack(mixpanelActionRequest); - const getRankingResult: SagaCallReturnType = yield* call( - getRanking, - {} as any - ); - if (E.isRight(getRankingResult)) { - if (getRankingResult.right.status === 200) { - void mixpanelTrack(mixpanelActionSuccess, { - count: getRankingResult.right.value?.length - }); - return E.right>( - convertRankingArrayV2(getRankingResult.right.value) - ); - } else if (getRankingResult.right.status === 404) { - void mixpanelTrack(mixpanelActionSuccess, { - count: 0 - }); - return E.right>([]); - } else { - return E.left>( - new Error(`response status ${getRankingResult.right.status}`) - ); - } - } else { - return E.left>( - new Error(readableReport(getRankingResult.left)) - ); - } - } catch (e) { - void mixpanelTrack(mixpanelActionFailure, { - reason: getError(e).message - }); - return E.left>(getError(e)); - } -} - -/** - * Handle the action bpdTransactionsLoadMilestone.request - * @param getRanking - * @param action - */ -export function* handleLoadMilestone( - getRanking: ReturnType["getRankingV2"], - action: ActionType -) { - // get the results - const result: SagaCallReturnType = yield* call( - bpdLoadRakingV2, - getRanking - ); - - const extractMilestonePivot = pipe( - result, - E.chain(x => - E.fromOption( - () => - new Error( - `Period ${action.payload} not found in the ranking response` - ) - )( - pipe( - x, - RA.findFirst(el => el.awardPeriodId === action.payload), - O.map(el => el.pivot) - ) - ) - ) - ); - - // dispatch the related action - if (E.isRight(extractMilestonePivot)) { - yield* put( - bpdTransactionsLoadMilestone.success({ - awardPeriodId: action.payload, - result: extractMilestonePivot.right - }) - ); - } else { - yield* put( - bpdTransactionsLoadMilestone.failure({ - awardPeriodId: action.payload, - error: extractMilestonePivot.left - }) - ); - } -} diff --git a/ts/features/bonus/bpd/saga/networking/transactions.ts b/ts/features/bonus/bpd/saga/networking/transactions.ts deleted file mode 100644 index 540d4f64fe2..00000000000 --- a/ts/features/bonus/bpd/saga/networking/transactions.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import { CircuitType } from "../../store/actions/transactions"; - -/** - * convert a network circuit into the relative app domain model - * TODO we don't know which values circuit type could have - * see https://docs.google.com/document/d/1LNbuPEeqyyt9fsJ8yAOOLQG1xLERO6nOdtlnG95cj74/edit#bookmark=id.nzho5vra0820 - */ -const mapNetworkCircuitType: Map = new Map< - string, - CircuitType ->([ - ["00", "PagoBancomat"], - ["01", "Visa"], - ["02", "Mastercard / Maestro"], - ["03", "Amex"], - ["04", "JCB"], - ["05", "UnionPay"], - ["06", "Diners"], - ["07", "PostePay"], - ["08", "BancomatPay"], - ["10", "Private"] -]); - -export const convertCircuitTypeCode = (code: string): CircuitType => - pipe( - mapNetworkCircuitType.get(code), - O.fromNullable, - O.getOrElse(() => "Unknown" as CircuitType) - ); diff --git a/ts/features/bonus/bpd/saga/networking/winning-transactions/countByDay.ts b/ts/features/bonus/bpd/saga/networking/winning-transactions/countByDay.ts deleted file mode 100644 index 7927f59ab57..00000000000 --- a/ts/features/bonus/bpd/saga/networking/winning-transactions/countByDay.ts +++ /dev/null @@ -1,100 +0,0 @@ -import * as E from "fp-ts/lib/Either"; -import { readableReport } from "@pagopa/ts-commons/lib/reporters"; -import { call, put } from "typed-redux-saga/macro"; -import { ActionType } from "typesafe-actions"; -import { mixpanelTrack } from "../../../../../../mixpanel"; -import { - ReduxSagaEffect, - SagaCallReturnType -} from "../../../../../../types/utils"; -import { getError } from "../../../../../../utils/errors"; -import { BackendBpdClient } from "../../../api/backendBpdClient"; -import { AwardPeriodId } from "../../../store/actions/periods"; -import { - bpdTransactionsLoadCountByDay, - TrxCountByDayResource -} from "../../../store/actions/transactions"; - -const mixpanelActionRequest = `BPD_COUNT_BY_DAY_REQUEST`; -const mixpanelActionSuccess = `BPD_COUNT_BY_DAY_SUCCESS`; -const mixpanelActionFailure = `BPD_COUNT_BY_DAY_FAILURE`; - -/** - * Load the countByDay for a period - * @param getCountByDay - * @param awardPeriodId - */ -export function* bpdLoadCountByDay( - getCountByDay: ReturnType< - typeof BackendBpdClient - >["winningTransactionsV2CountByDay"], - awardPeriodId: AwardPeriodId -): Generator< - ReduxSagaEffect, - E.Either, - SagaCallReturnType -> { - try { - void mixpanelTrack(mixpanelActionRequest, { awardPeriodId }); - const getCountByDayResults = yield* call(getCountByDay, { - awardPeriodId - } as any); - if (E.isRight(getCountByDayResults)) { - if (getCountByDayResults.right.status === 200) { - void mixpanelTrack(mixpanelActionSuccess, { - awardPeriodId, - count: getCountByDayResults.right.value?.length - }); - return E.right({ - awardPeriodId, - results: getCountByDayResults.right.value - }); - } else { - return E.left( - new Error(`response status ${getCountByDayResults.right.status}`) - ); - } - } else { - return E.left( - new Error(readableReport(getCountByDayResults.left)) - ); - } - } catch (e) { - void mixpanelTrack(mixpanelActionFailure, { - awardPeriodId, - reason: getError(e).message - }); - return E.left(getError(e)); - } -} - -/** - * handle the action bpdTransactionsLoadCountByDay.request - * @param getCountByDay - * @param action - */ -export function* handleCountByDay( - getCountByDay: ReturnType< - typeof BackendBpdClient - >["winningTransactionsV2CountByDay"], - action: ActionType -) { - // get the results - const result: SagaCallReturnType = yield* call( - bpdLoadCountByDay, - getCountByDay, - action.payload - ); - - // dispatch the related action - if (E.isRight(result)) { - yield* put(bpdTransactionsLoadCountByDay.success(result.right)); - } else { - yield* put( - bpdTransactionsLoadCountByDay.failure({ - awardPeriodId: action.payload, - error: result.left - }) - ); - } -} diff --git a/ts/features/bonus/bpd/saga/networking/winning-transactions/loadTransactionsRequiredData.ts b/ts/features/bonus/bpd/saga/networking/winning-transactions/loadTransactionsRequiredData.ts deleted file mode 100644 index 25f370d8e46..00000000000 --- a/ts/features/bonus/bpd/saga/networking/winning-transactions/loadTransactionsRequiredData.ts +++ /dev/null @@ -1,109 +0,0 @@ -import * as E from "fp-ts/lib/Either"; -import { call, put, take } from "typed-redux-saga/macro"; -import { ActionType, getType } from "typesafe-actions"; -import { - ReduxSagaEffect, - SagaCallReturnType -} from "../../../../../../types/utils"; -import { waitBackoffError } from "../../../../../../utils/backoffError"; -import { AwardPeriodId } from "../../../store/actions/periods"; -import { - BpdTransactionsError, - bpdTransactionsLoadCountByDay, - bpdTransactionsLoadMilestone, - bpdTransactionsLoadPage, - bpdTransactionsLoadRequiredData -} from "../../../store/actions/transactions"; - -/** - * Load all the information required to render the transactions list : - * - Milestone Pivot - * - CountByDay - * - First transaction page - * These are the required information in order to do the first render for the transaction page - */ -export function* loadTransactionsRequiredData( - periodId: AwardPeriodId -): Generator, any> { - // We check if there is a failure on the whole loadTransactionsRequiredData block - yield* call(waitBackoffError, bpdTransactionsLoadRequiredData.failure); - - // First request the Milestone Pivot - yield* put(bpdTransactionsLoadMilestone.request(periodId)); - - const milestoneResponse = yield* take< - ActionType< - | typeof bpdTransactionsLoadMilestone.success - | typeof bpdTransactionsLoadMilestone.failure - > - >([ - bpdTransactionsLoadMilestone.success, - bpdTransactionsLoadMilestone.failure - ]); - - if ( - milestoneResponse.type === getType(bpdTransactionsLoadMilestone.failure) - ) { - return E.left({ - awardPeriodId: periodId, - error: new Error("Failed to load bpd transactions milestone") - }); - } - - // Request CountByDay - yield* put(bpdTransactionsLoadCountByDay.request(periodId)); - - const countByDayResponse = yield* take< - ActionType< - | typeof bpdTransactionsLoadCountByDay.success - | typeof bpdTransactionsLoadCountByDay.failure - > - >([ - bpdTransactionsLoadCountByDay.success, - bpdTransactionsLoadCountByDay.failure - ]); - - if ( - countByDayResponse.type === getType(bpdTransactionsLoadCountByDay.failure) - ) { - return E.left({ - awardPeriodId: periodId, - error: new Error("Failed to load bpd transactions countByDay") - }); - } - - // Request first transaction page for the period - yield* put(bpdTransactionsLoadPage.request({ awardPeriodId: periodId })); - - const firstPageResponse = yield* take< - ActionType< - | typeof bpdTransactionsLoadPage.success - | typeof bpdTransactionsLoadPage.failure - > - >([bpdTransactionsLoadPage.success, bpdTransactionsLoadPage.failure]); - if (firstPageResponse.type === getType(bpdTransactionsLoadPage.failure)) { - return E.left({ - awardPeriodId: periodId, - error: new Error("Failed to load the first transactions page") - }); - } - return E.right(true); -} - -/** - * Handle the trigger action bpdTransactionsLoadRequiredData.request - * @param action - */ -export function* handleTransactionsLoadRequiredData( - action: ActionType -) { - // get the results - const result: SagaCallReturnType = - yield* call(loadTransactionsRequiredData, action.payload); - - if (E.isRight(result)) { - yield* put(bpdTransactionsLoadRequiredData.success(action.payload)); - } else { - yield* put(bpdTransactionsLoadRequiredData.failure(result.left)); - } -} diff --git a/ts/features/bonus/bpd/saga/networking/winning-transactions/transactionsPage.ts b/ts/features/bonus/bpd/saga/networking/winning-transactions/transactionsPage.ts deleted file mode 100644 index 54cbcdfa6d1..00000000000 --- a/ts/features/bonus/bpd/saga/networking/winning-transactions/transactionsPage.ts +++ /dev/null @@ -1,111 +0,0 @@ -import * as E from "fp-ts/lib/Either"; -import { readableReport } from "@pagopa/ts-commons/lib/reporters"; -import { call, put } from "typed-redux-saga/macro"; -import { ActionType } from "typesafe-actions"; -import { mixpanelTrack } from "../../../../../../mixpanel"; -import { - ReduxSagaEffect, - SagaCallReturnType -} from "../../../../../../types/utils"; -import { waitBackoffError } from "../../../../../../utils/backoffError"; -import { getError } from "../../../../../../utils/errors"; -import { BackendBpdClient } from "../../../api/backendBpdClient"; -import { AwardPeriodId } from "../../../store/actions/periods"; -import { - BpdTransactionPageSuccessPayload, - bpdTransactionsLoadPage -} from "../../../store/actions/transactions"; - -const mixpanelActionRequest = `BPD_TRANSACTIONS_PAGE_REQUEST`; -const mixpanelActionSuccess = `BPD_TRANSACTIONS_PAGE_SUCCESS`; -const mixpanelActionFailure = `BPD_TRANSACTIONS_PAGE_FAILURE`; - -/** - * Load a page of transactions for a period - * @param getTransactionPage - * @param awardPeriodId - * @param cursor - */ -export function* bpdLoadTransactionsPage( - getTransactionPage: ReturnType< - typeof BackendBpdClient - >["winningTransactionsV2"], - awardPeriodId: AwardPeriodId, - cursor?: number -): Generator< - ReduxSagaEffect, - E.Either, - SagaCallReturnType -> { - try { - void mixpanelTrack(mixpanelActionRequest, { awardPeriodId, cursor }); - const getTransactionsPageResults = yield* call(getTransactionPage, { - awardPeriodId, - nextCursor: cursor - } as any); - if (E.isRight(getTransactionsPageResults)) { - if (getTransactionsPageResults.right.status === 200) { - void mixpanelTrack(mixpanelActionSuccess, { - awardPeriodId, - cursor, - count: getTransactionsPageResults.right.value?.transactions.length - }); - return E.right({ - awardPeriodId, - results: getTransactionsPageResults.right.value - }); - } else { - return E.left( - new Error( - `response status ${getTransactionsPageResults.right.status}` - ) - ); - } - } else { - return E.left( - new Error(readableReport(getTransactionsPageResults.left)) - ); - } - } catch (e) { - void mixpanelTrack(mixpanelActionFailure, { - awardPeriodId, - cursor, - reason: getError(e).message - }); - return E.left(getError(e)); - } -} - -/** - * handle the action bpdTransactionsLoadCountByDay.request - * @param getTransactionsPage - * @param action - */ -export function* handleTransactionsPage( - getTransactionsPage: ReturnType< - typeof BackendBpdClient - >["winningTransactionsV2"], - action: ActionType -) { - yield* call(waitBackoffError, bpdTransactionsLoadPage.failure); - // get the results - const result: SagaCallReturnType = - yield* call( - bpdLoadTransactionsPage, - getTransactionsPage, - action.payload.awardPeriodId, - action.payload.nextCursor - ); - - // dispatch the related action - if (E.isRight(result)) { - yield* put(bpdTransactionsLoadPage.success(result.right)); - } else { - yield* put( - bpdTransactionsLoadPage.failure({ - awardPeriodId: action.payload.awardPeriodId, - error: result.left - }) - ); - } -} diff --git a/ts/features/bonus/bpd/saga/orchestration/activateBpdOnNewAddedPaymentMethods.ts b/ts/features/bonus/bpd/saga/orchestration/activateBpdOnNewAddedPaymentMethods.ts deleted file mode 100644 index 8c2b6aa8aba..00000000000 --- a/ts/features/bonus/bpd/saga/orchestration/activateBpdOnNewAddedPaymentMethods.ts +++ /dev/null @@ -1,37 +0,0 @@ -import * as E from "fp-ts/lib/Either"; -import { call } from "typed-redux-saga/macro"; -import { EnableableFunctionsEnum } from "../../../../../../definitions/pagopa/EnableableFunctions"; -import { navigateToWalletHome } from "../../../../../store/actions/navigation"; -import { PaymentMethod } from "../../../../../types/pagopa"; -import { SagaCallReturnType } from "../../../../../types/utils"; -import { hasFunctionEnabled } from "../../../../../utils/walletv2"; -import { isBpdEnabled } from "./onboarding/startOnboarding"; - -/** - * Allows the user to activate bpd on a set of new added payment methods - */ -export function* activateBpdOnNewPaymentMethods( - paymentMethods: ReadonlyArray, - navigateToActivateNewMethods: () => void -) { - const atLeastOnePaymentMethodWithBpdCapability = paymentMethods.some(b => - hasFunctionEnabled(b, EnableableFunctionsEnum.BPD) - ); - - // No payment method with bpd capability added in the current workflow, return to wallet home - if (!atLeastOnePaymentMethodWithBpdCapability) { - return yield* call(navigateToWalletHome); - } - const isBpdEnabledResponse: SagaCallReturnType = - yield* call(isBpdEnabled); - - // Error while reading the bpdEnabled, return to wallet - if (E.isLeft(isBpdEnabledResponse)) { - yield* call(navigateToWalletHome); - } else { - if (isBpdEnabledResponse.right) { - // navigate to activate cashback on new payment methods if the user is onboarded to the program and is active - yield* call(navigateToActivateNewMethods); - } - } -} diff --git a/ts/features/bonus/bpd/saga/orchestration/insertIban.ts b/ts/features/bonus/bpd/saga/orchestration/insertIban.ts deleted file mode 100644 index e5e723e1bdf..00000000000 --- a/ts/features/bonus/bpd/saga/orchestration/insertIban.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { SagaIterator } from "redux-saga"; -import { call, put, select, take } from "typed-redux-saga/macro"; -import { ActionType, isActionOf } from "typesafe-actions"; -import { - navigateBack, - navigateToWalletHome -} from "../../../../../store/actions/navigation"; -import { paymentMethodsSelector } from "../../../../../store/reducers/wallet/wallets"; -import { - bpdIbanInsertionCancel, - bpdIbanInsertionContinue -} from "../../store/actions/iban"; -import { bpdOnboardingCompleted } from "../../store/actions/onboarding"; -import { isBpdOnboardingOngoing } from "../../store/reducers/onboarding/ongoing"; - -// TODO: if isOnboarding===true, change with an action that triggers a saga that choose -// which screen to display, (the user already have payment methods or not) - -/** - * Old style orchestrator, please don't use this as reference for future development - * @deprecated - */ -export function* bpdIbanInsertionWorker() { - const onboardingOngoing: ReturnType = - yield* select(isBpdOnboardingOngoing); - // ensure the first screen of the saga is the iban main screen. - - // wait for the user iban insertion o cancellation - const nextAction = yield* take< - ActionType - >([bpdIbanInsertionCancel, bpdIbanInsertionContinue]); - if (isActionOf(bpdIbanInsertionCancel, nextAction)) { - yield* call(onboardingOngoing ? navigateToWalletHome : navigateBack); - } else { - if (onboardingOngoing) { - const paymentMethods: ReturnType = - yield* select(paymentMethodsSelector); - - // Error while loading the wallet, display a message that informs the user about the error - if (paymentMethods.kind === "PotNoneError") { - yield* put(bpdOnboardingCompleted()); - return; - } - - yield* put(bpdOnboardingCompleted()); - } else { - yield* call(navigateBack); - } - } -} - -/** - * This saga start the workflow that allows the user to insert / modify the IBAN associated to bpd. - * In this first phase subject to changes, the call to the bpdIbanInsertionWorker is preserved, - * instead of removing the call. - */ -export function* handleBpdIbanInsertion(): SagaIterator { - yield* call(bpdIbanInsertionWorker); -} diff --git a/ts/features/bonus/bpd/saga/orchestration/onboarding/enrollToBpd.ts b/ts/features/bonus/bpd/saga/orchestration/onboarding/enrollToBpd.ts deleted file mode 100644 index cd34de7c194..00000000000 --- a/ts/features/bonus/bpd/saga/orchestration/onboarding/enrollToBpd.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { CommonActions } from "@react-navigation/native"; -import { call, put, race, take } from "typed-redux-saga/macro"; -import { ActionType } from "typesafe-actions"; -import NavigationService from "../../../../../../navigation/NavigationService"; -import { bpdAllData } from "../../../store/actions/details"; -import { bpdIbanInsertionStart } from "../../../store/actions/iban"; -import { - bpdEnrollUserToProgram, - bpdOnboardingCancel -} from "../../../store/actions/onboarding"; - -export const isLoadingScreen = () => true; - -/** - * Old style orchestrator, please don't use this as reference for future development - * @deprecated - */ -function* enrollToBpdWorker() { - const currentRoute: ReturnType = - yield* call(NavigationService.getCurrentRouteName); - - if (currentRoute !== undefined && !isLoadingScreen()) { - // show the loading page while communicate with the server for the activation - throw new Error("Not in the loading screen"); - } - - // enroll the user and wait for the result - yield* put(bpdEnrollUserToProgram.request()); - - const enrollResult: ActionType = - yield* take(bpdEnrollUserToProgram.success); - - if (enrollResult.payload.enabled) { - yield* put(bpdAllData.request()); - yield* put(bpdIbanInsertionStart()); - } - // TODO: handle false case to avoid making the user remain blocked in case of malfunction -} - -/** - * This saga enroll the user to the bpd - */ -export function* handleBpdEnroll() { - const { cancelAction } = yield* race({ - enroll: call(enrollToBpdWorker), - cancelAction: take(bpdOnboardingCancel) - }); - - if (cancelAction) { - yield* call( - NavigationService.dispatchNavigationAction, - CommonActions.goBack() - ); - } -} diff --git a/ts/features/bonus/bpd/saga/orchestration/onboarding/startOnboarding.ts b/ts/features/bonus/bpd/saga/orchestration/onboarding/startOnboarding.ts deleted file mode 100644 index 78012d3cbb6..00000000000 --- a/ts/features/bonus/bpd/saga/orchestration/onboarding/startOnboarding.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { StackActions } from "@react-navigation/native"; -import * as E from "fp-ts/lib/Either"; -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { call, put, select, take, race } from "typed-redux-saga/macro"; -import { ActionType } from "typesafe-actions"; -import { pipe } from "fp-ts/lib/function"; -import NavigationService from "../../../../../../navigation/NavigationService"; -import { navigateBack } from "../../../../../../store/actions/navigation"; -import { fetchWalletsRequest } from "../../../../../../store/actions/wallet/wallets"; -import { - ReduxSagaEffect, - SagaCallReturnType -} from "../../../../../../types/utils"; -import { getAsyncResult } from "../../../../../../utils/saga"; -import { bpdLoadActivationStatus } from "../../../store/actions/details"; -import { - bpdOnboardingAcceptDeclaration, - bpdOnboardingCancel, - bpdUserActivate -} from "../../../store/actions/onboarding"; -import { bpdEnabledSelector } from "../../../store/reducers/details/activation"; - -export const isLoadingScreen = () => true; - -export function* getActivationStatus() { - return yield* call(() => getAsyncResult(bpdLoadActivationStatus, undefined)); -} - -export function* isBpdEnabled(): Generator< - ReduxSagaEffect, - E.Either, - any -> { - const remoteActive: ReturnType = yield* select( - bpdEnabledSelector - ); - if (pot.isSome(remoteActive)) { - return E.right(remoteActive.value); - } else { - const activationStatus = yield* call(getActivationStatus); - return pipe( - activationStatus, - E.map(citizen => citizen.enabled) - ); - } -} - -/** - * Old style orchestrator, please don't use this as reference for future development - * @deprecated - */ -export function* bpdStartOnboardingWorker() { - const currentRoute: ReturnType = - yield* call(NavigationService.getCurrentRouteName); - - // go to the loading page (if I'm not on that screen) - if (currentRoute !== undefined && !isLoadingScreen()) { - throw new Error("Not in the loading screen"); - } - - // read if the bpd is active for the user - const isBpdActive: SagaCallReturnType = yield* call( - isBpdEnabled - ); - - if (E.isRight(isBpdActive)) { - // Refresh the wallets to prevent that added cards are not visible - yield* put(fetchWalletsRequest()); - - // wait for the user that choose to continue - yield* take(bpdUserActivate); - - // Navigate to the Onboarding Declaration and wait for the action that complete the saga - } - - // The saga ends when the user accepts the declaration - yield* take(bpdOnboardingAcceptDeclaration); -} - -/** - * This saga check if the bpd is active for the user and choose if start the onboarding or go directly to the bpd details - */ -export function* handleBpdStartOnboardingSaga() { - const { cancelAction } = yield* race({ - onboarding: call(bpdStartOnboardingWorker), - cancelAction: - take>(bpdOnboardingCancel) - }); - - if (cancelAction) { - yield* call( - NavigationService.dispatchNavigationAction, - StackActions.popToTop() - ); - yield* call(navigateBack); - } -} diff --git a/ts/features/bonus/bpd/saga/orchestration/optInPaymentMethods/__tests__/optInDeletionChoiceHandler.test.ts b/ts/features/bonus/bpd/saga/orchestration/optInPaymentMethods/__tests__/optInDeletionChoiceHandler.test.ts deleted file mode 100644 index 2cb01d01737..00000000000 --- a/ts/features/bonus/bpd/saga/orchestration/optInPaymentMethods/__tests__/optInDeletionChoiceHandler.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { testSaga } from "redux-saga-test-plan"; -import { optInDeletionChoiceHandler } from "../optInDeletionChoiceHandler"; -import { - DeleteAllByFunctionSuccess, - deleteAllPaymentMethodsByFunction -} from "../../../../../../../store/actions/wallet/delete"; -import { EnableableFunctionsEnum } from "../../../../../../../../definitions/pagopa/EnableableFunctions"; -import { bpdUpdateOptInStatusMethod } from "../../../../store/actions/onboarding"; -import { CitizenOptInStatusEnum } from "../../../../../../../../definitions/bpd/citizen_v2/CitizenOptInStatus"; - -describe("optInDeletionChoiceHandler saga", () => { - jest.useFakeTimers(); - - it("If deleteAllPaymentMethodsByFunction fails, should return", () => { - testSaga(optInDeletionChoiceHandler) - .next() - .put( - deleteAllPaymentMethodsByFunction.request(EnableableFunctionsEnum.BPD) - ) - .next() - .take([ - deleteAllPaymentMethodsByFunction.success, - deleteAllPaymentMethodsByFunction.failure - ]) - .next(deleteAllPaymentMethodsByFunction.failure({ error: new Error() })) - .isDone(); - }); - - it("If deleteAllPaymentMethodsByFunction succeed, should put the bpdUpdateOptInStatusMethod.request action", () => { - testSaga(optInDeletionChoiceHandler) - .next() - .put( - deleteAllPaymentMethodsByFunction.request(EnableableFunctionsEnum.BPD) - ) - .next() - .take([ - deleteAllPaymentMethodsByFunction.success, - deleteAllPaymentMethodsByFunction.failure - ]) - .next( - deleteAllPaymentMethodsByFunction.success( - {} as DeleteAllByFunctionSuccess - ) - ) - .put(bpdUpdateOptInStatusMethod.request(CitizenOptInStatusEnum.DENIED)) - .next() - .isDone(); - }); -}); diff --git a/ts/features/bonus/bpd/saga/orchestration/optInPaymentMethods/__tests__/optInShouldShowChoiceHandler.test.ts b/ts/features/bonus/bpd/saga/orchestration/optInPaymentMethods/__tests__/optInShouldShowChoiceHandler.test.ts deleted file mode 100644 index 31950c6dd24..00000000000 --- a/ts/features/bonus/bpd/saga/orchestration/optInPaymentMethods/__tests__/optInShouldShowChoiceHandler.test.ts +++ /dev/null @@ -1,199 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { testSaga } from "redux-saga-test-plan"; -import { getType } from "typesafe-actions"; -import { CitizenOptInStatusEnum } from "../../../../../../../../definitions/bpd/citizen_v2/CitizenOptInStatus"; -import { - fetchWalletsFailure, - fetchWalletsRequestWithExpBackoff, - fetchWalletsSuccess -} from "../../../../../../../store/actions/wallet/wallets"; -import { - getBPDMethodsVisibleInWalletSelector, - pagoPaCreditCardWalletV1Selector -} from "../../../../../../../store/reducers/wallet/wallets"; -import { - remoteReady, - remoteUndefined -} from "../../../../../../../common/model/RemoteValue"; -import { - BpdActivationPayload, - bpdLoadActivationStatus -} from "../../../../store/actions/details"; -import { optInPaymentMethodsShowChoice } from "../../../../store/actions/optInPaymentMethods"; -import { - activationStatusSelector, - optInStatusSelector -} from "../../../../store/reducers/details/activation"; -import { optInShouldShowChoiceHandler } from "../optInShouldShowChoiceHandler"; - -const mockActivationStatus: BpdActivationPayload = { - enabled: true, - activationStatus: "never", - payoffInstr: undefined, - optInStatus: CitizenOptInStatusEnum.NOREQ -}; - -describe("optInShouldShowChoiceHandler saga", () => { - jest.useFakeTimers(); - - it("If bpdAllData fails, should dispatch the optInPaymentMethodsShowChoice.failure action and return", () => { - testSaga(optInShouldShowChoiceHandler) - .next() - .select(activationStatusSelector) - .next(remoteUndefined) - .put(bpdLoadActivationStatus.request()) - .next() - .take([ - getType(bpdLoadActivationStatus.success), - getType(bpdLoadActivationStatus.failure) - ]) - .next(bpdLoadActivationStatus.failure(new Error())) - .put(optInPaymentMethodsShowChoice.failure(new Error())) - .next() - .isDone(); - }); - - it("If bpdEnabled is not potSome, should dispatch the optInPaymentMethodsShowChoice.failure action and return", () => { - testSaga(optInShouldShowChoiceHandler) - .next() - .select(activationStatusSelector) - .next(remoteUndefined) - .put(bpdLoadActivationStatus.request()) - .next() - .take([ - getType(bpdLoadActivationStatus.success), - getType(bpdLoadActivationStatus.failure) - ]) - .next(bpdLoadActivationStatus.success(mockActivationStatus)) - .select(activationStatusSelector) - .next(remoteUndefined) - .put( - optInPaymentMethodsShowChoice.failure( - new Error("The bpdEnabled value is not potSome") - ) - ) - .next() - .isDone(); - }); - - it("If bpdEnabled is potSome with the value false, should dispatch the optInPaymentMethodsShowChoice.success action with payload false and return", () => { - testSaga(optInShouldShowChoiceHandler) - .next() - .select(activationStatusSelector) - .next(remoteUndefined) - .put(bpdLoadActivationStatus.request()) - .next() - .take([ - getType(bpdLoadActivationStatus.success), - getType(bpdLoadActivationStatus.failure) - ]) - .next(bpdLoadActivationStatus.success(mockActivationStatus)) - .select(activationStatusSelector) - .next(remoteReady("never")) - .put(optInPaymentMethodsShowChoice.success(false)) - .next() - .isDone(); - }); - - it("If optInStatus is not potSome, should dispatch the optInPaymentMethodsShowChoice.failure action and return", () => { - testSaga(optInShouldShowChoiceHandler) - .next() - .select(activationStatusSelector) - .next(remoteUndefined) - .put(bpdLoadActivationStatus.request()) - .next() - .take([ - getType(bpdLoadActivationStatus.success), - getType(bpdLoadActivationStatus.failure) - ]) - .next(bpdLoadActivationStatus.success(mockActivationStatus)) - .select(activationStatusSelector) - .next(remoteReady("subscribed")) - .select(optInStatusSelector) - .next(pot.none) - .put( - optInPaymentMethodsShowChoice.failure( - new Error("The optInStatus value is not potSome") - ) - ) - .next() - .isDone(); - }); - - it("If optInStatus is potSome with value different from NOREQ, should dispatch the optInPaymentMethodsShowChoice.success action with payload false and return", () => { - testSaga(optInShouldShowChoiceHandler) - .next() - .select(activationStatusSelector) - .next(remoteUndefined) - .put(bpdLoadActivationStatus.request()) - .next() - .take([ - getType(bpdLoadActivationStatus.success), - getType(bpdLoadActivationStatus.failure) - ]) - .next(bpdLoadActivationStatus.success(mockActivationStatus)) - .select(activationStatusSelector) - .next(remoteReady("subscribed")) - .select(optInStatusSelector) - .next(pot.some(CitizenOptInStatusEnum.DENIED)) - .put(optInPaymentMethodsShowChoice.success(false)) - .next() - .isDone(); - }); - - it("If fetchWallets fails, should dispatch the optInPaymentMethodsShowChoice.failure action", () => { - testSaga(optInShouldShowChoiceHandler) - .next() - .select(activationStatusSelector) - .next(remoteUndefined) - .put(bpdLoadActivationStatus.request()) - .next() - .take([ - getType(bpdLoadActivationStatus.success), - getType(bpdLoadActivationStatus.failure) - ]) - .next(bpdLoadActivationStatus.success(mockActivationStatus)) - .select(activationStatusSelector) - .next(remoteReady("subscribed")) - .select(optInStatusSelector) - .next(pot.some(CitizenOptInStatusEnum.NOREQ)) - .select(pagoPaCreditCardWalletV1Selector) - .next(pot.none) - .put(fetchWalletsRequestWithExpBackoff()) - .next() - .take([getType(fetchWalletsSuccess), getType(fetchWalletsFailure)]) - .next(fetchWalletsFailure(new Error())) - .put(optInPaymentMethodsShowChoice.failure(new Error())) - .next() - .isDone(); - }); - - it("If fetchWallets succeed, should dispatch the optInPaymentMethodsShowChoice.success action", () => { - testSaga(optInShouldShowChoiceHandler) - .next() - .select(activationStatusSelector) - .next(remoteUndefined) - .put(bpdLoadActivationStatus.request()) - .next() - .take([ - getType(bpdLoadActivationStatus.success), - getType(bpdLoadActivationStatus.failure) - ]) - .next(bpdLoadActivationStatus.success(mockActivationStatus)) - .select(activationStatusSelector) - .next(remoteReady("subscribed")) - .select(optInStatusSelector) - .next(pot.some(CitizenOptInStatusEnum.NOREQ)) - .select(pagoPaCreditCardWalletV1Selector) - .next(pot.none) - .put(fetchWalletsRequestWithExpBackoff()) - .next() - .take([getType(fetchWalletsSuccess), getType(fetchWalletsFailure)]) - .next(fetchWalletsSuccess([])) - .select(getBPDMethodsVisibleInWalletSelector) - .next([]) - .put(optInPaymentMethodsShowChoice.success(false)) - .next() - .isDone(); - }); -}); diff --git a/ts/features/bonus/bpd/saga/orchestration/optInPaymentMethods/optInDeletionChoiceHandler.ts b/ts/features/bonus/bpd/saga/orchestration/optInPaymentMethods/optInDeletionChoiceHandler.ts deleted file mode 100644 index 05db6adfe00..00000000000 --- a/ts/features/bonus/bpd/saga/orchestration/optInPaymentMethods/optInDeletionChoiceHandler.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { put, take } from "typed-redux-saga/macro"; -import { ActionType, isActionOf } from "typesafe-actions"; -import { deleteAllPaymentMethodsByFunction } from "../../../../../../store/actions/wallet/delete"; -import { EnableableFunctionsEnum } from "../../../../../../../definitions/pagopa/EnableableFunctions"; -import { bpdUpdateOptInStatusMethod } from "../../../store/actions/onboarding"; -import { CitizenOptInStatusEnum } from "../../../../../../../definitions/bpd/citizen_v2/CitizenOptInStatus"; -import { ReduxSagaEffect } from "../../../../../../types/utils"; - -/** - * This saga orchestrate the choice of the user to delete the payment methods added during the cashback. - * This saga execute 2 actions: - * - delete all the payment methods with the BPD capability - * - store the user choice - */ - -export function* optInDeletionChoiceHandler(): Generator< - ReduxSagaEffect, - void, - any -> { - // Perform the payment methods deletion - yield* put( - deleteAllPaymentMethodsByFunction.request(EnableableFunctionsEnum.BPD) - ); - const deleteAllPaymentMethodsByFunctionStatus = yield* take< - ActionType< - | typeof deleteAllPaymentMethodsByFunction.success - | typeof deleteAllPaymentMethodsByFunction.failure - > - >([ - deleteAllPaymentMethodsByFunction.success, - deleteAllPaymentMethodsByFunction.failure - ]); - if ( - isActionOf( - deleteAllPaymentMethodsByFunction.success, - deleteAllPaymentMethodsByFunctionStatus - ) - ) { - // If the payment methods deletion succeeded, perform the opt-in update - yield* put( - bpdUpdateOptInStatusMethod.request(CitizenOptInStatusEnum.DENIED) - ); - } -} diff --git a/ts/features/bonus/bpd/saga/orchestration/optInPaymentMethods/optInShouldShowChoiceHandler.ts b/ts/features/bonus/bpd/saga/orchestration/optInPaymentMethods/optInShouldShowChoiceHandler.ts deleted file mode 100644 index 171c7b86ca8..00000000000 --- a/ts/features/bonus/bpd/saga/orchestration/optInPaymentMethods/optInShouldShowChoiceHandler.ts +++ /dev/null @@ -1,144 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { put, select, take } from "typed-redux-saga/macro"; -import { ActionType, getType, isActionOf } from "typesafe-actions"; -import { CitizenOptInStatusEnum } from "../../../../../../../definitions/bpd/citizen_v2/CitizenOptInStatus"; -import { - fetchWalletsFailure, - fetchWalletsRequestWithExpBackoff, - fetchWalletsSuccess -} from "../../../../../../store/actions/wallet/wallets"; -import { - getBPDMethodsVisibleInWalletSelector, - pagoPaCreditCardWalletV1Selector -} from "../../../../../../store/reducers/wallet/wallets"; -import { ReduxSagaEffect } from "../../../../../../types/utils"; -import { - isLoading, - isReady, - RemoteValue -} from "../../../../../../common/model/RemoteValue"; -import { - ActivationStatus, - bpdLoadActivationStatus -} from "../../../store/actions/details"; -import { optInPaymentMethodsShowChoice } from "../../../store/actions/optInPaymentMethods"; -import { - activationStatusSelector, - optInStatusSelector -} from "../../../store/reducers/details/activation"; - -/** - * This saga manage the flow that checks if a user has already take a choice about the opt-in of the payment methods. - * - * The saga follows this flow: - * - check if the user participate or not in the cashback program - * - check if the user has already taken the opt-in payment methods choice - * - request the user's payment methods - * - check if the loading of the payment method succeed - * - if succeed start the saga - */ -export function* optInShouldShowChoiceHandler(): Generator< - ReduxSagaEffect, - void, - any -> { - const bpdActivationInitialStatus: RemoteValue = - yield* select(activationStatusSelector); - - // Check is needed to avoid to spawn multiple request if the status - // is already loading - if (!isLoading(bpdActivationInitialStatus)) { - // Load the information about the participation of the user to the bpd program - yield* put(bpdLoadActivationStatus.request()); - } - const bpdLoadActivationStatusResponse = yield* take< - ActionType< - | typeof bpdLoadActivationStatus.success - | typeof bpdLoadActivationStatus.failure - > - >([ - getType(bpdLoadActivationStatus.success), - getType(bpdLoadActivationStatus.failure) - ]); - - // If the bpdAllData request fail report the error - if ( - isActionOf(bpdLoadActivationStatus.failure, bpdLoadActivationStatusResponse) - ) { - yield* put( - optInPaymentMethodsShowChoice.failure( - bpdLoadActivationStatusResponse.payload - ) - ); - return; - } - - const activationStatus: RemoteValue = yield* select( - activationStatusSelector - ); - - // Safety check on field returned in @link{activationStatus} and managed by @{activationStatusReducer} - if (!isReady(activationStatus)) { - yield* put( - optInPaymentMethodsShowChoice.failure( - new Error("The bpdEnabled value is not potSome") - ) - ); - return; - } - - // If the user is never been enrolled in the bpd program returns with value false - if (activationStatus.kind === "ready" && activationStatus.value === "never") { - yield* put(optInPaymentMethodsShowChoice.success(false)); - return; - } - - const optInStatus: pot.Pot = yield* select( - optInStatusSelector - ); - - // Safety check on field returned in @link{optInStatus} and managed by @{optInStatusReducer} - if (optInStatus.kind !== "PotSome") { - yield* put( - optInPaymentMethodsShowChoice.failure( - new Error("The optInStatus value is not potSome") - ) - ); - return; - } - - // If the user already take a choice returns with value false - if (optInStatus.value !== CitizenOptInStatusEnum.NOREQ) { - yield* put(optInPaymentMethodsShowChoice.success(false)); - return; - } - - // Check if wallets are already loaded - // this check is needed becaus the exponential backoff would raise an error cause of the spawning of multiple requests - const potWallets = yield* select(pagoPaCreditCardWalletV1Selector); - - if (!pot.isLoading(potWallets)) { - // Load the user payment methods - yield* put(fetchWalletsRequestWithExpBackoff()); - } - const fetchWalletsResultAction = yield* take< - ActionType - >([getType(fetchWalletsSuccess), getType(fetchWalletsFailure)]); - - // If the loading work successfully starts the OptInPaymentMethods saga - if (isActionOf(fetchWalletsSuccess, fetchWalletsResultAction)) { - const bpdPaymentMethods = yield* select( - getBPDMethodsVisibleInWalletSelector - ); - - if (bpdPaymentMethods.length > 0) { - yield* put(optInPaymentMethodsShowChoice.success(true)); - return; - } - yield* put(optInPaymentMethodsShowChoice.success(false)); - } else { - yield* put( - optInPaymentMethodsShowChoice.failure(fetchWalletsResultAction.payload) - ); - } -} diff --git a/ts/features/bonus/bpd/screens/details/BpdDetailsScreen.tsx b/ts/features/bonus/bpd/screens/details/BpdDetailsScreen.tsx deleted file mode 100644 index 12d88cd55ce..00000000000 --- a/ts/features/bonus/bpd/screens/details/BpdDetailsScreen.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import * as React from "react"; -import { useEffect } from "react"; -import { StyleSheet, View } from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import LoadingSpinnerOverlay from "../../../../../components/LoadingSpinnerOverlay"; -import DarkLayout from "../../../../../components/screens/DarkLayout"; -import SectionStatusComponent from "../../../../../components/SectionStatus"; -import I18n from "../../../../../i18n"; -import { navigateBack } from "../../../../../store/actions/navigation"; -import { GlobalState } from "../../../../../store/reducers/types"; -import { emptyContextualHelp } from "../../../../../utils/emptyContextualHelp"; -import { showToast } from "../../../../../utils/showToast"; -import { useHardwareBackButton } from "../../../../../hooks/useHardwareBackButton"; -import BpdLastUpdateComponent from "../../components/BpdLastUpdateComponent"; -import { - isError, - isLoading, - isReady -} from "../../../../../common/model/RemoteValue"; -import { bpdAllData } from "../../store/actions/details"; -import { - bpdUnsubscribeCompleted, - bpdUnsubscribeFailure -} from "../../store/actions/onboarding"; -import { bpdUnsubscriptionSelector } from "../../store/reducers/details/activation"; -import { bpdSelectedPeriodSelector } from "../../store/reducers/details/selectedPeriod"; -import { bpdTransactionsForSelectedPeriod } from "../../store/reducers/details/transactions"; -import BpdPeriodSelector from "./BpdPeriodSelector"; -import BpdPeriodDetail from "./periods/BpdPeriodDetail"; -import GoToTransactions from "./transaction/GoToTransactions"; - -export type Props = ReturnType & - ReturnType; - -const styles = StyleSheet.create({ - headerSpacer: { - height: 172 - }, - selector: { - // TODO: temp, as placeholder from invision, waiting for the components - height: 192 + 10 + 16 + 16, - width: "100%", - position: "absolute", - top: 16, - zIndex: 7, - elevation: 7 - }, - selectorSpacer: { - height: 60 - } -}); - -/** - * The screen that allows the user to see all the details related to the bpd. - * @constructor - */ -const BpdDetailsScreen: React.FunctionComponent = props => { - const loading = isLoading(props.unsubscription); - const { - unsubscription, - completeUnsubscriptionSuccess, - completeUnsubscriptionFailure - } = props; - - useEffect(() => { - if (isError(unsubscription)) { - showToast(I18n.t("bonus.bpd.unsubscribe.failure"), "danger"); - completeUnsubscriptionFailure(); - } else if (isReady(unsubscription)) { - showToast(I18n.t("bonus.bpd.unsubscribe.success"), "success"); - completeUnsubscriptionSuccess(); - } - }, [ - unsubscription, - completeUnsubscriptionSuccess, - completeUnsubscriptionFailure - ]); - - useHardwareBackButton(() => { - props.goBack(); - return true; - }); - - /** - * Display the transactions button when: - * - Period is closed and transactions number is > 0 - * - Period is active - * never displays for inactive/incoming period - */ - const canRenderButton = pipe( - props.selectedPeriod, - O.fromNullable, - O.fold( - () => false, - sp => { - switch (sp.status) { - case "Closed": - return pipe( - props.selectedPeriod?.amount?.transactionNumber, - O.fromNullable, - O.map(trx => trx > 0), - O.getOrElse(() => false) - ); - case "Inactive": - return false; - default: - return true; - } - } - ) - ); - return ( - - } - gradientHeader={true} - hideHeader={true} - contextualHelp={emptyContextualHelp} - footerContent={ - canRenderButton ? ( - - ) : ( - // We need to render a footer element in order to have the right spacing when the device has the notch - - ) - } - footerFullWidth={} - > - - - - - - - - - ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - completeUnsubscriptionSuccess: () => { - dispatch(bpdAllData.request()); - dispatch(bpdUnsubscribeCompleted()); - navigateBack(); - }, - goToTransactions: () => null, - goBack: () => navigateBack(), - completeUnsubscriptionFailure: () => dispatch(bpdUnsubscribeFailure()) -}); - -const mapStateToProps = (state: GlobalState) => ({ - unsubscription: bpdUnsubscriptionSelector(state), - transactions: bpdTransactionsForSelectedPeriod(state), - selectedPeriod: bpdSelectedPeriodSelector(state) -}); - -export default connect(mapStateToProps, mapDispatchToProps)(BpdDetailsScreen); diff --git a/ts/features/bonus/bpd/screens/details/BpdPeriodSelector.tsx b/ts/features/bonus/bpd/screens/details/BpdPeriodSelector.tsx deleted file mode 100644 index d4770d5a015..00000000000 --- a/ts/features/bonus/bpd/screens/details/BpdPeriodSelector.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import * as AR from "fp-ts/lib/Array"; -import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import * as React from "react"; -import { useEffect, useState } from "react"; -import { View, StyleSheet } from "react-native"; -import { widthPercentageToDP } from "react-native-responsive-screen"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { IOColors } from "@pagopa/io-app-design-system"; -import { IOStyles } from "../../../../../components/core/variables/IOStyles"; -import { HorizontalScroll } from "../../../../../components/HorizontalScroll"; -import { GlobalState } from "../../../../../store/reducers/types"; -import { BpdCardComponent } from "../../components/bpdCardComponent/BpdCardComponent"; -import { bpdSelectPeriod } from "../../store/actions/selectedPeriod"; -import { bpdPeriodsAmountWalletVisibleSelector } from "../../store/reducers/details/combiner"; -import { BpdPeriodWithInfo } from "../../store/reducers/details/periods"; -import { bpdSelectedPeriodSelector } from "../../store/reducers/details/selectedPeriod"; - -export type Props = ReturnType & - ReturnType; - -const styles = StyleSheet.create({ - cardWrapper: { - width: widthPercentageToDP("100%"), - shadowColor: IOColors.black, - shadowOffset: { - width: 0, - height: 7 - }, - shadowOpacity: 0.29, - shadowRadius: 4.65, - height: 192 - } -}); - -/** - * An horizontal snap scroll view used to select a specific period of bpd. - * Each period is represented as a BpdPeriodCard. - * @constructor - */ -const BpdPeriodSelector: React.FunctionComponent = props => { - const periodWithAmountList = pot.getOrElse(props.periodsWithAmount, []); - const [initialPeriod, setInitialperiod] = useState(); - const constructPeriodList = () => - periodWithAmountList.map((periodWithAmount, i) => ( - - - - )); - - const selectPeriod = (index: number) => - pipe( - periodWithAmountList[index], - O.fromNullable, - O.map(currentItem => { - if (currentItem.awardPeriodId === props.selectedPeriod?.awardPeriodId) { - return; - } - props.changeSelectPeriod(currentItem); - }) - ); - - useEffect(() => { - if (initialPeriod === undefined) { - setInitialperiod( - pipe( - periodWithAmountList, - AR.findIndex( - elem => elem.awardPeriodId === props.selectedPeriod?.awardPeriodId - ), - O.getOrElse(() => 0) - ) - ); - } - }, [ - initialPeriod, - periodWithAmountList, - props.selectedPeriod?.awardPeriodId - ]); - - return ( - - {pot.isSome(props.periodsWithAmount) && - props.periodsWithAmount.value.length > 0 && ( - - )} - - ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - changeSelectPeriod: (period: BpdPeriodWithInfo) => - dispatch(bpdSelectPeriod(period)) -}); - -const mapStateToProps = (state: GlobalState) => ({ - // ATM the rules of visualization of a period in the selector is the same of the wallet - periodsWithAmount: bpdPeriodsAmountWalletVisibleSelector(state), - selectedPeriod: bpdSelectedPeriodSelector(state) -}); - -export default connect(mapStateToProps, mapDispatchToProps)(BpdPeriodSelector); diff --git a/ts/features/bonus/bpd/screens/details/components/bottomsheet/HowItWorks.tsx b/ts/features/bonus/bpd/screens/details/components/bottomsheet/HowItWorks.tsx deleted file mode 100644 index 4b970249384..00000000000 --- a/ts/features/bonus/bpd/screens/details/components/bottomsheet/HowItWorks.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { View } from "react-native"; -import * as React from "react"; -import { VSpacer } from "@pagopa/io-app-design-system"; -import Markdown from "../../../../../../../components/ui/Markdown"; -import I18n from "../../../../../../../i18n"; -import { localeDateFormat } from "../../../../../../../utils/locale"; -import { BpdPeriod } from "../../../../store/actions/periods"; - -type Props = { - period: BpdPeriod; -}; - -/** - * Display information about the current period - * @constructor - */ -export const HowItWorks: React.FunctionComponent = props => ( - - - - - {I18n.t("bonus.bpd.details.howItWorks.body", { - ...props.period, - startDate: localeDateFormat( - props.period.startDate, - I18n.t("global.dateFormats.fullFormatFullMonthLiteral") - ), - endDate: localeDateFormat( - props.period.endDate, - I18n.t("global.dateFormats.fullFormatFullMonthLiteral") - ) - })} - - - -); diff --git a/ts/features/bonus/bpd/screens/details/components/bottomsheet/WhyOtherCards.tsx b/ts/features/bonus/bpd/screens/details/components/bottomsheet/WhyOtherCards.tsx deleted file mode 100644 index 73cc3570d60..00000000000 --- a/ts/features/bonus/bpd/screens/details/components/bottomsheet/WhyOtherCards.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import * as React from "react"; -import { View, StyleSheet } from "react-native"; -import { IOColors, VSpacer } from "@pagopa/io-app-design-system"; -import ButtonDefaultOpacity from "../../../../../../../components/ButtonDefaultOpacity"; -import { Body } from "../../../../../../../components/core/typography/Body"; -import { Link } from "../../../../../../../components/core/typography/Link"; -import I18n from "../../../../../../../i18n"; -import { useLegacyIOBottomSheetModal } from "../../../../../../../utils/hooks/bottomSheet"; -import { openWebUrl } from "../../../../../../../utils/url"; - -const styles = StyleSheet.create({ - link: { - backgroundColor: IOColors.white, - borderColor: IOColors.white, - paddingRight: 0, - paddingLeft: 0 - } -}); - -const findOutMore = "https://io.italia.it/cashback/faq/#n3_11"; - -/** - * Explains why there are other cards - * @constructor - */ -export const WhyOtherCards = () => ( - - - - - {I18n.t( - "bonus.bpd.details.paymentMethods.activateOnOthersChannel.whyOtherCards.body" - )} - - openWebUrl(findOutMore)} - onPressWithGestureHandler={true} - style={styles.link} - > - - {I18n.t( - "bonus.bpd.details.paymentMethods.activateOnOthersChannel.whyOtherCards.cta" - )} - - - - -); - -export const useWhyOtherCardsBottomSheet = () => - useLegacyIOBottomSheetModal( - , - I18n.t( - "bonus.bpd.details.paymentMethods.activateOnOthersChannel.whyOtherCards.title" - ), - 300 - ); diff --git a/ts/features/bonus/bpd/screens/details/components/iban/BaseIbanInformationComponent.tsx b/ts/features/bonus/bpd/screens/details/components/iban/BaseIbanInformationComponent.tsx deleted file mode 100644 index 5ec7171ae5b..00000000000 --- a/ts/features/bonus/bpd/screens/details/components/iban/BaseIbanInformationComponent.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { Button } from "native-base"; -import * as React from "react"; -import { View, StyleSheet } from "react-native"; -import { VSpacer } from "@pagopa/io-app-design-system"; -import { InfoBox } from "../../../../../../../components/box/InfoBox"; -import { Body } from "../../../../../../../components/core/typography/Body"; -import { H4 } from "../../../../../../../components/core/typography/H4"; -import { Label } from "../../../../../../../components/core/typography/Label"; -import { Link } from "../../../../../../../components/core/typography/Link"; -import { Monospace } from "../../../../../../../components/core/typography/Monospace"; -import { IOStyles } from "../../../../../../../components/core/variables/IOStyles"; -import I18n from "../../../../../../../i18n"; -import { isStringNullyOrEmpty } from "../../../../../../../utils/strings"; -import { - isReady, - RemoteValue -} from "../../../../../../../common/model/RemoteValue"; - -export type BaseIbanProps = { - iban: string | undefined; - technicalAccount: RemoteValue; - onInsertIban: () => void; -}; - -const styles = StyleSheet.create({ - row: { flexDirection: "row", justifyContent: "space-between" }, - insertIbanButton: { width: "100%" } -}); - -/** - * Render a Infobox that warns the user that should insert the IBAN to receive - * the cashback amount - * @param props - * @constructor - */ -const NoIbanComponent = (props: { onPress: () => void }) => ( - <> - - {I18n.t("bonus.bpd.details.components.iban.noIbanBody")} - - - - -); - -/** - * Display the current IBAN - * @constructor - */ -const IbanComponent = (props: { iban: string }) => ( - {props.iban} -); - -/** - * Display the technical IBAN message - * @constructor - */ -const TechnicalIbanComponent = (props: { technicalIban: string }) => ( - {props.technicalIban} -); - -export const BaseIbanInformationComponent: React.FunctionComponent< - BaseIbanProps -> = props => ( - - -

{I18n.t("bonus.bpd.details.components.iban.title")}

- {!isStringNullyOrEmpty(props.iban) && ( - - {I18n.t("global.buttons.edit").toLowerCase()} - - )} -
- - {/* Also if it is a technical IBAN the field IBAN is filled (with a fake IBAN). */} - {props.iban ? ( - isReady(props.technicalAccount) && - props.technicalAccount.value !== undefined ? ( - - ) : ( - - ) - ) : ( - - )} - -
-); diff --git a/ts/features/bonus/bpd/screens/details/components/iban/IbanInformationComponent.tsx b/ts/features/bonus/bpd/screens/details/components/iban/IbanInformationComponent.tsx deleted file mode 100644 index 0ae8417a91f..00000000000 --- a/ts/features/bonus/bpd/screens/details/components/iban/IbanInformationComponent.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import * as React from "react"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import Placeholder from "rn-placeholder"; -import { GlobalState } from "../../../../../../../store/reducers/types"; -import { - fold, - RemoteValue -} from "../../../../../../../common/model/RemoteValue"; -import { bpdIbanInsertionStart } from "../../../../store/actions/iban"; -import { bpdIbanSelector } from "../../../../store/reducers/details/activation"; -import { bpdTechnicalAccountSelector } from "../../../../store/reducers/details/activation/technicalAccount"; -import { - BaseIbanInformationComponent, - BaseIbanProps -} from "./BaseIbanInformationComponent"; - -export type Props = ReturnType & - ReturnType; - -const LoadingIban = () => ; - -/** - * Render the Iban based on the RemoteValue. - * For safeness, even undefined, remote and error cases are managed, although the iban detail - * screen is only accessible if bpd has been activated. - * @param props - * @constructor - */ -const RenderRemoteIban = ( - props: { - iban: RemoteValue; - } & Omit -) => - fold( - props.iban, - () => null, - () => , - value => ( - - ), - _ => null - ); - -/** - * Link {@link BaseIbanInformationComponent} to the business logic - * Read the iban RemoteValue from the store - * Dispatch bpdIbanInsertionStart() in case of new iban insertion (or modification - * @param props - * @constructor - */ -const IbanInformationComponent: React.FunctionComponent = props => ( - -); - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - startIbanOnboarding: () => dispatch(bpdIbanInsertionStart()) -}); - -const mapStateToProps = (state: GlobalState) => ({ - iban: bpdIbanSelector(state), - technicalAccount: bpdTechnicalAccountSelector(state) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(IbanInformationComponent); diff --git a/ts/features/bonus/bpd/screens/details/components/paymentMethod/WalletPaymentMethodBpdList.tsx b/ts/features/bonus/bpd/screens/details/components/paymentMethod/WalletPaymentMethodBpdList.tsx deleted file mode 100644 index e05b0760ad5..00000000000 --- a/ts/features/bonus/bpd/screens/details/components/paymentMethod/WalletPaymentMethodBpdList.tsx +++ /dev/null @@ -1,207 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import * as O from "fp-ts/lib/Option"; -import * as React from "react"; -import { useEffect, useState } from "react"; -import { View, ActivityIndicator, Alert, StyleSheet } from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { VSpacer } from "@pagopa/io-app-design-system"; -import { InfoBox } from "../../../../../../../components/box/InfoBox"; -import { Body } from "../../../../../../../components/core/typography/Body"; -import { H4 } from "../../../../../../../components/core/typography/H4"; -import { Link } from "../../../../../../../components/core/typography/Link"; -import I18n from "../../../../../../../i18n"; -import { navigateToWalletAddPaymentMethod } from "../../../../../../../store/actions/navigation"; -import { fetchWalletsRequestWithExpBackoff } from "../../../../../../../store/actions/wallet/wallets"; -import { GlobalState } from "../../../../../../../store/reducers/types"; -import { showToast } from "../../../../../../../utils/showToast"; -import { PaymentMethodGroupedList } from "../../../../components/paymentMethodActivationToggle/list/PaymentMethodGroupedList"; -import { - atLeastOnePaymentMethodHasBpdEnabledSelector, - paymentMethodsWithActivationStatusSelector -} from "../../../../store/reducers/details/combiner"; - -type Props = ReturnType & - ReturnType; - -const styles = StyleSheet.create({ - row: { - flexDirection: "row", - justifyContent: "space-between" - } -}); - -const Spinner = () => ( - -); - -const UpdateLabel = (props: Props & { caption: string }) => - pot.isLoading(props.potWallets) ? ( - - ) : ( - {props.caption} - ); - -/** - * No payment methods are active - * @constructor - */ -const NoPaymentMethodAreActiveWarning = () => ( - - - {I18n.t("bonus.bpd.details.paymentMethods.noActiveMethod")} - - - -); - -/** - * No payment methods are found - * @constructor - */ -const NoPaymentMethodFound = () => ( - - - {I18n.t("bonus.bpd.details.paymentMethods.noPaymentMethods")} - - -); - -/** - * The wallet is none - * @param props - * @constructor - */ -const PaymentMethodNone = (props: Props) => ( - <> - -

{I18n.t("wallet.paymentMethods")}

- -
- -); - -/** - * The wallet is error - * @param props - * @constructor - */ -const PaymentMethodError = (props: Props) => ( - <> - -

{I18n.t("wallet.paymentMethods")}

- -
- - - {I18n.t("bonus.bpd.details.paymentMethods.error")} - - -); - -/** - * The wallet is some - * @param props - * @constructor - */ -const PaymentMethodSome = (props: Props) => - pot.isSome(props.potWallets) ? ( - - -

{I18n.t("wallet.paymentMethods")}

- -
- - {!props.atLeastOnePaymentMethodActive && - props.potWallets.value.length > 0 && ( - - )} - - {props.potWallets.value.length > 0 ? ( - - ) : ( - - )} -
- ) : null; - -const addPaymentMethod = (action: () => void) => - Alert.alert( - I18n.t("global.genericAlert"), - I18n.t("bonus.bpd.details.paymentMethods.add.alertBody"), - [ - { - text: I18n.t("global.buttons.continue"), - onPress: action - }, - { - text: I18n.t("global.buttons.cancel"), - style: "cancel" - } - ] - ); - -/** - * Render all the wallet v2 as bpd toggle - * @param props - * @constructor - */ -const WalletPaymentMethodBpdList: React.FunctionComponent = props => { - const [potState, setPotCurrentState] = useState(props.potWallets.kind); - const { potWallets } = props; - - useEffect(() => { - if (potWallets.kind !== potState) { - setPotCurrentState(potWallets.kind); - if (pot.isError(potWallets)) { - showToast(I18n.t("global.genericError"), "danger"); - } - } - }, [potWallets, potState]); - - return pot.fold( - props.potWallets, - () => , - () => , - _ => , - _ => , - _ => , - _ => , - _ => , - _ => - ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - addPaymentMethod: () => { - addPaymentMethod(() => - navigateToWalletAddPaymentMethod({ inPayment: O.none }) - ); - }, - loadWallets: () => dispatch(fetchWalletsRequestWithExpBackoff()) -}); - -const mapStateToProps = (state: GlobalState) => ({ - potWallets: paymentMethodsWithActivationStatusSelector(state), - atLeastOnePaymentMethodActive: - atLeastOnePaymentMethodHasBpdEnabledSelector(state) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(WalletPaymentMethodBpdList); diff --git a/ts/features/bonus/bpd/screens/details/components/summary/BpdSummaryComponent.tsx b/ts/features/bonus/bpd/screens/details/components/summary/BpdSummaryComponent.tsx deleted file mode 100644 index 32c9f8e9760..00000000000 --- a/ts/features/bonus/bpd/screens/details/components/summary/BpdSummaryComponent.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import * as React from "react"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { View, StyleSheet } from "react-native"; -import { HSpacer, VSpacer } from "@pagopa/io-app-design-system"; -import { profileNameSelector } from "../../../../../../../store/reducers/profile"; -import { GlobalState } from "../../../../../../../store/reducers/types"; -import { BpdPeriod } from "../../../../store/actions/periods"; -import { BpdPeriodWithInfo } from "../../../../store/reducers/details/periods"; -import { bpdSelectedPeriodSelector } from "../../../../store/reducers/details/selectedPeriod"; -import SuperCashbackRankingSummary from "./ranking/SuperCashbackRankingSummary"; -import { TextualSummary } from "./textualSummary/TextualSummary"; -import TransactionsGraphicalSummary from "./TransactionsGraphicalSummary"; - -type Props = ReturnType & - ReturnType; - -type SummaryData = { - period: BpdPeriodWithInfo; - name: string | undefined; -}; - -const styles = StyleSheet.create({ - row: { - flex: 1, - flexDirection: "row" - } -}); - -/** - * The graphical summary is visible only when the period is closed or when the period is Active - * and transactionNumber > 0 - * @param period - */ -const isGraphicalSummaryVisible = (period: BpdPeriodWithInfo) => - period.status === "Closed" || - (period.status === "Active" && period.amount.transactionNumber > 0); - -/** - * Return true if the SuperCashback is visible for the specified period - * @param period - */ -const isSuperCashbackVisible = (period: BpdPeriod) => period.minPosition > 0; - -const Content = (sd: SummaryData) => ( - - {isGraphicalSummaryVisible(sd.period) ? ( - - - {isSuperCashbackVisible(sd.period) ? ( - <> - - - - ) : null} - - ) : null} - - - -); - -/** - * Display a summary with a graphical and textual information about the minimum transaction - * and the amount earned for the period. - * @constructor - */ -const BpdSummaryComponent: React.FunctionComponent = props => - props.currentPeriod ? ( - - ) : null; - -const mapDispatchToProps = (_: Dispatch) => ({}); - -const mapStateToProps = (state: GlobalState) => ({ - currentPeriod: bpdSelectedPeriodSelector(state), - name: profileNameSelector(state) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(BpdSummaryComponent); diff --git a/ts/features/bonus/bpd/screens/details/components/summary/TransactionsGraphicalSummary.tsx b/ts/features/bonus/bpd/screens/details/components/summary/TransactionsGraphicalSummary.tsx deleted file mode 100644 index 1086496f9f4..00000000000 --- a/ts/features/bonus/bpd/screens/details/components/summary/TransactionsGraphicalSummary.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import * as React from "react"; -import { StyleSheet, TouchableOpacity } from "react-native"; -import { connect } from "react-redux"; -import { VSpacer } from "@pagopa/io-app-design-system"; -import { H2 } from "../../../../../../../components/core/typography/H2"; -import { H5 } from "../../../../../../../components/core/typography/H5"; -import { IOStyles } from "../../../../../../../components/core/variables/IOStyles"; -import I18n from "../../../../../../../i18n"; -import { Dispatch } from "../../../../../../../store/actions/types"; -import { GlobalState } from "../../../../../../../store/reducers/types"; -import { formatIntegerNumber } from "../../../../../../../utils/stringBuilder"; -import { BpdPeriod } from "../../../../store/actions/periods"; -import { BpdBaseShadowBoxLayout } from "./base/BpdBaseShadowBoxLayout"; -import { ProgressBar } from "./base/ProgressBar"; - -type Props = { - transactions: number; - minTransactions: number; - period: BpdPeriod; -} & ReturnType & - ReturnType; - -const styles = StyleSheet.create({ - title: { - textAlign: "center" - } -}); - -const loadLocales = () => ({ - title: I18n.t("bonus.bpd.details.components.transactionsCountOverview.title"), - of: I18n.t("bonus.bpd.details.components.transactionsCountOverview.of") -}); - -/** - * When transactions < minTransactions, display a progress bar with the related information - * @param props - * @deprecated not used anymore, it is kept for some time in case of second thoughts - */ -export const PercentageTransactionsSummary = (props: Props) => { - const { title, of } = loadLocales(); - return ( - - {title} - - } - row2={ -

-

- {formatIntegerNumber(props.transactions)} -

{" "} - {of} {formatIntegerNumber(props.minTransactions)} - - } - row3={ - <> - - - - } - /> - ); -}; - -/** - * When transactions >= minTransactions, display only a textual summary - * @param props - * @constructor - */ -const TextualTransactionsSummary = (props: Props) => { - const { title, of } = loadLocales(); - return ( - {title}} - row2={ -

- {formatIntegerNumber(props.transactions)} -

- } - row3={ -
- {of} {formatIntegerNumber(props.minTransactions)} -
- } - /> - ); -}; - -/** - * Displays to the user a summary of the transactions and how many are missing - * to reach the minimum necessary to receive the cashback. - * @param props - * @constructor - */ -const TransactionsGraphicalSummary = (props: Props) => ( - - - -); - -const mapStateToProps = (_: GlobalState) => ({}); - -const mapDispatchToProps = (_: Dispatch) => ({ - goToTransactions: () => null -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(TransactionsGraphicalSummary); diff --git a/ts/features/bonus/bpd/screens/details/components/summary/base/BpdBaseShadowBoxLayout.tsx b/ts/features/bonus/bpd/screens/details/components/summary/base/BpdBaseShadowBoxLayout.tsx deleted file mode 100644 index f3f0a76e69b..00000000000 --- a/ts/features/bonus/bpd/screens/details/components/summary/base/BpdBaseShadowBoxLayout.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import * as React from "react"; -import { VSpacer } from "@pagopa/io-app-design-system"; -import { ShadowBox } from "./ShadowBox"; - -type Props = { - row1: React.ReactNode; - row2: React.ReactNode; - row3: React.ReactNode; -}; - -/** - * Define a base layout for a bpd infobox, using a {@link ShadowBox} - * @param props - * @constructor - */ -export const BpdBaseShadowBoxLayout: React.FunctionComponent = props => ( - - {props.row1} - - {props.row2} - {props.row3} - -); diff --git a/ts/features/bonus/bpd/screens/details/components/summary/base/ShadowBox.tsx b/ts/features/bonus/bpd/screens/details/components/summary/base/ShadowBox.tsx deleted file mode 100644 index d70870060db..00000000000 --- a/ts/features/bonus/bpd/screens/details/components/summary/base/ShadowBox.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { IOColors } from "@pagopa/io-app-design-system"; -import * as React from "react"; -import { View, StyleSheet } from "react-native"; - -const styles = StyleSheet.create({ - body: { - borderRadius: 8, - backgroundColor: IOColors.white, - shadowColor: IOColors.bluegreyDark, - shadowOffset: { - width: 0, - height: 3 - }, - shadowOpacity: 0.2, - shadowRadius: 2.0, - elevation: 4, - flex: 1, - marginHorizontal: 2 - }, - container: { - paddingVertical: 12, - paddingHorizontal: 16 - } -}); - -/** - * A base shadowed box with a content - * @param props - * @constructor - */ -export const ShadowBox: React.FunctionComponent = props => ( - - {props.children} - -); diff --git a/ts/features/bonus/bpd/screens/details/components/summary/ranking/RankingNotReadyBottomSheet.tsx b/ts/features/bonus/bpd/screens/details/components/summary/ranking/RankingNotReadyBottomSheet.tsx deleted file mode 100644 index 1bc966cf071..00000000000 --- a/ts/features/bonus/bpd/screens/details/components/summary/ranking/RankingNotReadyBottomSheet.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { View } from "react-native"; -import * as React from "react"; -import { VSpacer } from "@pagopa/io-app-design-system"; -import I18n from "../../../../../../../../i18n"; -import Markdown from "../../../../../../../../components/ui/Markdown"; -import { useLegacyIOBottomSheetModal } from "../../../../../../../../utils/hooks/bottomSheet"; - -/** - * Display information about the current period - * @constructor - */ -const RankingNotReady = (): React.ReactElement => ( - - - - - {I18n.t("bonus.bpd.details.components.ranking.notReady.body")} - - - -); - -export const useRankingNotReadyBottomSheet = () => - useLegacyIOBottomSheetModal( - , - I18n.t("bonus.bpd.details.components.ranking.notReady.title"), - 450 - ); diff --git a/ts/features/bonus/bpd/screens/details/components/summary/ranking/SuperCashbackRankingSummary.tsx b/ts/features/bonus/bpd/screens/details/components/summary/ranking/SuperCashbackRankingSummary.tsx deleted file mode 100644 index 1d8e852dd8a..00000000000 --- a/ts/features/bonus/bpd/screens/details/components/summary/ranking/SuperCashbackRankingSummary.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import { Icon, VSpacer } from "@pagopa/io-app-design-system"; -import * as React from "react"; -import { StyleSheet, TouchableOpacity, View } from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { H2 } from "../../../../../../../../components/core/typography/H2"; -import { H5 } from "../../../../../../../../components/core/typography/H5"; -import { IOStyles } from "../../../../../../../../components/core/variables/IOStyles"; -import I18n from "../../../../../../../../i18n"; -import { GlobalState } from "../../../../../../../../store/reducers/types"; -import { formatIntegerNumber } from "../../../../../../../../utils/stringBuilder"; -import { useSuperCashbackRankingBottomSheet } from "../../../../../components/superCashbackRanking/SuperCashbackRanking"; -import { - BpdPeriodWithInfo, - BpdRanking, - BpdRankingReady, - isBpdRankingReady -} from "../../../../../store/reducers/details/periods"; -import { BpdBaseShadowBoxLayout } from "../base/BpdBaseShadowBoxLayout"; -import { useRankingNotReadyBottomSheet } from "./RankingNotReadyBottomSheet"; - -const loadLocales = () => ({ - title: I18n.t("bonus.bpd.details.components.ranking.title"), - of: I18n.t("bonus.bpd.details.components.transactionsCountOverview.of"), - wip: I18n.t("profile.preferences.list.wip") -}); - -const styles = StyleSheet.create({ - title: { - textAlign: "center" - } -}); - -type OwnProps = { - period: BpdPeriodWithInfo; -}; - -type Props = ReturnType & - ReturnType & - OwnProps; - -const SuperCashbackRankingReady = (props: { - ranking: number; - minRanking: number; -}): React.ReactElement => { - const { title, of } = loadLocales(); - const { present, bottomSheet } = useSuperCashbackRankingBottomSheet(); - return ( - <> - {bottomSheet} - - - {title} - - } - row2={ -

- {formatIntegerNumber(props.ranking)}° -

- } - row3={ -
- {of} {formatIntegerNumber(props.minRanking)} -
- } - /> -
- - ); -}; - -const SuperCashbackRankingNotReady = (): React.ReactElement => { - const { title, wip } = loadLocales(); - const { present, bottomSheet } = useRankingNotReadyBottomSheet(); - return ( - <> - {bottomSheet} - - - {title} - - } - row2={ - <> - - - - - - - } - row3={ -
- {wip} -
- } - /> -
- - ); -}; - -/** - * The ranking should be visible only when the remoteRanking is enabled && isBpdRankingReady - * @param ranking - * @param remoteEnabled - */ -const shouldDisplayRankingReady = ( - ranking: BpdRanking, - remoteEnabled: boolean | undefined -): ranking is BpdRankingReady => - remoteEnabled === true && isBpdRankingReady(ranking); - -/** - * Choose the right super cashback ranking representation: - * 1) The ranking is ready: SuperCashbackRankingReady - * 2) The ranking is not ready: TBD - * TODO: the cashback ranking should also be remotely activable - * @param props - * @constructor - */ -const SuperCashbackRankingSummary = (props: Props): React.ReactElement => - shouldDisplayRankingReady(props.period.ranking, undefined) ? ( - - ) : ( - - ); - -const mapDispatchToProps = (_: Dispatch) => ({}); - -const mapStateToProps = (_: GlobalState) => ({}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(SuperCashbackRankingSummary); diff --git a/ts/features/bonus/bpd/screens/details/components/summary/textualSummary/ActiveTextualSummary.tsx b/ts/features/bonus/bpd/screens/details/components/summary/textualSummary/ActiveTextualSummary.tsx deleted file mode 100644 index c4ad08c57c5..00000000000 --- a/ts/features/bonus/bpd/screens/details/components/summary/textualSummary/ActiveTextualSummary.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import * as React from "react"; -import { InfoBox } from "../../../../../../../../components/box/InfoBox"; -import { Body } from "../../../../../../../../components/core/typography/Body"; -import { H4 } from "../../../../../../../../components/core/typography/H4"; -import I18n from "../../../../../../../../i18n"; -import { formatNumberAmount } from "../../../../../../../../utils/stringBuilder"; -import { BpdPeriod } from "../../../../../store/actions/periods"; -import { isBpdRankingReady } from "../../../../../store/reducers/details/periods"; -import { TextualSummary } from "./TextualSummary"; - -type Props = React.ComponentProps; - -/** - * Display a warning for the current period if transactions < minTransaction and status === "Active" - */ -const Warning = (props: { period: BpdPeriod }) => ( - - - {I18n.t( - "bonus.bpd.details.components.transactionsCountOverview.currentPeriodKOBody.one" - )} -

- {I18n.t( - "bonus.bpd.details.components.transactionsCountOverview.currentPeriodKOBody.two", - { - transactions: props.period.minTransactionNumber - } - )} -

- {I18n.t( - "bonus.bpd.details.components.transactionsCountOverview.currentPeriodKOBody.three" - )} - -
-); - -/** - * Display a message informing the user that the cashback is unlocked for the current period - */ -const Unlock = (props: Props) => ( - - - {I18n.t( - "bonus.bpd.details.components.transactionsCountOverview.currentPeriodUnlockBody", - { - transactions: props.period.minTransactionNumber, - name: props.name - } - )} - - -); - -/** - * Display a message informing the user that he reached the max cashback amount for the current period - */ -const MaxAmount = (props: { name: string | undefined }) => ( - - - {I18n.t( - "bonus.bpd.details.components.transactionsCountOverview.currentPeriodMaxAmount", - { - name: props.name - } - )} - - -); - -/** - * Display a message informing the user that at the moment he may be eligible for supercashback - */ -const SuperCashback = (props: { superCashbackAmount: number }) => ( - - - {I18n.t( - "bonus.bpd.details.components.transactionsCountOverview.currentPeriodSuperCashback", - { superCashbackAmount: formatNumberAmount(props.superCashbackAmount) } - )} - - -); - -export const ActiveTextualSummary = (props: Props) => { - // active period but still not enough transaction - if ( - props.period.amount.transactionNumber < props.period.minTransactionNumber && - props.period.amount.totalCashback > 0 - ) { - return ; - } - if ( - props.period.amount.transactionNumber >= props.period.minTransactionNumber - ) { - // The user is in the supercashback ranking atm - if ( - isBpdRankingReady(props.period.ranking) && - props.period.ranking.ranking <= props.period.minPosition - ) { - return ( - - ); - } - // The max cashback amount is reached - if (props.period.amount.totalCashback >= props.period.maxPeriodCashback) { - return ; - } - // Cashback unlocked! visible for the next 10 transaction only - if ( - props.period.amount.transactionNumber <= - props.period.minTransactionNumber + 10 - ) { - return ; - } - } - return null; -}; diff --git a/ts/features/bonus/bpd/screens/details/components/summary/textualSummary/ClosedTextualSummary.tsx b/ts/features/bonus/bpd/screens/details/components/summary/textualSummary/ClosedTextualSummary.tsx deleted file mode 100644 index 3948b728777..00000000000 --- a/ts/features/bonus/bpd/screens/details/components/summary/textualSummary/ClosedTextualSummary.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import * as React from "react"; -import { InfoBox } from "../../../../../../../../components/box/InfoBox"; -import { Body } from "../../../../../../../../components/core/typography/Body"; -import I18n from "../../../../../../../../i18n"; -import { dateToAccessibilityReadableFormat } from "../../../../../../../../utils/accessibility"; -import { localeDateFormat } from "../../../../../../../../utils/locale"; -import { - formatIntegerNumber, - formatNumberAmount -} from "../../../../../../../../utils/stringBuilder"; -import { BpdPeriod } from "../../../../../store/actions/periods"; -import { isGracePeriod } from "../../../../../store/reducers/details/periods"; -import { TextualSummary } from "./TextualSummary"; - -type Props = React.ComponentProps; - -/** - * We await receipt of the latest transactions to consolidate the count - * @param props - * @constructor - */ -const GracePeriod = (props: { period: BpdPeriod }) => ( - - - {I18n.t( - "bonus.bpd.details.components.transactionsCountOverview.gracePeriodBody", - { - date: dateToAccessibilityReadableFormat(props.period.endDate), - endGracePeriodDate: dateToAccessibilityReadableFormat( - endGracePeriod(props.period) - ) - } - )} - - -); - -/** - * The user doesn't receive the amount (not enough transactions for the closed period) - * @param props - * @constructor - */ -const KO = (props: Props) => ( - - - {I18n.t( - "bonus.bpd.details.components.transactionsCountOverview.closedPeriodKOBody", - { - transactions: formatIntegerNumber(props.period.minTransactionNumber), - amount: formatNumberAmount(props.period.amount.totalCashback) - } - )} - - -); - -const transferDate = (period: BpdPeriod) => { - const endDate = new Date(period.endDate); - - // 60: max days to receive the money transfer - endDate.setDate(period.endDate.getDate() + 60); - return endDate; -}; - -const endGracePeriod = (period: BpdPeriod) => { - const endDate = new Date(period.endDate); - - endDate.setDate(period.endDate.getDate() + period.gracePeriod); - return endDate; -}; - -/** - * Enriches the text in case of Super Cashback or max amount - * @param props - */ -const enhanceOkText = (props: Props): O.Option => { - // the user earned the super cashback - if ( - props.period.superCashbackAmount > 0 && - props.period.amount.totalCashback >= props.period.superCashbackAmount - ) { - return O.some( - I18n.t( - "bonus.bpd.details.components.transactionsCountOverview.closedPeriodSuperCashback", - { - amount: formatNumberAmount(props.period.superCashbackAmount) - } - ) - ); - } - // the user earned the max amount - else if ( - props.period.amount.totalCashback >= props.period.maxPeriodCashback - ) { - return O.some( - I18n.t( - "bonus.bpd.details.components.transactionsCountOverview.closedPeriodMaxAmount" - ) - ); - } - return O.none; -}; - -/** - * The user will receive the refund! - * @param props - * @constructor - */ -const OK = (props: Props) => ( - - - {I18n.t( - "bonus.bpd.details.components.transactionsCountOverview.closedPeriodOKBody", - { - name: props.name, - amount: formatNumberAmount(props.period.amount.totalCashback) - } - )} - {pipe( - enhanceOkText(props), - O.getOrElse(() => "") - ) + "!\n"} - {I18n.t( - "bonus.bpd.details.components.transactionsCountOverview.moneyTransfer", - { - date: localeDateFormat( - transferDate(props.period), - I18n.t("global.dateFormats.fullFormatFullMonthLiteral") - ) - } - )} - - -); - -/** - * Return a textual summary for a closed period - * @param props - * @constructor - */ -export const ClosedTextualSummary = (props: Props) => { - // we are still in the grace period and warns the user that some transactions - // may still be pending - if (isGracePeriod(props.period)) { - return ; - } - // not enough transaction to receive the cashback - if ( - props.period.amount.transactionNumber < props.period.minTransactionNumber - ) { - return ; - } - // Congratulation! cashback received - return ; -}; diff --git a/ts/features/bonus/bpd/screens/details/components/summary/textualSummary/InactiveTextualSummary.tsx b/ts/features/bonus/bpd/screens/details/components/summary/textualSummary/InactiveTextualSummary.tsx deleted file mode 100644 index 5652f19b4d7..00000000000 --- a/ts/features/bonus/bpd/screens/details/components/summary/textualSummary/InactiveTextualSummary.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import * as React from "react"; -import { InfoBox } from "../../../../../../../../components/box/InfoBox"; -import { Body } from "../../../../../../../../components/core/typography/Body"; -import I18n from "../../../../../../../../i18n"; -import { dateToAccessibilityReadableFormat } from "../../../../../../../../utils/accessibility"; -import { BpdPeriod } from "../../../../../store/actions/periods"; - -/** - * Inform the user about the start date of the next period - * @param props - * @constructor - */ -export const InactiveTextualSummary = (props: { - period: BpdPeriod; -}): React.ReactElement => ( - - - {I18n.t( - "bonus.bpd.details.components.transactionsCountOverview.inactivePeriodBody", - { - date: dateToAccessibilityReadableFormat(props.period.startDate) - } - )} - - -); diff --git a/ts/features/bonus/bpd/screens/details/components/summary/textualSummary/TextualSummary.tsx b/ts/features/bonus/bpd/screens/details/components/summary/textualSummary/TextualSummary.tsx deleted file mode 100644 index be6e7bde7d4..00000000000 --- a/ts/features/bonus/bpd/screens/details/components/summary/textualSummary/TextualSummary.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import * as React from "react"; -import { BpdPeriodWithInfo } from "../../../../../store/reducers/details/periods"; -import { ActiveTextualSummary } from "./ActiveTextualSummary"; -import { ClosedTextualSummary } from "./ClosedTextualSummary"; -import { InactiveTextualSummary } from "./InactiveTextualSummary"; - -type Props = { - period: BpdPeriodWithInfo; - name: string | undefined; -}; - -/** - * Render additional text information for the user, related to the transactions and cashback amount - * Choose the textual infobox based on period and amount values - * @param props - * @constructor - */ -export const TextualSummary = (props: Props): React.ReactElement => { - switch (props.period.status) { - case "Inactive": - return ; - case "Closed": - return ; - case "Active": - return ; - } -}; diff --git a/ts/features/bonus/bpd/screens/details/components/unsubscribe/UnsubscribeComponent.tsx b/ts/features/bonus/bpd/screens/details/components/unsubscribe/UnsubscribeComponent.tsx deleted file mode 100644 index 92c50fe7bd7..00000000000 --- a/ts/features/bonus/bpd/screens/details/components/unsubscribe/UnsubscribeComponent.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { View } from "react-native"; -import * as React from "react"; -import { IOIconSizeScale, Icon, VSpacer } from "@pagopa/io-app-design-system"; -import { H3 } from "../../../../../../../components/core/typography/H3"; -import Markdown from "../../../../../../../components/ui/Markdown"; -import I18n from "../../../../../../../i18n"; - -const iconSize: IOIconSizeScale = 48; - -/** - * Informs the user about the consequences of the cashback unsubscription - * @constructor - */ -export const UnsubscribeComponent = (): React.ReactElement => ( - - - - -

{I18n.t("bonus.bpd.unsubscribe.body1")}

- - {I18n.t("bonus.bpd.unsubscribe.body2")} -
-); diff --git a/ts/features/bonus/bpd/screens/details/components/unsubscribe/UnsubscribeToBpd.tsx b/ts/features/bonus/bpd/screens/details/components/unsubscribe/UnsubscribeToBpd.tsx deleted file mode 100644 index b0005d0bf2d..00000000000 --- a/ts/features/bonus/bpd/screens/details/components/unsubscribe/UnsubscribeToBpd.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import * as React from "react"; -import { StyleSheet } from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { IOColors } from "@pagopa/io-app-design-system"; -import ButtonDefaultOpacity from "../../../../../../../components/ButtonDefaultOpacity"; -import { Label } from "../../../../../../../components/core/typography/Label"; -import I18n from "../../../../../../../i18n"; -import { GlobalState } from "../../../../../../../store/reducers/types"; -import { bpdDeleteUserFromProgram } from "../../../../store/actions/onboarding"; -import { identificationRequest } from "../../../../../../../store/actions/identification"; -import { shufflePinPadOnPayment } from "../../../../../../../config"; -import { useLegacyIOBottomSheetModal } from "../../../../../../../utils/hooks/bottomSheet"; -import { - cancelButtonProps, - errorButtonProps -} from "../../../../../../../components/buttons/ButtonConfigurations"; -import FooterWithButtons from "../../../../../../../components/ui/FooterWithButtons"; -import { UnsubscribeComponent } from "./UnsubscribeComponent"; - -type Props = ReturnType & - ReturnType; - -const styles = StyleSheet.create({ - button: { - width: "100%", - borderColor: IOColors.red, - borderWidth: 1, - backgroundColor: IOColors.white - } -}); - -/** - * Allow the user to unsubscribe from bpd - * @constructor - */ -const UnsubscribeToBpd: React.FunctionComponent = props => { - const { present, bottomSheet, dismiss } = useLegacyIOBottomSheetModal( - , - I18n.t("bonus.bpd.unsubscribe.title"), - 582, - dismiss()), - onPressWithGestureHandler: true - }} - rightButton={{ - ...errorButtonProps(() => { - dismiss(); - props.cancelBpd(); - }, I18n.t("bonus.bpd.unsubscribe.confirmCta")), - onPressWithGestureHandler: true - }} - /> - ); - - return ( - <> - - - - {bottomSheet} - - ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - cancelBpd: () => { - const onSuccess = () => dispatch(bpdDeleteUserFromProgram.request()); - dispatch( - identificationRequest( - false, - true, - undefined, - { - label: I18n.t("global.buttons.cancel"), - onCancel: () => undefined - }, - { - onSuccess - }, - shufflePinPadOnPayment - ) - ); - } -}); - -const mapStateToProps = (_: GlobalState) => ({}); - -export default connect(mapStateToProps, mapDispatchToProps)(UnsubscribeToBpd); diff --git a/ts/features/bonus/bpd/screens/details/periods/BpdActivePeriod.tsx b/ts/features/bonus/bpd/screens/details/periods/BpdActivePeriod.tsx deleted file mode 100644 index bd8329460e6..00000000000 --- a/ts/features/bonus/bpd/screens/details/periods/BpdActivePeriod.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { View } from "react-native"; -import * as React from "react"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { VSpacer } from "@pagopa/io-app-design-system"; -import { IOStyles } from "../../../../../../components/core/variables/IOStyles"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import IbanInformationComponent from "../components/iban/IbanInformationComponent"; -import BpdSummaryComponent from "../components/summary/BpdSummaryComponent"; -import UnsubscribeToBpd from "../components/unsubscribe/UnsubscribeToBpd"; -import WalletPaymentMethodBpdList from "../components/paymentMethod/WalletPaymentMethodBpdList"; - -export type Props = ReturnType & - ReturnType; - -/** - * Render the details for a current active cashback period - * @constructor - */ -const BpdActivePeriod: React.FunctionComponent = () => ( - - - - - - - - - - - -); - -const mapDispatchToProps = (_: Dispatch) => ({}); - -const mapStateToProps = (_: GlobalState) => ({}); - -export default connect(mapStateToProps, mapDispatchToProps)(BpdActivePeriod); diff --git a/ts/features/bonus/bpd/screens/details/periods/BpdClosedPeriod.tsx b/ts/features/bonus/bpd/screens/details/periods/BpdClosedPeriod.tsx deleted file mode 100644 index 1dddc2e798e..00000000000 --- a/ts/features/bonus/bpd/screens/details/periods/BpdClosedPeriod.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { View } from "react-native"; -import * as React from "react"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { VSpacer } from "@pagopa/io-app-design-system"; -import { IOStyles } from "../../../../../../components/core/variables/IOStyles"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { - BpdPeriodWithInfo, - isGracePeriod -} from "../../../store/reducers/details/periods"; -import { bpdSelectedPeriodSelector } from "../../../store/reducers/details/selectedPeriod"; -import IbanInformationComponent from "../components/iban/IbanInformationComponent"; -import BpdSummaryComponent from "../components/summary/BpdSummaryComponent"; - -export type Props = ReturnType & - ReturnType; - -const shouldRenderIbanComponent = (period: BpdPeriodWithInfo) => - isGracePeriod(period) || - (period.status === "Closed" && - period.amount.transactionNumber >= period.minTransactionNumber); - -/** - * Render the details for a completed and closed cashback periods - * @constructor - */ -const BpdClosedPeriod = (props: Props): React.ReactElement => ( - - - - - {props.currentPeriod && shouldRenderIbanComponent(props.currentPeriod) && ( - <> - - - - )} - -); - -const mapDispatchToProps = (_: Dispatch) => ({}); - -const mapStateToProps = (state: GlobalState) => ({ - currentPeriod: bpdSelectedPeriodSelector(state) -}); - -export default connect(mapStateToProps, mapDispatchToProps)(BpdClosedPeriod); diff --git a/ts/features/bonus/bpd/screens/details/periods/BpdInactivePeriod.tsx b/ts/features/bonus/bpd/screens/details/periods/BpdInactivePeriod.tsx deleted file mode 100644 index cd4a9300d54..00000000000 --- a/ts/features/bonus/bpd/screens/details/periods/BpdInactivePeriod.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { View } from "react-native"; -import * as React from "react"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { VSpacer } from "@pagopa/io-app-design-system"; -import { IOStyles } from "../../../../../../components/core/variables/IOStyles"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import IbanInformationComponent from "../components/iban/IbanInformationComponent"; -import BpdSummaryComponent from "../components/summary/BpdSummaryComponent"; -import UnsubscribeToBpd from "../components/unsubscribe/UnsubscribeToBpd"; -import WalletPaymentMethodBpdList from "../components/paymentMethod/WalletPaymentMethodBpdList"; - -export type Props = ReturnType & - ReturnType; - -/** - * Render the details for a future cashback period - * @constructor - */ -const BpdInactivePeriod: React.FunctionComponent = () => ( - - - - - - - - - - - -); - -const mapDispatchToProps = (_: Dispatch) => ({}); - -const mapStateToProps = (_: GlobalState) => ({}); - -export default connect(mapStateToProps, mapDispatchToProps)(BpdInactivePeriod); diff --git a/ts/features/bonus/bpd/screens/details/periods/BpdPeriodDetail.tsx b/ts/features/bonus/bpd/screens/details/periods/BpdPeriodDetail.tsx deleted file mode 100644 index 4ab0821aeb7..00000000000 --- a/ts/features/bonus/bpd/screens/details/periods/BpdPeriodDetail.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { View } from "react-native"; -import * as React from "react"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { IOStyles } from "../../../../../../components/core/variables/IOStyles"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { BpdPeriodWithInfo } from "../../../store/reducers/details/periods"; -import { bpdSelectedPeriodSelector } from "../../../store/reducers/details/selectedPeriod"; -import BpdActivePeriod from "./BpdActivePeriod"; -import BpdClosedPeriod from "./BpdClosedPeriod"; -import BpdInactivePeriod from "./BpdInactivePeriod"; - -export type Props = ReturnType & - ReturnType; - -const selectPeriodScreen = (period: BpdPeriodWithInfo) => { - switch (period.status) { - case "Active": - return ; - case "Closed": - return ; - case "Inactive": - return ; - } -}; - -/** - * The body and details for a specific cashback period. Will change if is Active, Inactive or Closed - * @constructor - */ -const BpdPeriodDetail: React.FunctionComponent = props => ( - - {props.selectedPeriod && selectPeriodScreen(props.selectedPeriod)} - -); - -const mapDispatchToProps = (_: Dispatch) => ({}); - -const mapStateToProps = (state: GlobalState) => ({ - selectedPeriod: bpdSelectedPeriodSelector(state) -}); - -export default connect(mapStateToProps, mapDispatchToProps)(BpdPeriodDetail); diff --git a/ts/features/bonus/bpd/screens/details/transaction/BpdAvailableTransactionsScreen.tsx b/ts/features/bonus/bpd/screens/details/transaction/BpdAvailableTransactionsScreen.tsx deleted file mode 100644 index e7fb721c6f1..00000000000 --- a/ts/features/bonus/bpd/screens/details/transaction/BpdAvailableTransactionsScreen.tsx +++ /dev/null @@ -1,339 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { compareDesc } from "date-fns"; -import * as AR from "fp-ts/lib/Array"; -import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import * as React from "react"; -import { - View, - SafeAreaView, - ScrollView, - SectionList, - SectionListData, - SectionListRenderItem -} from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { VSpacer } from "@pagopa/io-app-design-system"; -import { InfoBox } from "../../../../../../components/box/InfoBox"; -import { H1 } from "../../../../../../components/core/typography/H1"; -import { H4 } from "../../../../../../components/core/typography/H4"; -import { IOStyles } from "../../../../../../components/core/variables/IOStyles"; -import BaseScreenComponent from "../../../../../../components/screens/BaseScreenComponent"; -import I18n from "../../../../../../i18n"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { emptyContextualHelp } from "../../../../../../utils/emptyContextualHelp"; -import { localeDateFormat } from "../../../../../../utils/locale"; -import BaseDailyTransactionHeader from "../../../components/BaseDailyTransactionHeader"; -import BpdTransactionSummaryComponent from "../../../components/BpdTransactionSummaryComponent"; -import { - BpdTransactionItem, - EnhancedBpdTransaction -} from "../../../components/transactionItem/BpdTransactionItem"; -import { - atLeastOnePaymentMethodHasBpdEnabledSelector, - bpdDisplayTransactionsSelector, - paymentMethodsWithActivationStatusSelector -} from "../../../store/reducers/details/combiner"; -import { bpdSelectedPeriodSelector } from "../../../store/reducers/details/selectedPeriod"; -import BpdCashbackMilestoneComponent from "./BpdCashbackMilestoneComponent"; -import BpdEmptyTransactionsList from "./BpdEmptyTransactionsList"; -import { BpdTransactionDetailRepresentation } from "./detail/BpdTransactionDetailComponent"; - -export type Props = ReturnType & - ReturnType; - -type TotalCashbackPerDate = { - trxDate: Date; - totalCashBack: number; -}; - -const dataForFlatList = ( - transactions: pot.Pot, Error> -) => pot.getOrElse(transactions, []); - -export const isTotalCashback = (item: any): item is TotalCashbackPerDate => - item.totalCashBack !== undefined; - -/** - * Builds the array of objects needed to show the sectionsList grouped by transaction day. - * - * We check the subtotal of TotalCashback earned on each transaction to check when the user reaches the cashback. - * - * When creating the final array if we reached the cashback amount we set all the following transaction cashback value to 0 - * - * If the sum of cashback comes over the award we remove the exceeding part on the transaction. - * @param transactions - * @param cashbackAward - */ -const getTransactionsByDaySections = ( - transactions: ReadonlyArray, - cashbackAward: number -): ReadonlyArray< - SectionListData -> => { - const dates = [ - ...new Set( - transactions.map(trx => - localeDateFormat(trx.trxDate, I18n.t("global.dateFormats.dayFullMonth")) - ) - ) - ]; - - const transactionsAsc = AR.reverse([...transactions]); - - // accumulator to define when the user reached the cashback award amount - // and tracing the sum of all the cashback value to check if any negative trx may cause a revoke of cashback award - const amountWinnerAccumulator = transactionsAsc.reduce( - ( - acc: { - winner?: { - amount: number; - index: number; - date: Date; - }; - sumAmount: number; - }, - t: EnhancedBpdTransaction, - currIndex: number - ) => { - const sum = acc.sumAmount + t.cashback; - if (sum >= cashbackAward && !acc.winner) { - return { - winner: { - amount: sum, - index: currIndex, - date: new Date(t.trxDate) - }, - sumAmount: sum - }; - } else if (sum < cashbackAward) { - return { - sumAmount: sum - }; - } - return { - ...acc, - sumAmount: sum - }; - }, - { - sumAmount: 0 - } - ); - - const maybeWinner = O.fromNullable(amountWinnerAccumulator.winner); - - // If the user reached the cashback amount within transactions we actualize all the cashback value starting from the index of winning transaction - // if the winning transaction makes cashback value exceed the limit we set the amount to the difference of transaction cashback value, total amout at winnign transaction and cashback award limit. - // all the following transactions will be set to 0 cashback value, since the limit has been reached (a dedicated item will be displayed) - const updatedTransactions = [...transactionsAsc].map( - (t, i): BpdTransactionDetailRepresentation => { - if (O.isSome(maybeWinner)) { - if ( - i === maybeWinner.value.index && - maybeWinner.value.amount > cashbackAward - ) { - return { - ...t, - cashback: t.cashback - (maybeWinner.value.amount - cashbackAward), - validForCashback: true - }; - } else if (i > maybeWinner.value.index) { - return { - ...t, - cashback: 0, - validForCashback: false - }; - } - } - return { ...t, validForCashback: true }; - } - ); - - return dates.map(d => ({ - title: d, - data: [ - ...updatedTransactions.filter( - t => - localeDateFormat( - t.trxDate, - I18n.t("global.dateFormats.dayFullMonth") - ) === d - ), - // we add the the data array an item to display the milestone reached - // in order to display the milestone after the latest transaction summed in the total we add 1 ms so that the ordering will set it correctly - ...pipe( - maybeWinner, - O.fold( - () => [], - w => { - if ( - localeDateFormat( - w.date, - I18n.t("global.dateFormats.dayFullMonth") - ) === d - ) { - return [ - { - totalCashBack: w.amount, - trxDate: new Date( - w.date.setMilliseconds(w.date.getMilliseconds() + 1) - ) - } - ]; - } - return []; - } - ) - ) - ].sort((trx1, trx2) => compareDesc(trx1.trxDate, trx2.trxDate)) - })); -}; - -const renderSectionHeader = (info: { - section: SectionListData< - BpdTransactionDetailRepresentation | TotalCashbackPerDate - >; -}): React.ReactNode => ( - !isTotalCashback(i)).length - } - /> -); - -export const NoPaymentMethodAreActiveWarning = () => ( - - -

- {I18n.t("bonus.bpd.details.transaction.noPaymentMethod.text1")} -

- {I18n.t("bonus.bpd.details.transaction.noPaymentMethod.text2")} -

- {I18n.t("bonus.bpd.details.transaction.noPaymentMethod.text3")} - -
- -
-); - -/** - * Display all the transactions for a specific period - * TODO: scroll to refresh, display error, display loading - * @constructor - */ -const BpdAvailableTransactionsScreen: React.FunctionComponent< - Props -> = props => { - const transactions = dataForFlatList(props.transactionForSelectedPeriod); - - const trxSortByDate = [...transactions].sort((trx1, trx2) => - compareDesc(trx1.trxDate, trx2.trxDate) - ); - - const maybeLastUpdateDate = pipe( - [...trxSortByDate].map(t => t.trxDate), - AR.lookup(0) - ); - - const renderTransactionItem: SectionListRenderItem< - BpdTransactionDetailRepresentation | TotalCashbackPerDate - > = info => { - if (isTotalCashback(info.item)) { - return ( - 0, - p => p.maxPeriodCashback - ) - )} - /> - ); - } - return ; - }; - - return ( - - - - -

{I18n.t("bonus.bpd.details.transaction.title")}

-
- - - - {props.selectedPeriod && O.isSome(maybeLastUpdateDate) && ( - <> - - - - )} - - {props.selectedPeriod && - (transactions.length > 0 ? ( - - isTotalCashback(t) - ? `awarded_cashback_item${t.totalCashBack}` - : t.keyId - } - /> - ) : !props.atLeastOnePaymentMethodActive && - pot.isSome(props.potWallets) && - props.potWallets.value.length > 0 ? ( - - - - ) : ( - - - - ))} - -
-
- ); -}; - -const mapDispatchToProps = (_: Dispatch) => ({}); - -const mapStateToProps = (state: GlobalState) => ({ - transactionForSelectedPeriod: bpdDisplayTransactionsSelector(state), - selectedPeriod: bpdSelectedPeriodSelector(state), - potWallets: paymentMethodsWithActivationStatusSelector(state), - atLeastOnePaymentMethodActive: - atLeastOnePaymentMethodHasBpdEnabledSelector(state) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(BpdAvailableTransactionsScreen); diff --git a/ts/features/bonus/bpd/screens/details/transaction/BpdCashbackMilestoneComponent.tsx b/ts/features/bonus/bpd/screens/details/transaction/BpdCashbackMilestoneComponent.tsx deleted file mode 100644 index 0c1986c28ed..00000000000 --- a/ts/features/bonus/bpd/screens/details/transaction/BpdCashbackMilestoneComponent.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import * as React from "react"; -import { View, Image, StyleSheet } from "react-native"; -import { IOColors } from "@pagopa/io-app-design-system"; -import fireworksIcon from "../../../../../../../img/bonus/bpd/fireworks.png"; -import { formatNumberAmount } from "../../../../../../utils/stringBuilder"; -import { H4 } from "../../../../../../components/core/typography/H4"; -import { IOStyles } from "../../../../../../components/core/variables/IOStyles"; -import I18n from "../../../../../../i18n"; - -type Props = { - cashbackValue: number; -}; - -const styles = StyleSheet.create({ - container: { - flexDirection: "row", - alignItems: "center", - justifyContent: "space-between", - backgroundColor: IOColors.blue, - paddingVertical: 16 - }, - image: { width: 32, height: 32, resizeMode: "cover" }, - textPadding: { - paddingHorizontal: 16 - } -}); -const BpdCashbackMilestoneComponent: React.FunctionComponent = ( - props: Props -) => ( - - - -

- {I18n.t( - "bonus.bpd.details.transaction.detail.summary.milestone.title", - { cashbackValue: formatNumberAmount(props.cashbackValue, true) } - )} -

-

- {I18n.t("bonus.bpd.details.transaction.detail.summary.milestone.body")} -

-
-
-); - -export default BpdCashbackMilestoneComponent; diff --git a/ts/features/bonus/bpd/screens/details/transaction/BpdEmptyTransactionsList.tsx b/ts/features/bonus/bpd/screens/details/transaction/BpdEmptyTransactionsList.tsx deleted file mode 100644 index b0d44b9bcc8..00000000000 --- a/ts/features/bonus/bpd/screens/details/transaction/BpdEmptyTransactionsList.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { View } from "react-native"; -import * as React from "react"; -import { VSpacer } from "@pagopa/io-app-design-system"; -import { InfoBox } from "../../../../../../components/box/InfoBox"; -import { Body } from "../../../../../../components/core/typography/Body"; -import { H4 } from "../../../../../../components/core/typography/H4"; -import I18n from "../../../../../../i18n"; -import { BottomSheetBpdTransactionsBody } from "../../../components/BpdTransactionSummaryComponent"; - -const BpdEmptyTransactionsList: React.FunctionComponent = () => ( - <> - -

{I18n.t("bonus.bpd.details.transaction.detail.empty.text1")}

- {I18n.t("bonus.bpd.details.transaction.detail.empty.text2")} -
- - - - -

- {I18n.t( - "bonus.bpd.details.transaction.detail.summary.calendarBlock.text1" - )} -

- {" "} - {I18n.t( - "bonus.bpd.details.transaction.detail.summary.calendarBlock.text2" - )} -

- {I18n.t( - "bonus.bpd.details.transaction.detail.summary.calendarBlock.text3" - )} - -
- - - -); - -export default BpdEmptyTransactionsList; diff --git a/ts/features/bonus/bpd/screens/details/transaction/BpdTransactionsScreen.tsx b/ts/features/bonus/bpd/screens/details/transaction/BpdTransactionsScreen.tsx deleted file mode 100644 index c037245eac2..00000000000 --- a/ts/features/bonus/bpd/screens/details/transaction/BpdTransactionsScreen.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import * as React from "react"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { EnhancedBpdTransaction } from "../../../components/transactionItem/BpdTransactionItem"; -import { bpdAllData } from "../../../store/actions/details"; -import { bpdDisplayTransactionsSelector } from "../../../store/reducers/details/combiner"; -import { bpdLastUpdateSelector } from "../../../store/reducers/details/lastUpdate"; -import BpdAvailableTransactionsScreen from "./BpdAvailableTransactionsScreen"; -import LoadTransactions from "./LoadTransactions"; -import TransactionsUnavailable from "./TransactionsUnavailable"; - -export type Props = ReturnType & - ReturnType; - -/** - * Associate at every state of the pot transactions status the right screen to show - * @param transactions - */ -const handleTransactionsStatus = ( - transactions: pot.Pot, Error> -) => - pot.fold( - transactions, - () => , - () => , - _ => , - _ => , - _ => , - _ => , - _ => , - _ => - ); - -/** - * Display all the transactions for a specific period if available, in other case show a loading or an error screen. - * First check the whole bpd status than if is some check the transactions status. - * TODO: Delete, replaced by BpdTransactionsRouterScreen - * @deprecated - * @constructor - */ -const BpdTransactionsScreen: React.FC = (props: Props) => { - const { bpdLastUpdate, transactionForSelectedPeriod, loadTransactions } = - props; - React.useEffect(() => { - if ( - pot.isError(bpdLastUpdate) || - pot.isError(transactionForSelectedPeriod) - ) { - loadTransactions(); - } - }, [bpdLastUpdate, transactionForSelectedPeriod, loadTransactions]); - return pot.fold( - props.bpdLastUpdate, - () => , - () => , - _ => , - _ => , - _ => handleTransactionsStatus(props.transactionForSelectedPeriod), - _ => , - _ => , - _ => - ); -}; -const mapDispatchToProps = (dispatch: Dispatch) => ({ - loadTransactions: () => dispatch(bpdAllData.request()) -}); - -const mapStateToProps = (state: GlobalState) => ({ - transactionForSelectedPeriod: bpdDisplayTransactionsSelector(state), - bpdLastUpdate: bpdLastUpdateSelector(state) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(BpdTransactionsScreen); diff --git a/ts/features/bonus/bpd/screens/details/transaction/GoToTransactions.tsx b/ts/features/bonus/bpd/screens/details/transaction/GoToTransactions.tsx deleted file mode 100644 index 3eb39bf2c62..00000000000 --- a/ts/features/bonus/bpd/screens/details/transaction/GoToTransactions.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import * as React from "react"; -import { getBottomSpace, isIphoneX } from "react-native-iphone-x-helper"; -import DeviceInfo from "react-native-device-info"; -import { Icon, HSpacer } from "@pagopa/io-app-design-system"; -import ButtonDefaultOpacity from "../../../../../../components/ButtonDefaultOpacity"; -import { Label } from "../../../../../../components/core/typography/Label"; -import I18n from "../../../../../../i18n"; - -type Props = { goToTransactions: () => void }; - -/** - * Display the transactions button when: - * - Period is closed and transactions number is > 0 - * - Period is active - * never displays for inactive/incoming period - * @param props - * @constructor - */ -const GoToTransactions: React.FunctionComponent = props => ( - - - - - -); - -export default GoToTransactions; diff --git a/ts/features/bonus/bpd/screens/details/transaction/LoadTransactions.tsx b/ts/features/bonus/bpd/screens/details/transaction/LoadTransactions.tsx deleted file mode 100644 index 1fbadfad0a6..00000000000 --- a/ts/features/bonus/bpd/screens/details/transaction/LoadTransactions.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import * as React from "react"; -import { View, ActivityIndicator } from "react-native"; -import { IOStyles } from "../../../../../../components/core/variables/IOStyles"; -import { InfoScreenComponent } from "../../../../../../components/infoScreen/InfoScreenComponent"; -import I18n from "../../../../../../i18n"; - -/** - * This screen is displayed when loading the list of transactions - * @constructor - */ -const LoadTransactions: React.FunctionComponent = () => ( - - - } - title={I18n.t("bonus.bpd.details.transaction.loading")} - /> - -); - -export default LoadTransactions; diff --git a/ts/features/bonus/bpd/screens/details/transaction/TransactionsUnavailable.tsx b/ts/features/bonus/bpd/screens/details/transaction/TransactionsUnavailable.tsx deleted file mode 100644 index 6b4a36ee800..00000000000 --- a/ts/features/bonus/bpd/screens/details/transaction/TransactionsUnavailable.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import * as React from "react"; -import { SafeAreaView } from "react-native"; -import image from "../../../../../../../img/wallet/errors/payment-unavailable-icon.png"; -import { IOStyles } from "../../../../../../components/core/variables/IOStyles"; -import { renderInfoRasterImage } from "../../../../../../components/infoScreen/imageRendering"; -import { InfoScreenComponent } from "../../../../../../components/infoScreen/InfoScreenComponent"; -import BaseScreenComponent from "../../../../../../components/screens/BaseScreenComponent"; -import I18n from "../../../../../../i18n"; - -export type Props = Pick< - React.ComponentProps, - "contextualHelp" ->; - -const loadLocales = () => ({ - headerTitle: I18n.t("bonus.bpd.details.transaction.goToButton"), - title: I18n.t("bonus.bpd.details.transaction.error.title"), - body: I18n.t("bonus.bpd.details.transaction.error.body") -}); - -/** - * This screen informs the user that there are problems retrieving the transactions list. - * @deprecated - * @constructor - */ -const TransactionsUnavailable: React.FunctionComponent = props => { - const { headerTitle, title, body } = loadLocales(); - - return ( - - - - - - ); -}; - -export default TransactionsUnavailable; diff --git a/ts/features/bonus/bpd/screens/details/transaction/detail/BpdTransactionDetailComponent.tsx b/ts/features/bonus/bpd/screens/details/transaction/detail/BpdTransactionDetailComponent.tsx deleted file mode 100644 index 36da07696bc..00000000000 --- a/ts/features/bonus/bpd/screens/details/transaction/detail/BpdTransactionDetailComponent.tsx +++ /dev/null @@ -1,183 +0,0 @@ -import * as React from "react"; -import { View, Image, StyleSheet } from "react-native"; -import { HSpacer, VSpacer } from "@pagopa/io-app-design-system"; -import CopyButtonComponent from "../../../../../../../components/CopyButtonComponent"; -import { IOBadge } from "../../../../../../../components/core/IOBadge"; -import { Body } from "../../../../../../../components/core/typography/Body"; -import { H4 } from "../../../../../../../components/core/typography/H4"; -import { H5 } from "../../../../../../../components/core/typography/H5"; -import { Monospace } from "../../../../../../../components/core/typography/Monospace"; -import { IOStyles } from "../../../../../../../components/core/variables/IOStyles"; -import I18n from "../../../../../../../i18n"; -import { localeDateFormat } from "../../../../../../../utils/locale"; -import { formatNumberAmount } from "../../../../../../../utils/stringBuilder"; -import { EnhancedBpdTransaction } from "../../../../components/transactionItem/BpdTransactionItem"; -import { BpdTransactionWarning } from "./BpdTransactionWarning"; - -/** - * @deprecated - */ -export type BpdTransactionDetailRepresentation = EnhancedBpdTransaction & { - // false if the transaction is not valid for the cashback (eg: the user has - // already reached the maximum cashback value for the period ) - validForCashback: boolean; -}; - -export type BpdTransactionDetailRepresentationV2 = - BpdTransactionDetailRepresentation & { - isPivot: boolean; - }; - -type Props = { transaction: BpdTransactionDetailRepresentation }; - -const styles = StyleSheet.create({ - image: { width: 48, height: 30 }, - rowId: { - flexDirection: "row", - justifyContent: "space-between" - }, - copyText: { - flex: 1, - paddingRight: 16 - } -}); - -const loadLocales = () => ({ - paymentMethod: I18n.t("wallet.paymentMethod"), - acquirerId: I18n.t("bonus.bpd.details.transaction.detail.acquirerId"), - issuerId: I18n.t("bonus.bpd.details.transaction.detail.issuerId") -}); - -type IdBlockProps = { - title: string; - value: string; -}; - -const CancelBadge = () => ( - -); - -const Table = (props: Props) => { - const isMidNight = - props.transaction.trxDate.getHours() + - props.transaction.trxDate.getMinutes() + - props.transaction.trxDate.getSeconds() === - 0; - return ( - - -
- {isMidNight - ? I18n.t("payment.details.info.onlyDate") - : I18n.t("payment.details.info.dateAndTime")} -
-

- {isMidNight - ? localeDateFormat( - props.transaction.trxDate, - I18n.t( - "global.dateFormats.fullFormatShortMonthLiteralWithoutTime" - ) - ) - : localeDateFormat( - props.transaction.trxDate, - I18n.t("global.dateFormats.fullFormatShortMonthLiteralWithTime") - )} -

-
- - -
- {I18n.t("bonus.bpd.details.transaction.detail.paymentCircuit")} -
-

- {props.transaction.circuitType === "Unknown" - ? I18n.t("global.unknown") - : props.transaction.circuitType} -

-
- - -
- {I18n.t("bonus.bpd.details.transaction.detail.transactionAmount")} -
- - {props.transaction.amount < 0 && ( - <> - - - - )} -

- {formatNumberAmount(props.transaction.amount, true)} -

-
-
- - -
- {I18n.t("bonus.bpd.details.transaction.detail.cashbackAmount")} -
-

- {formatNumberAmount(props.transaction.cashback, true)} -

-
-
- ); -}; - -const IdBlock = (props: IdBlockProps) => ( - -
{props.title}
- - - {props.value} - - - -
-); - -/** - * This screen shows the details about a single transaction. - * https://pagopa.invisionapp.com/console/IO---Cashback---Dettaglio-e-transazioni-ckg6bcpcr0ryb016nas1h4nbf/ckg6cwqfc03uf012khxbr6mun/play - * @param props - * @constructor - */ -export const BpdTransactionDetailComponent: React.FunctionComponent< - Props -> = props => { - const { paymentMethod, acquirerId, issuerId } = loadLocales(); - - return ( - - - {paymentMethod} - - - - -

{props.transaction.title}

-
- - {/* using the keyvaluetable with custom style in order to be quick */} - - - - - - - - ); -}; diff --git a/ts/features/bonus/bpd/screens/details/transaction/detail/BpdTransactionWarning.tsx b/ts/features/bonus/bpd/screens/details/transaction/detail/BpdTransactionWarning.tsx deleted file mode 100644 index f966dafe117..00000000000 --- a/ts/features/bonus/bpd/screens/details/transaction/detail/BpdTransactionWarning.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import * as React from "react"; -import { VSpacer } from "@pagopa/io-app-design-system"; -import { InfoBox } from "../../../../../../../components/box/InfoBox"; -import { Body } from "../../../../../../../components/core/typography/Body"; -import I18n from "../../../../../../../i18n"; -import { BpdTransactionDetailRepresentation } from "./BpdTransactionDetailComponent"; - -type Props = { transaction: BpdTransactionDetailRepresentation }; - -const TransactionWarning = (props: { text: string }) => ( - <> - - - {props.text} - - -); - -/** - * Displays a warning when certain conditions occur - * @param props - * @constructor - */ -export const BpdTransactionWarning: React.FunctionComponent = props => { - // transaction not valid for cashback (eg: the user has already reached - // the maximum cashback value for the period - if (!props.transaction.validForCashback) { - return ( - - ); - } - // max cashback for a single transaction reached - if ( - props.transaction.maxCashbackForTransactionAmount === - props.transaction.cashback - ) { - return ( - - ); - } - // transaction canceled (negative amount) - if (props.transaction.cashback < 0) { - return ( - - ); - } - return null; -}; diff --git a/ts/features/bonus/bpd/screens/details/transaction/detail/__test__/BdpTransactionDetailComponent.test.tsx b/ts/features/bonus/bpd/screens/details/transaction/detail/__test__/BdpTransactionDetailComponent.test.tsx deleted file mode 100644 index 2b26abeebc5..00000000000 --- a/ts/features/bonus/bpd/screens/details/transaction/detail/__test__/BdpTransactionDetailComponent.test.tsx +++ /dev/null @@ -1,263 +0,0 @@ -// import { debug } from "console"; -import { render } from "@testing-library/react-native"; -import * as React from "react"; -import I18n from "../../../../../../../../i18n"; -import { HPan } from "../../../../../store/actions/paymentMethods"; -import { AwardPeriodId } from "../../../../../store/actions/periods"; -import { - BpdTransactionDetailComponent, - BpdTransactionDetailRepresentation -} from "../BpdTransactionDetailComponent"; - -const baseTransaction: BpdTransactionDetailRepresentation = { - hashPan: - "0d4194712c5d820fcbbb2e7ba199e15f73cfd43f8fe49f0aa62e7901253506df" as HPan, - idTrxAcquirer: "10126487773E", - idTrxIssuer: "R64692", - amount: 87.79, - awardPeriodId: 2 as AwardPeriodId, - image: 29, - maxCashbackForTransactionAmount: 15, - title: "xxxxxxx", - trxDate: new Date("2100-12-17T00:00"), - keyId: "xxxxxxxxx", - cashback: 8.779, - circuitType: "Mastercard / Maestro", - validForCashback: true -}; - -describe("Test Transaction Timestamp", () => { - it("It should render label 'Date' and a value without hours and minutes when the transaction has a timestamp 00:00", () => { - const myTransaction: BpdTransactionDetailRepresentation = { - hashPan: - "0d4194712c5d820fcbbb2e7ba199e15f73cfd43f8fe49f0aa62e7901253506df" as HPan, - idTrxAcquirer: "10126487773E", - idTrxIssuer: "R64692", - amount: 87.79, - awardPeriodId: 2 as AwardPeriodId, - image: 29, - maxCashbackForTransactionAmount: 15, - title: "xxxxxxx", - trxDate: new Date("2100-12-17T00:00"), - keyId: "xxxxxxxxx", - cashback: 8.779, - circuitType: "Mastercard / Maestro", - validForCashback: true - }; - - // cut : Component Under Test - const cut = render( - - ); - - const dateLabel = cut.getByTestId("dateLabel"); - const dateValue = cut.getByTestId("dateValue"); - - expect(dateLabel.children[0]).toMatch(/Date/); - expect(dateValue.children[0]).toMatch(/17 Dec 2100/); - }); - - it("It should render label 'Date and time' and a value with hours and minutes when the transaction has a timestamp different from 00:00 ", () => { - const myTransaction: BpdTransactionDetailRepresentation = { - hashPan: - "0d4194712c5d820fcbbb2e7ba199e15f73cfd43f8fe49f0aa62e7901253506df" as HPan, - idTrxAcquirer: "10126487773E", - idTrxIssuer: "R64692", - amount: 87.79, - awardPeriodId: 2 as AwardPeriodId, - image: 29, - maxCashbackForTransactionAmount: 0, - title: "xxxxxxx", - trxDate: new Date("2100-12-17T01:00"), - keyId: "xxxxxxxxx", - cashback: 8.779, - circuitType: "Mastercard / Maestro", - validForCashback: true - }; - - // cut : Component Under Test - const cut = render( - - ); - const dateLabel = cut.getByTestId("dateLabel"); - const dateValue = cut.getByTestId("dateValue"); - - expect(dateLabel.children[0]).toMatch(/Date and time/); - expect(dateValue.children[0]).toMatch(/17 Dec 2100, 01:00/); - }); -}); - -describe("Test BpdTransactionDetailComponent warnings", () => { - describe("It should render warning 'max cashback for transaction' when maxCashbackForTransactionAmount === cashback", () => { - const maxAmount = 15.01; - const testCases: ReadonlyArray = [ - { - ...baseTransaction, - cashback: maxAmount, - maxCashbackForTransactionAmount: maxAmount, - validForCashback: true - } - ]; - - test.each(testCases)("Test case: %p", transaction => { - const component = render( - - ); - - const warningTest = component.queryByText( - I18n.t("bonus.bpd.details.transaction.detail.maxCashbackWarning", { - amount: maxAmount - }) - ); - expect(warningTest).not.toBeNull(); - }); - }); - - describe("It shouldn't render warning 'max cashback for transaction' when maxCashbackForTransactionAmount !== cashback", () => { - const maxAmount = 15.01; - const testCases: ReadonlyArray = [ - { - ...baseTransaction, - cashback: maxAmount, - maxCashbackForTransactionAmount: maxAmount - 0.01, - validForCashback: true - }, - { - ...baseTransaction, - cashback: maxAmount, - maxCashbackForTransactionAmount: 0, - validForCashback: false - }, - { - ...baseTransaction, - cashback: maxAmount, - maxCashbackForTransactionAmount: maxAmount, - validForCashback: false - } - ]; - - test.each(testCases)("Test case: %p", transaction => { - const component = render( - - ); - - const warningTest = component.queryByText( - I18n.t("bonus.bpd.details.transaction.detail.maxCashbackWarning", { - amount: maxAmount - }) - ); - expect(warningTest).toBeNull(); - }); - }); - - describe("It should render warning 'canceledOperationWarning' when cashback value < 0", () => { - const testCases: ReadonlyArray = [ - { - ...baseTransaction, - cashback: -0.01, - validForCashback: true - } - ]; - - test.each(testCases)("Test case: %p", transaction => { - const component = render( - - ); - - const warningTest = component.queryByText( - I18n.t("bonus.bpd.details.transaction.detail.canceledOperationWarning") - ); - expect(warningTest).not.toBeNull(); - }); - }); - describe("It shouldn't render warning 'canceledOperationWarning' when cashback value >= 0", () => { - const testCases: ReadonlyArray = [ - { - ...baseTransaction, - cashback: 0, - validForCashback: true - }, - { - ...baseTransaction, - cashback: 5, - validForCashback: false - }, - { - ...baseTransaction, - cashback: -5, - validForCashback: false - } - ]; - - test.each(testCases)("Test case: %p", transaction => { - const component = render( - - ); - - const warningTest = component.queryByText( - I18n.t("bonus.bpd.details.transaction.detail.canceledOperationWarning") - ); - expect(warningTest).toBeNull(); - }); - }); - describe("It should render warning 'maxCashbackForPeriodWarning' when validForCashback === false", () => { - const testCases: ReadonlyArray = [ - { - ...baseTransaction, - cashback: 0, - validForCashback: false - }, - { - ...baseTransaction, - cashback: 10.5, - validForCashback: false - }, - { - ...baseTransaction, - cashback: -10.5, - validForCashback: false - }, - { - ...baseTransaction, - cashback: 15, - maxCashbackForTransactionAmount: 15, - validForCashback: false - } - ]; - - test.each(testCases)("Test case: %p", transaction => { - const component = render( - - ); - - const warningTest = component.queryByText( - I18n.t( - "bonus.bpd.details.transaction.detail.maxCashbackForPeriodWarning" - ) - ); - expect(warningTest).not.toBeNull(); - }); - }); - describe("It shouldn't render warning 'maxCashbackForPeriodWarning'", () => { - const testCases: ReadonlyArray = [ - { - ...baseTransaction, - cashback: 0, - validForCashback: true - } - ]; - - test.each(testCases)("Test case: %p", transaction => { - const component = render( - - ); - - const warningTest = component.queryByText( - I18n.t( - "bonus.bpd.details.transaction.detail.maxCashbackForPeriodWarning" - ) - ); - expect(warningTest).toBeNull(); - }); - }); -}); diff --git a/ts/features/bonus/bpd/screens/details/transaction/v2/BpdAvailableTransactionsScreenV2.tsx b/ts/features/bonus/bpd/screens/details/transaction/v2/BpdAvailableTransactionsScreenV2.tsx deleted file mode 100644 index e0f64466103..00000000000 --- a/ts/features/bonus/bpd/screens/details/transaction/v2/BpdAvailableTransactionsScreenV2.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { View, SafeAreaView } from "react-native"; -import * as React from "react"; -import { VSpacer } from "@pagopa/io-app-design-system"; -import { H1 } from "../../../../../../../components/core/typography/H1"; -import { IOStyles } from "../../../../../../../components/core/variables/IOStyles"; -import BaseScreenComponent from "../../../../../../../components/screens/BaseScreenComponent"; -import I18n from "../../../../../../../i18n"; -import { emptyContextualHelp } from "../../../../../../../utils/emptyContextualHelp"; -import TransactionsSectionList from "./TransactionsSectionList"; - -export const BpdAvailableTransactionsScreenV2 = (): React.ReactElement => ( - - - - -

{I18n.t("bonus.bpd.details.transaction.title")}

-
- -
-
-); diff --git a/ts/features/bonus/bpd/screens/details/transaction/v2/BpdTransactionsRouterScreen.tsx b/ts/features/bonus/bpd/screens/details/transaction/v2/BpdTransactionsRouterScreen.tsx deleted file mode 100644 index 3ca6f6db0c3..00000000000 --- a/ts/features/bonus/bpd/screens/details/transaction/v2/BpdTransactionsRouterScreen.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import * as React from "react"; -import { useEffect, useState } from "react"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import WorkunitGenericFailure from "../../../../../../../components/error/WorkunitGenericFailure"; -import { mixpanelTrack } from "../../../../../../../mixpanel"; -import { GlobalState } from "../../../../../../../store/reducers/types"; -import { AwardPeriodId } from "../../../../store/actions/periods"; -import { bpdTransactionsLoadRequiredData } from "../../../../store/actions/transactions"; -import { bpdSelectedPeriodSelector } from "../../../../store/reducers/details/selectedPeriod"; -import { bpdTransactionsRequiredDataLoadStateSelector } from "../../../../store/reducers/details/transactionsv2/ui"; -import LoadTransactions from "../LoadTransactions"; -import { BpdAvailableTransactionsScreenV2 } from "./BpdAvailableTransactionsScreenV2"; -import TransactionsUnavailableV2 from "./TransactionsUnavailableV2"; - -type Props = ReturnType & - ReturnType; - -/** - * V2 version of the screen, makes sure that all essential data for viewing transactions is loaded - * @param props - * @constructor - */ -const BpdTransactionsRouterScreen = ( - props: Props -): React.ReactElement | null => { - const [firstLoadingRequest, setFirstLoadingRequest] = useState(false); - const [unexpectedError, setUnexpectedError] = useState(false); - - const { selectedPeriod, loadRequiredData } = props; - - // Refresh the transactions required data when the screen is open - useEffect(() => { - if (selectedPeriod) { - setFirstLoadingRequest(true); - loadRequiredData(selectedPeriod.awardPeriodId); - } else { - // This should never happens - setUnexpectedError(true); - } - }, [selectedPeriod, loadRequiredData]); - - // Handling unexpected error - if (unexpectedError) { - void mixpanelTrack("BPD_TRANSACTIONS_SCREEN_UNEXPECTED_ERROR"); - return ; - } - - // We want to be sure that before rendering the pot state, a first load request has been sent - if (!firstLoadingRequest) { - return ; - } - - return pot.fold( - props.transactionsRequiredData, - () => , - () => , - _ => , - _ => , - _ => , - _ => , - (_, __) => , - _ => - ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - loadRequiredData: (periodId: AwardPeriodId) => - dispatch(bpdTransactionsLoadRequiredData.request(periodId)) -}); - -const mapStateToProps = (state: GlobalState) => ({ - transactionsRequiredData: bpdTransactionsRequiredDataLoadStateSelector(state), - selectedPeriod: bpdSelectedPeriodSelector(state) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(BpdTransactionsRouterScreen); diff --git a/ts/features/bonus/bpd/screens/details/transaction/v2/TransactionsSectionList.tsx b/ts/features/bonus/bpd/screens/details/transaction/v2/TransactionsSectionList.tsx deleted file mode 100644 index 24e49a64579..00000000000 --- a/ts/features/bonus/bpd/screens/details/transaction/v2/TransactionsSectionList.tsx +++ /dev/null @@ -1,251 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import * as React from "react"; -import { useEffect } from "react"; -import { - View, - ActivityIndicator, - SectionList, - SectionListData, - SectionListRenderItemInfo -} from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { VSpacer } from "@pagopa/io-app-design-system"; -import { IOStyles } from "../../../../../../../components/core/variables/IOStyles"; -import I18n from "../../../../../../../i18n"; -import { GlobalState } from "../../../../../../../store/reducers/types"; -import { localeDateFormat } from "../../../../../../../utils/locale"; -import { showToast } from "../../../../../../../utils/showToast"; -import BaseDailyTransactionHeader from "../../../../components/BaseDailyTransactionHeader"; -import BpdTransactionSummaryComponent from "../../../../components/BpdTransactionSummaryComponent"; -import { BpdTransactionItem } from "../../../../components/transactionItem/BpdTransactionItem"; -import { AwardPeriodId } from "../../../../store/actions/periods"; -import { - BpdTransactionId, - bpdTransactionsLoadPage -} from "../../../../store/actions/transactions"; -import { - atLeastOnePaymentMethodHasBpdEnabledSelector, - paymentMethodsWithActivationStatusSelector -} from "../../../../store/reducers/details/combiner"; -import { bpdLastUpdateSelector } from "../../../../store/reducers/details/lastUpdate"; -import { bpdSelectedPeriodSelector } from "../../../../store/reducers/details/selectedPeriod"; -import { bpdDaysInfoByIdSelector } from "../../../../store/reducers/details/transactionsv2/daysInfo"; -import { - bpdTransactionByIdSelector, - bpdTransactionsGetNextCursor, - BpdTransactionsSectionItem, - bpdTransactionsSelector -} from "../../../../store/reducers/details/transactionsv2/ui"; -import { NoPaymentMethodAreActiveWarning } from "../BpdAvailableTransactionsScreen"; -import BpdCashbackMilestoneComponent from "../BpdCashbackMilestoneComponent"; -import BpdEmptyTransactionsList from "../BpdEmptyTransactionsList"; - -type Props = ReturnType & - ReturnType; - -type HeaderProps = Props & { - info: { - section: SectionListData; - }; -}; - -const RenderSectionHeaderBase = ( - // we need to pass props as argument because with the current react-redux version useSelect cannot be used - props: HeaderProps -) => - pipe( - props.bpdDaysInfoByIdSelector(props.info.section.dayInfoId), - O.fold( - () => null, - daysInfo => ( - - ) - ) - ); - -/** - * In order to optimize the rendering of the item, we use the dayInfo as unique identifier to avoid to redraw the component. - * The dayInfo data cannot change while consulting the list and we use this information to avoid a deep comparison - */ -export const RenderSectionHeader = React.memo( - RenderSectionHeaderBase, - (prev: HeaderProps, curr: HeaderProps) => - prev.info.section.dayInfoId === curr.info.section.dayInfoId -); - -type ItemProps = Props & { - trxId: SectionListRenderItemInfo; -}; - -const RenderItemBase = ( - // we need to pass props as argument because with the current react-redux version useSelect cannot be used - props: ItemProps -): React.ReactElement | null => - pipe( - props.bpdTransactionByIdSelector(props.trxId.item), - O.fold( - () => null, - trx => ( - <> - {trx.isPivot && ( - 0, - p => p.maxPeriodCashback - ) - )} - /> - )} - - - ) - ) - ); - -/** - * In order to optimize the rendering of the item, we use the keyId as unique identifier to avoid to redraw the component. - * The trx data cannot change while consulting the list and we use this information to avoid a deep comparison - */ -export const RenderItem = React.memo( - RenderItemBase, - (prev: ItemProps, curr: ItemProps) => prev.trxId.item === curr.trxId.item -); - -/** - * The header of the transactions list - * @param props - * @constructor - */ -const TransactionsHeader = ( - props: Pick -) => ( - - - {props.selectedPeriod && - pot.isSome(props.maybeLastUpdateDate) && - props.selectedPeriod.amount.transactionNumber > 0 && ( - <> - - - - )} - -); - -/** - * This component is rendered when no transactions are available - * @param props - * @constructor - */ -const TransactionsEmpty = ( - props: Pick -) => ( - - {!props.atLeastOnePaymentMethodActive && - pot.isSome(props.potWallets) && - props.potWallets.value.length > 0 ? ( - - ) : ( - - )} - -); - -/** - * Loading item, placed in the footer during the loading of the next page - * @constructor - */ -const FooterLoading = () => ( - <> - - - -); - -const TransactionsSectionList = (props: Props): React.ReactElement => { - const isError = pot.isError(props.potTransactions); - const isLoading = pot.isLoading(props.potTransactions); - const transactions = pot.getOrElse(props.potTransactions, []); - - useEffect(() => { - if (isError) { - showToast( - I18n.t("bonus.bpd.details.transaction.error.pageLoading"), - "danger" - ); - } - }, [isError]); - - return ( - ( - - )} - ListHeaderComponent={} - ListEmptyComponent={} - ListFooterComponent={isLoading && } - onEndReached={() => { - if (props.selectedPeriod && props.nextCursor && !isLoading) { - props.loadNextPage( - props.selectedPeriod.awardPeriodId, - props.nextCursor - ); - } - }} - scrollEnabled={true} - stickySectionHeadersEnabled={true} - sections={transactions} - renderItem={ri => } - keyExtractor={t => t} - /> - ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - loadNextPage: (awardPeriodId: AwardPeriodId, nextCursor: number) => - dispatch(bpdTransactionsLoadPage.request({ awardPeriodId, nextCursor })) -}); - -const mapStateToProps = (state: GlobalState) => ({ - selectedPeriod: bpdSelectedPeriodSelector(state), - maybeLastUpdateDate: bpdLastUpdateSelector(state), - potWallets: paymentMethodsWithActivationStatusSelector(state), - atLeastOnePaymentMethodActive: - atLeastOnePaymentMethodHasBpdEnabledSelector(state), - potTransactions: bpdTransactionsSelector(state), - nextCursor: bpdTransactionsGetNextCursor(state), - bpdTransactionByIdSelector: (trxId: BpdTransactionId) => - bpdTransactionByIdSelector(state, trxId), - bpdDaysInfoByIdSelector: (id: string) => bpdDaysInfoByIdSelector(state, id) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(TransactionsSectionList); diff --git a/ts/features/bonus/bpd/screens/details/transaction/v2/TransactionsUnavailableV2.tsx b/ts/features/bonus/bpd/screens/details/transaction/v2/TransactionsUnavailableV2.tsx deleted file mode 100644 index 3bb18877df4..00000000000 --- a/ts/features/bonus/bpd/screens/details/transaction/v2/TransactionsUnavailableV2.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import * as React from "react"; -import { SafeAreaView } from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import image from "../../../../../../../../img/wallet/errors/payment-unavailable-icon.png"; -import { IOStyles } from "../../../../../../../components/core/variables/IOStyles"; -import { renderInfoRasterImage } from "../../../../../../../components/infoScreen/imageRendering"; -import { InfoScreenComponent } from "../../../../../../../components/infoScreen/InfoScreenComponent"; -import BaseScreenComponent from "../../../../../../../components/screens/BaseScreenComponent"; -import FooterWithButtons from "../../../../../../../components/ui/FooterWithButtons"; -import I18n from "../../../../../../../i18n"; -import { navigateBack } from "../../../../../../../store/actions/navigation"; -import { GlobalState } from "../../../../../../../store/reducers/types"; -import { - cancelButtonProps, - confirmButtonProps -} from "../../../../../../../components/buttons/ButtonConfigurations"; -import { AwardPeriodId } from "../../../../store/actions/periods"; -import { bpdTransactionsLoadRequiredData } from "../../../../store/actions/transactions"; -import { bpdSelectedPeriodSelector } from "../../../../store/reducers/details/selectedPeriod"; - -export type Props = Pick< - React.ComponentProps, - "contextualHelp" -> & - ReturnType & - ReturnType; - -const loadLocales = () => ({ - headerTitle: I18n.t("bonus.bpd.details.transaction.goToButton"), - title: I18n.t("bonus.bpd.details.transaction.error.title"), - body: I18n.t("bonus.bpd.details.transaction.error.body") -}); - -/** - * This screen informs the user that there are problems retrieving the transactions list. - * Replace TransactionsUnavailable, adding also the retry button - * @constructor - */ -const TransactionsUnavailableV2Base: React.FunctionComponent = props => { - const { headerTitle, title, body } = loadLocales(); - - return ( - - - - - props.selectedPeriod - ? props.retry(props.selectedPeriod.awardPeriodId) - : props.cancel(), - I18n.t("global.buttons.retry") - )} - /> - - - ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - cancel: () => navigateBack(), - retry: (periodId: AwardPeriodId) => - dispatch(bpdTransactionsLoadRequiredData.request(periodId)) -}); - -const mapStateToProps = (state: GlobalState) => ({ - selectedPeriod: bpdSelectedPeriodSelector(state) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(TransactionsUnavailableV2Base); diff --git a/ts/features/bonus/bpd/screens/iban/IbanCTAEditScreen.tsx b/ts/features/bonus/bpd/screens/iban/IbanCTAEditScreen.tsx deleted file mode 100644 index 21f138ca4ae..00000000000 --- a/ts/features/bonus/bpd/screens/iban/IbanCTAEditScreen.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import * as React from "react"; -import { Alert } from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { LoadingErrorComponent } from "../../../../../components/LoadingErrorComponent"; -import BaseScreenComponent from "../../../../../components/screens/BaseScreenComponent"; -import I18n from "../../../../../i18n"; -import { navigateBack } from "../../../../../store/actions/navigation"; -import { GlobalState } from "../../../../../store/reducers/types"; -import { useActionOnFocus } from "../../../../../utils/hooks/useOnFocus"; -import { isStrictSome } from "../../../../../utils/pot"; -import { bpdAllData } from "../../store/actions/details"; -import { bpdSelectPeriod } from "../../store/actions/selectedPeriod"; -import { bpdEnabledSelector } from "../../store/reducers/details/activation"; -import { bpdLastUpdateSelector } from "../../store/reducers/details/lastUpdate"; -import { - bpdPeriodsSelector, - BpdPeriodWithInfo -} from "../../store/reducers/details/periods"; - -type Props = ReturnType & - ReturnType; - -const loadLocales = () => ({ - loadingCaption: I18n.t("global.remoteStates.loading"), - title: I18n.t("bonus.bpd.iban.verify"), - alertNotActiveTitle: I18n.t("bonus.bpd.iban.cta.bpdNotActiveTitle"), - alertNotActiveMessage: I18n.t("bonus.bpd.iban.cta.bpdNotActiveMessage"), - alertNotPeriodActiveTitle: I18n.t("bonus.bpd.iban.cta.noActivePeriodTitle"), - alertNotPeriodActiveMessage: I18n.t( - "bonus.bpd.iban.cta.noActivePeriodMessage" - ) -}); - -const InnerIbanCTAEditScreen = (props: Props) => { - useActionOnFocus(props.load); - // keep track if loading has been completed or not - // to avoid to handle not update data coming from the store - const [isLoadingComplete, setLoadingComplete] = - React.useState(false); - const { title, loadingCaption } = loadLocales(); - const { - bpdLoadState, - bpdEnabled, - goBack, - navigateToBPDPeriodDetails, - bpdPeriods - } = props; - React.useEffect(() => { - const { - alertNotActiveTitle, - alertNotActiveMessage, - alertNotPeriodActiveTitle, - alertNotPeriodActiveMessage - } = loadLocales(); - if (!pot.isNone(bpdLoadState) && !pot.isNone(bpdEnabled)) { - setLoadingComplete(true); - } - if ( - isLoadingComplete && - isStrictSome(bpdLoadState) && - pot.isSome(bpdEnabled) - ) { - // citizen not active to BPD - if (!bpdEnabled.value) { - Alert.alert(alertNotActiveTitle, alertNotActiveMessage, [ - { - onPress: goBack - } - ]); - return; - } - const activePeriod = pot - .getOrElse(bpdPeriods, []) - .find(p => p.status === "Active"); - if (activePeriod) { - goBack(); - navigateToBPDPeriodDetails(activePeriod); - } - // no active period - else { - Alert.alert(alertNotPeriodActiveTitle, alertNotPeriodActiveMessage, [ - { - onPress: goBack - } - ]); - } - } - }, [ - bpdLoadState, - bpdEnabled, - isLoadingComplete, - bpdPeriods, - goBack, - navigateToBPDPeriodDetails - ]); - - const hasErrors = pot.isError(props.bpdLoadState); - return ( - - - - ); -}; - -/** - * Landing screen from the CTA message that asks to review user's IBAN insertion - */ -const IbanCTAEditScreen: React.FC = (props: Props) => ( - -); - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - load: () => { - dispatch(bpdAllData.request()); - }, - goBack: () => navigateBack(), - navigateToBPDPeriodDetails: (bpdPeriod: BpdPeriodWithInfo) => { - dispatch(bpdSelectPeriod(bpdPeriod)); - } -}); - -const mapStateToProps = (state: GlobalState) => ({ - bpdLoadState: bpdLastUpdateSelector(state), - bpdPeriods: bpdPeriodsSelector(state), - bpdEnabled: bpdEnabledSelector(state), - bpdRemoteConfig: {} -}); - -export default connect(mapStateToProps, mapDispatchToProps)(IbanCTAEditScreen); diff --git a/ts/features/bonus/bpd/screens/iban/IbanLoadingUpsert.tsx b/ts/features/bonus/bpd/screens/iban/IbanLoadingUpsert.tsx deleted file mode 100644 index 48be3b97c49..00000000000 --- a/ts/features/bonus/bpd/screens/iban/IbanLoadingUpsert.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import * as React from "react"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { Iban } from "../../../../../../definitions/backend/Iban"; -import I18n from "../../../../../i18n"; -import { GlobalState } from "../../../../../store/reducers/types"; -import { useHardwareBackButton } from "../../../../../hooks/useHardwareBackButton"; -import { LoadingErrorComponent } from "../../../../../components/LoadingErrorComponent"; -import { - bpdIbanInsertionCancel, - bpdUpsertIban -} from "../../store/actions/iban"; -import { - bpdUpsertIbanIsError, - bpdUpsertIbanSelector -} from "../../store/reducers/details/activation/payoffInstrument"; - -export type Props = ReturnType & - ReturnType; - -/** - * displays to the user a loading screen while sending the new iban or - * possibly an error screen in case of unmanaged errors. - * @param props - * @constructor - */ -const IbanLoadingUpsert: React.FunctionComponent = props => { - const loading = I18n.t("bonus.bpd.iban.loading"); - - useHardwareBackButton(() => { - if (!props.isLoading) { - props.onAbort(); - } - return true; - }); - return ( - - pipe(props.ibanValue.value, O.fromNullable, O.map(props.onRetry)) - } - /> - ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - onAbort: () => dispatch(bpdIbanInsertionCancel()), - onRetry: (iban: Iban) => dispatch(bpdUpsertIban.request(iban)) -}); - -const mapStateToProps = (state: GlobalState) => ({ - isLoading: !bpdUpsertIbanIsError(state), - ibanValue: bpdUpsertIbanSelector(state) -}); - -export default connect(mapStateToProps, mapDispatchToProps)(IbanLoadingUpsert); diff --git a/ts/features/bonus/bpd/screens/iban/MainIbanScreen.tsx b/ts/features/bonus/bpd/screens/iban/MainIbanScreen.tsx deleted file mode 100644 index 96ac8356324..00000000000 --- a/ts/features/bonus/bpd/screens/iban/MainIbanScreen.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import * as React from "react"; -import { useEffect } from "react"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { GlobalState } from "../../../../../store/reducers/types"; -import { showToast } from "../../../../../utils/showToast"; -import { - isError, - isLoading, - isReady -} from "../../../../../common/model/RemoteValue"; -import { IbanStatus } from "../../saga/networking/patchCitizenIban"; -import { - bpdIbanInsertionContinue, - bpdIbanInsertionResetScreen -} from "../../store/actions/iban"; -import { bpdUpsertIbanSelector } from "../../store/reducers/details/activation/payoffInstrument"; -import { isBpdOnboardingOngoing } from "../../store/reducers/onboarding/ongoing"; -import I18n from "../../../../../i18n"; -import { emptyContextualHelp } from "../../../../../utils/emptyContextualHelp"; -import IbanLoadingUpsert from "./IbanLoadingUpsert"; -import IbanInsertionScreen from "./insertion/IbanInsertionScreen"; -import IbanKoNotOwned from "./ko/IbanKONotOwned"; -import IbanKOWrong from "./ko/IbanKOWrong"; - -export type Props = ReturnType & - ReturnType; - -const chooseRenderScreen = (props: Props) => { - const ibanStatus = props.upsertValue.outcome; - if (isLoading(ibanStatus) || isError(ibanStatus)) { - return ; - } else if (isReady(ibanStatus)) { - switch (ibanStatus.value) { - case IbanStatus.NOT_OWNED: - return ; - case IbanStatus.NOT_VALID: - return ; - case IbanStatus.OK: - case IbanStatus.CANT_VERIFY: - props.reset(); - props.completed(); - if (!props.onboardingOngoing) { - showToast(I18n.t("bonus.bpd.iban.success"), "success"); - } - } - } - return ; -}; - -const MainIbanScreen: React.FunctionComponent = props => { - const { reset } = props; - useEffect(() => { - reset(); - }, [reset]); - return chooseRenderScreen(props); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - reset: () => dispatch(bpdIbanInsertionResetScreen()), - completed: () => dispatch(bpdIbanInsertionContinue()) -}); - -const mapStateToProps = (state: GlobalState) => ({ - upsertValue: bpdUpsertIbanSelector(state), - onboardingOngoing: isBpdOnboardingOngoing(state) -}); - -export default connect(mapStateToProps, mapDispatchToProps)(MainIbanScreen); diff --git a/ts/features/bonus/bpd/screens/iban/insertion/IbanInsertionComponent.tsx b/ts/features/bonus/bpd/screens/iban/insertion/IbanInsertionComponent.tsx deleted file mode 100644 index 23de7bcbbb5..00000000000 --- a/ts/features/bonus/bpd/screens/iban/insertion/IbanInsertionComponent.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import * as E from "fp-ts/lib/Either"; -import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import * as React from "react"; -import { useState } from "react"; -import { View, Platform, SafeAreaView, ScrollView } from "react-native"; -import { VSpacer } from "@pagopa/io-app-design-system"; -import { Iban } from "../../../../../../../definitions/backend/Iban"; -import { makeFontStyleObject } from "../../../../../../components/core/fonts"; -import { Body } from "../../../../../../components/core/typography/Body"; -import { H1 } from "../../../../../../components/core/typography/H1"; -import { H4 } from "../../../../../../components/core/typography/H4"; -import { H5 } from "../../../../../../components/core/typography/H5"; -import { IOStyles } from "../../../../../../components/core/variables/IOStyles"; -import { LabelledItem } from "../../../../../../components/LabelledItem"; -import BaseScreenComponent from "../../../../../../components/screens/BaseScreenComponent"; -import I18n from "../../../../../../i18n"; -import { maybeNotNullyString } from "../../../../../../utils/strings"; -import { FooterTwoButtons } from "../../../../../../components/markdown/FooterTwoButtons"; - -type OwnProps = { - onBack: () => void; - onIbanConfirm: (iban: Iban) => void; - onContinue: () => void; - startIban?: string; - cancelText: string; -}; - -type Props = OwnProps & - Pick, "contextualHelp">; - -// https://en.wikipedia.org/wiki/International_Bank_Account_Number -// Italian Iban max length: 27 (sample: IT60X0542811101000000123456) -const IbanMaxLength = 34; - -const loadLocales = () => ({ - headerTitle: I18n.t("bonus.bpd.title"), - title: I18n.t("bonus.bpd.iban.insertion.title"), - body1: I18n.t("bonus.bpd.iban.insertion.body1"), - body1Bold: I18n.t("bonus.bpd.iban.insertion.body1Bold"), - body2: I18n.t("bonus.bpd.iban.insertion.body2"), - ibanDescription: I18n.t("bonus.bpd.iban.iban") -}); - -const IBANInputStyle = makeFontStyleObject("Regular", false, "RobotoMono"); - -const upperCaseAndNoBlanks = (text: string) => - text.replace(/\s/g, "").toUpperCase(); - -export const IbanInsertionComponent: React.FunctionComponent = props => { - const [iban, setIban] = useState(props.startIban); - const isValidIban = iban && E.isRight(Iban.decode(iban)); - const { headerTitle, title, body1, body1Bold, body2, ibanDescription } = - loadLocales(); - return ( - - - - - -

{title}

- - - {body1} -

{body1Bold}

- - -
{ibanDescription}
- undefined, - _ => E.isRight(Iban.decode(iban)) - ) - )} - inputProps={{ - value: iban, - autoCapitalize: "characters", - maxLength: IbanMaxLength, - onChangeText: text => { - // On Android we cannot modify the input text, or the text is duplicated - setIban( - text - ? Platform.select({ - android: text, - default: upperCaseAndNoBlanks(text) - }) - : undefined - ); - }, - style: IBANInputStyle - }} - /> - - {body2} -
-
- - pipe( - Iban.decode(upperCaseAndNoBlanks(iban as string)), - E.map(props.onIbanConfirm) - ) - } - onCancel={props.onContinue} - rightText={I18n.t("global.buttons.continue")} - leftText={props.cancelText} - /> -
-
- ); -}; diff --git a/ts/features/bonus/bpd/screens/iban/insertion/IbanInsertionScreen.tsx b/ts/features/bonus/bpd/screens/iban/insertion/IbanInsertionScreen.tsx deleted file mode 100644 index d9e0cb37594..00000000000 --- a/ts/features/bonus/bpd/screens/iban/insertion/IbanInsertionScreen.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import * as React from "react"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { Iban } from "../../../../../../../definitions/backend/Iban"; -import I18n from "../../../../../../i18n"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { emptyContextualHelp } from "../../../../../../utils/emptyContextualHelp"; -import { - bpdIbanInsertionCancel, - bpdIbanInsertionContinue, - bpdUpsertIban -} from "../../../store/actions/iban"; -import { bpdIbanPrefillSelector } from "../../../store/reducers/details/activation"; -import { isBpdOnboardingOngoing } from "../../../store/reducers/onboarding/ongoing"; -import { IbanInsertionComponent } from "./IbanInsertionComponent"; - -export type Props = ReturnType & - ReturnType; - -/** - * This screen allows the user to add / modify an iban to be used to receive the refund provided by bpd - * @constructor - */ -const IbanInsertionScreen: React.FunctionComponent = props => ( - -); - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - cancel: () => dispatch(bpdIbanInsertionCancel()), - continue: () => dispatch(bpdIbanInsertionContinue()), - submitIban: (iban: Iban) => dispatch(bpdUpsertIban.request(iban)) -}); - -const mapStateToProps = (state: GlobalState) => ({ - prefillIban: bpdIbanPrefillSelector(state), - onboardingOngoing: isBpdOnboardingOngoing(state) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(IbanInsertionScreen); diff --git a/ts/features/bonus/bpd/screens/iban/ko/IbanKOCannotVerify.tsx b/ts/features/bonus/bpd/screens/iban/ko/IbanKOCannotVerify.tsx deleted file mode 100644 index 544ec726984..00000000000 --- a/ts/features/bonus/bpd/screens/iban/ko/IbanKOCannotVerify.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import * as React from "react"; -import { SafeAreaView } from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import image from "../../../../../../../img/search/search-icon.png"; -import { IOStyles } from "../../../../../../components/core/variables/IOStyles"; -import { renderInfoRasterImage } from "../../../../../../components/infoScreen/imageRendering"; -import { InfoScreenComponent } from "../../../../../../components/infoScreen/InfoScreenComponent"; -import BaseScreenComponent from "../../../../../../components/screens/BaseScreenComponent"; -import I18n from "../../../../../../i18n"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { FooterTwoButtons } from "../../../../../../components/markdown/FooterTwoButtons"; -import { - bpdIbanInsertionContinue, - bpdIbanInsertionResetScreen -} from "../../../store/actions/iban"; -import IbanKoBody from "./IbanKoBody"; - -export type Props = ReturnType & - ReturnType; - -const loadLocales = () => ({ - headerTitle: I18n.t("bonus.bpd.title"), - edit: I18n.t("bonus.bpd.iban.edit"), - continueStr: I18n.t("global.buttons.continue"), - title: I18n.t("bonus.bpd.iban.koCannotVerify.title"), - text1: I18n.t("bonus.bpd.iban.koCannotVerify.text1"), - text2: I18n.t("bonus.bpd.iban.koCannotVerify.text2") -}); - -/** - * This screen warns the user that the provided iban cannot be verified. - * This is just a warning, the user can continue and the iban has been registered on the bpd remote system. - * @constructor - * @deprecated not used anymore, still here just in case of change of mind - */ -const IbanKoCannotVerify: React.FunctionComponent = props => { - const { headerTitle, continueStr, edit, title, text1, text2 } = loadLocales(); - return ( - - - } - /> - - - - ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - modifyIban: () => dispatch(bpdIbanInsertionResetScreen()), - completeInsertion: () => dispatch(bpdIbanInsertionContinue()) -}); - -const mapStateToProps = (_: GlobalState) => ({}); - -export default connect(mapStateToProps, mapDispatchToProps)(IbanKoCannotVerify); diff --git a/ts/features/bonus/bpd/screens/iban/ko/IbanKONotOwned.tsx b/ts/features/bonus/bpd/screens/iban/ko/IbanKONotOwned.tsx deleted file mode 100644 index d5eec253e6e..00000000000 --- a/ts/features/bonus/bpd/screens/iban/ko/IbanKONotOwned.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import * as React from "react"; -import { View, SafeAreaView, StyleSheet } from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import image from "../../../../../../../img/pictograms/doubt.png"; -import { Body } from "../../../../../../components/core/typography/Body"; -import { H4 } from "../../../../../../components/core/typography/H4"; -import { Monospace } from "../../../../../../components/core/typography/Monospace"; -import { IOStyles } from "../../../../../../components/core/variables/IOStyles"; -import { renderInfoRasterImage } from "../../../../../../components/infoScreen/imageRendering"; -import { InfoScreenComponent } from "../../../../../../components/infoScreen/InfoScreenComponent"; -import BaseScreenComponent from "../../../../../../components/screens/BaseScreenComponent"; -import I18n from "../../../../../../i18n"; -import { profileSelector } from "../../../../../../store/reducers/profile"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { FooterTwoButtons } from "../../../../../../components/markdown/FooterTwoButtons"; -import { - bpdIbanInsertionCancel, - bpdIbanInsertionResetScreen -} from "../../../store/actions/iban"; -import IbanKoBody from "./IbanKoBody"; - -export type Props = ReturnType & - ReturnType & - Pick, "contextualHelp">; - -const loadLocales = () => ({ - headerTitle: I18n.t("bonus.bpd.title"), - cancel: I18n.t("global.buttons.cancel"), - verify: I18n.t("bonus.bpd.iban.verify"), - title: I18n.t("bonus.bpd.iban.koNotOwned.title"), - text1: I18n.t("bonus.bpd.iban.koNotOwned.text1"), - text2: I18n.t("bonus.bpd.iban.koNotOwned.text2") -}); - -const styles = StyleSheet.create({ - profile: { - flex: 1, - alignContent: "flex-end" - }, - text: { - textAlign: "center" - } -}); - -/** - * Render the profile information (name, surname, fiscal code) - * @param props - * @constructor - */ -const ProfileInformation = (props: Props) => { - // undefined profile should never happens - const profile = pot.getOrElse(props.profile, undefined); - - return ( - -

- {profile?.name} {profile?.family_name}, -

- - {I18n.t("profile.fiscalCode.fiscalCode")}{" "} - {profile?.fiscal_code} - -
- ); -}; - -/** - * This screen warns the user that the provided iban does not belong to him. - * This is just a warning, the user can continue and iban has been registered on the bpd remote system. - * @constructor - */ -const IbanKoNotOwned: React.FunctionComponent = props => { - const { headerTitle, verify, cancel, title, text1, text2 } = loadLocales(); - return ( - - - - - - - } - /> - - - - ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - modifyIban: () => dispatch(bpdIbanInsertionResetScreen()), - cancel: () => dispatch(bpdIbanInsertionCancel()) -}); - -const mapStateToProps = (state: GlobalState) => ({ - profile: profileSelector(state) -}); - -export default connect(mapStateToProps, mapDispatchToProps)(IbanKoNotOwned); diff --git a/ts/features/bonus/bpd/screens/iban/ko/IbanKOWrong.tsx b/ts/features/bonus/bpd/screens/iban/ko/IbanKOWrong.tsx deleted file mode 100644 index faa06e7f4ae..00000000000 --- a/ts/features/bonus/bpd/screens/iban/ko/IbanKOWrong.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import * as React from "react"; -import { SafeAreaView } from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import image from "../../../../../../../img/servicesStatus/error-detail-icon.png"; -import { IOStyles } from "../../../../../../components/core/variables/IOStyles"; -import { renderInfoRasterImage } from "../../../../../../components/infoScreen/imageRendering"; -import { InfoScreenComponent } from "../../../../../../components/infoScreen/InfoScreenComponent"; -import BaseScreenComponent from "../../../../../../components/screens/BaseScreenComponent"; -import I18n from "../../../../../../i18n"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { FooterTwoButtons } from "../../../../../../components/markdown/FooterTwoButtons"; -import { - bpdIbanInsertionCancel, - bpdIbanInsertionResetScreen -} from "../../../store/actions/iban"; -import IbanKoBody from "./IbanKoBody"; - -export type Props = ReturnType & - ReturnType & - Pick, "contextualHelp">; - -const loadLocales = () => ({ - headerTitle: I18n.t("bonus.bpd.title"), - edit: I18n.t("bonus.bpd.iban.edit"), - title: I18n.t("bonus.bpd.iban.koWrong.title"), - text1: I18n.t("bonus.bpd.iban.koWrong.text1"), - text2: I18n.t("bonus.bpd.iban.koWrong.text2") -}); - -/** - * This screen informs the user that the provided iban is not right and cannot continue. - * @constructor - */ -const IbanKoWrong: React.FunctionComponent = props => { - const { headerTitle, title, text1, text2, edit } = loadLocales(); - return ( - - - } - /> - - - - ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - modifyIban: () => { - dispatch(bpdIbanInsertionResetScreen()); - }, - cancel: () => dispatch(bpdIbanInsertionCancel()) -}); - -const mapStateToProps = (_: GlobalState) => ({}); - -export default connect(mapStateToProps, mapDispatchToProps)(IbanKoWrong); diff --git a/ts/features/bonus/bpd/screens/iban/ko/IbanKoBody.tsx b/ts/features/bonus/bpd/screens/iban/ko/IbanKoBody.tsx deleted file mode 100644 index 8fda0497a35..00000000000 --- a/ts/features/bonus/bpd/screens/iban/ko/IbanKoBody.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import * as React from "react"; -import { View, StyleSheet } from "react-native"; -import { connect } from "react-redux"; -import { VSpacer } from "@pagopa/io-app-design-system"; -import { H4 } from "../../../../../../components/core/typography/H4"; -import { Monospace } from "../../../../../../components/core/typography/Monospace"; -import { IOStyles } from "../../../../../../components/core/variables/IOStyles"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { bpdUpsertIbanSelector } from "../../../store/reducers/details/activation/payoffInstrument"; - -type OwnProps = { - text1: string; - text2: string; - isFlex?: boolean; -}; - -export type Props = OwnProps & ReturnType; - -const styles = StyleSheet.create({ - text: { - textAlign: "center" - } -}); - -/** - * Common rendering of the body for all the Iban KO screens - * @param props - * @constructor - */ -const IbanKoBody: React.FunctionComponent = props => { - const iban: string = props.candidateIban.value ?? ""; - const style = props.isFlex ?? true ? IOStyles.flex : {}; - - return ( - -

- {props.text1} -

- - - {iban} - - -

- {props.text2} -

-
- ); -}; - -const mapStateToProps = (state: GlobalState) => ({ - candidateIban: bpdUpsertIbanSelector(state) -}); - -export default connect(mapStateToProps)(IbanKoBody); diff --git a/ts/features/bonus/bpd/screens/onboarding/BpdInformationScreen.tsx b/ts/features/bonus/bpd/screens/onboarding/BpdInformationScreen.tsx deleted file mode 100644 index 8c397896958..00000000000 --- a/ts/features/bonus/bpd/screens/onboarding/BpdInformationScreen.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import * as React from "react"; -import { connect } from "react-redux"; -import I18n from "../../../../../i18n"; -import { Dispatch } from "../../../../../store/actions/types"; -import { GlobalState } from "../../../../../store/reducers/types"; -import { emptyContextualHelp } from "../../../../../utils/emptyContextualHelp"; -import { actionWithAlert } from "../../../common/components/alert/ActionWithAlert"; -import BonusInformationComponent from "../../../common/components/BonusInformationComponent"; -import { - bpdOnboardingCancel, - bpdUserActivate -} from "../../store/actions/onboarding"; -import { bpdEnabledSelector } from "../../store/reducers/details/activation"; - -export type Props = ReturnType & - ReturnType; - -/** - * This Screen shows all the information about the bpd program, with the rules and t&c. - */ -const BpdInformationScreen: React.FunctionComponent = (props: Props) => { - const onConfirm = () => - pot.getOrElse(props.bpdActiveBonus, false) - ? actionWithAlert({ - title: I18n.t("bonus.bpd.onboarding.alert.title"), - body: I18n.t("bonus.bpd.onboarding.alert.body"), - cancelText: I18n.t("bonus.bpd.onboarding.alert.cancel"), - confirmText: I18n.t("bonus.bpd.onboarding.alert.confirm"), - onConfirmAction: props.onCancel - }) - : props.userActivateBpd(); - - return ( - <> - {props.bonus ? ( - - ) : null} - - ); -}; - -const mapStateToProps = (state: GlobalState) => ({ - bonus: undefined, - bpdActiveBonus: bpdEnabledSelector(state) -}); - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - userActivateBpd: () => dispatch(bpdUserActivate()), - onCancel: () => dispatch(bpdOnboardingCancel()) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(BpdInformationScreen); diff --git a/ts/features/bonus/bpd/screens/onboarding/EnrollPaymentMethodsScreen.tsx b/ts/features/bonus/bpd/screens/onboarding/EnrollPaymentMethodsScreen.tsx deleted file mode 100644 index bf3a6adc246..00000000000 --- a/ts/features/bonus/bpd/screens/onboarding/EnrollPaymentMethodsScreen.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import * as React from "react"; -import { View, SafeAreaView, ScrollView } from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { VSpacer } from "@pagopa/io-app-design-system"; -import { Body } from "../../../../../components/core/typography/Body"; -import { H1 } from "../../../../../components/core/typography/H1"; -import { IOStyles } from "../../../../../components/core/variables/IOStyles"; -import BaseScreenComponent from "../../../../../components/screens/BaseScreenComponent"; -import FooterWithButtons from "../../../../../components/ui/FooterWithButtons"; -import I18n from "../../../../../i18n"; -import { navigateToWalletHome } from "../../../../../store/actions/navigation"; -import { GlobalState } from "../../../../../store/reducers/types"; -import { PaymentMethod } from "../../../../../types/pagopa"; -import { emptyContextualHelp } from "../../../../../utils/emptyContextualHelp"; -import { PaymentMethodGroupedList } from "../../components/paymentMethodActivationToggle/list/PaymentMethodGroupedList"; -import { paymentMethodsWithActivationStatusSelector } from "../../store/reducers/details/combiner"; -import { areAnyPaymentMethodsActiveSelector } from "../../store/reducers/details/paymentMethods"; - -const loadLocales = () => ({ - headerTitle: I18n.t("bonus.bpd.title"), - continueStr: I18n.t("global.buttons.continue"), - skip: I18n.t("global.buttons.skip"), - title: I18n.t("bonus.bpd.onboarding.enrollPaymentMethod.title"), - body1: I18n.t("bonus.bpd.onboarding.enrollPaymentMethod.body1"), - body2: I18n.t("bonus.bpd.onboarding.enrollPaymentMethod.body2") -}); - -export type Props = ReturnType & - ReturnType; - -const renderPaymentMethod = ( - potWallets: pot.Pot, Error> -) => - pot.fold( - potWallets, - // TODO: handle error, loading with spinner if needed - () => null, - () => null, - _ => null, - _ => null, - value => , - value => , - value => , - value => - ); - -/** - * return a two button footer - * left button is enabled when no payment methods are BPD active - * right button button is enabled when at least one payment method is BPD active - * @param props - */ -const getFooter = (props: Props) => { - const { continueStr, skip } = loadLocales(); - const notNowButtonProps = { - primary: false, - bordered: true, - disabled: props.areAnyPaymentMethodsActive, - onPress: props.skip, - title: skip - }; - const continueButtonProps = { - block: true, - primary: true, - disabled: !props.areAnyPaymentMethodsActive, - onPress: props.skip, - title: continueStr - }; - return ( - - ); -}; - -/** - * This screen allows the user to activate bpd on the payment methods already in the wallet - */ -const EnrollPaymentMethodsScreen: React.FunctionComponent = props => { - const { headerTitle, title, body1, body2 } = loadLocales(); - return ( - - - - - -

{title}

- - {body1} - - {renderPaymentMethod(props.potWallets)} - - {body2} -
-
- {getFooter(props)} -
-
- ); -}; - -const mapDispatchToProps = (_: Dispatch) => ({ - skip: () => { - navigateToWalletHome(); - } -}); - -const mapStateToProps = (state: GlobalState) => { - const potWallets = paymentMethodsWithActivationStatusSelector(state); - return { - potWallets, - areAnyPaymentMethodsActive: areAnyPaymentMethodsActiveSelector( - pot.getOrElse(potWallets, []) - )(state) - }; -}; - -export default connect( - mapStateToProps, - mapDispatchToProps -)(EnrollPaymentMethodsScreen); diff --git a/ts/features/bonus/bpd/screens/onboarding/ErrorPaymentMethodsScreen.tsx b/ts/features/bonus/bpd/screens/onboarding/ErrorPaymentMethodsScreen.tsx deleted file mode 100644 index e9fb04cc972..00000000000 --- a/ts/features/bonus/bpd/screens/onboarding/ErrorPaymentMethodsScreen.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import * as React from "react"; -import { View, SafeAreaView, ScrollView, StyleSheet } from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { Icon, VSpacer } from "@pagopa/io-app-design-system"; -import { Body } from "../../../../../components/core/typography/Body"; -import { H2 } from "../../../../../components/core/typography/H2"; -import { IOStyles } from "../../../../../components/core/variables/IOStyles"; -import BaseScreenComponent from "../../../../../components/screens/BaseScreenComponent"; -import FooterWithButtons from "../../../../../components/ui/FooterWithButtons"; -import I18n from "../../../../../i18n"; -import { navigateToWalletHome } from "../../../../../store/actions/navigation"; -import { GlobalState } from "../../../../../store/reducers/types"; -import { emptyContextualHelp } from "../../../../../utils/emptyContextualHelp"; -import { confirmButtonProps } from "../../../../../components/buttons/ButtonConfigurations"; - -export type Props = ReturnType & - ReturnType; - -const styles = StyleSheet.create({ - center: { textAlign: "center" } -}); - -const loadLocales = () => ({ - headerTitle: I18n.t("bonus.bpd.title"), - title: I18n.t("bonus.bpd.onboarding.errorPaymentMethod.title"), - body: I18n.t("bonus.bpd.onboarding.errorPaymentMethod.body"), - cta: I18n.t("bonus.bpd.onboarding.errorPaymentMethod.cta") -}); - -const ErrorPaymentMethodsScreen: React.FunctionComponent = props => { - const { headerTitle, title, body, cta } = loadLocales(); - return ( - - - - {/* TODO: Refactor the following screen because you can't - use huge spacers to center the screen content. - You have to think about position logic. */} - - - - - -

{title}

- - {body} -
-
- -
-
- ); -}; - -const mapDispatchToProps = (_: Dispatch) => ({ - skip: () => { - navigateToWalletHome(); - } -}); - -const mapStateToProps = (_: GlobalState) => ({}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(ErrorPaymentMethodsScreen); diff --git a/ts/features/bonus/bpd/screens/onboarding/LoadActivateBpdScreen.tsx b/ts/features/bonus/bpd/screens/onboarding/LoadActivateBpdScreen.tsx deleted file mode 100644 index 343de78761f..00000000000 --- a/ts/features/bonus/bpd/screens/onboarding/LoadActivateBpdScreen.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import * as React from "react"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import I18n from "../../../../../i18n"; -import { GlobalState } from "../../../../../store/reducers/types"; -import { useHardwareBackButton } from "../../../../../hooks/useHardwareBackButton"; -import { LoadingErrorComponent } from "../../../../../components/LoadingErrorComponent"; -import { isError } from "../../../../../common/model/RemoteValue"; -import { - bpdOnboardingAcceptDeclaration, - bpdOnboardingCancel -} from "../../store/actions/onboarding"; -import { bpdEnrollSelector } from "../../store/reducers/onboarding/enroll"; - -type Props = ReturnType & - ReturnType; - -/** - * This screen is displayed during the activation of the bpd program for the user - */ -const LoadActivateBpdScreen: React.FunctionComponent = props => { - const loadingCaption = - I18n.t("bonus.bpd.onboarding.loadingActivateBpd.title") + - "\n" + - I18n.t("bonus.bpd.onboarding.loadingActivateBpd.body"); - - useHardwareBackButton(() => { - if (!props.isLoading) { - props.onAbort(); - } - return true; - }); - - return ( - - ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - onAbort: () => dispatch(bpdOnboardingCancel()), - onRetry: () => { - dispatch(bpdOnboardingAcceptDeclaration()); - } -}); - -const mapStateToProps = (globalState: GlobalState) => ({ - // display the error with the retry only in case of networking errors - isLoading: !isError(bpdEnrollSelector(globalState)) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(LoadActivateBpdScreen); diff --git a/ts/features/bonus/bpd/screens/onboarding/LoadBpdActivationStatus.tsx b/ts/features/bonus/bpd/screens/onboarding/LoadBpdActivationStatus.tsx deleted file mode 100644 index 7c60be9d180..00000000000 --- a/ts/features/bonus/bpd/screens/onboarding/LoadBpdActivationStatus.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import * as React from "react"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import I18n from "../../../../../i18n"; -import { GlobalState } from "../../../../../store/reducers/types"; -import { useHardwareBackButton } from "../../../../../hooks/useHardwareBackButton"; -import { LoadingErrorComponent } from "../../../../../components/LoadingErrorComponent"; -import { - bpdOnboardingCancel, - bpdOnboardingStart -} from "../../store/actions/onboarding"; -import { bpdEnabledSelector } from "../../store/reducers/details/activation"; - -type Props = ReturnType & - ReturnType; - -/** - * This screen is displayed when the user start the onboarding flow but the application does not know yet if the user - * is already registered to the bpd service - * @constructor - */ - -const LoadBpdActivationStatus: React.FunctionComponent = props => { - const loadingCaption = I18n.t( - "bonus.bpd.onboarding.loadingActivationStatus.title" - ); - - useHardwareBackButton(() => { - if (!props.isLoading) { - props.onAbort(); - } - return true; - }); - - return ( - - ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - onAbort: () => dispatch(bpdOnboardingCancel()), - onRetry: () => { - dispatch(bpdOnboardingStart()); - } -}); - -const mapStateToProps = (globalState: GlobalState) => ({ - // display the error with the retry only in case of networking errors - isLoading: !pot.isError(bpdEnabledSelector(globalState)) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(LoadBpdActivationStatus); diff --git a/ts/features/bonus/bpd/screens/onboarding/NoPaymentMethodsAvailableScreen.tsx b/ts/features/bonus/bpd/screens/onboarding/NoPaymentMethodsAvailableScreen.tsx deleted file mode 100644 index 7c0dc2be012..00000000000 --- a/ts/features/bonus/bpd/screens/onboarding/NoPaymentMethodsAvailableScreen.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import * as O from "fp-ts/lib/Option"; -import * as React from "react"; -import { View, SafeAreaView } from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { VSpacer } from "@pagopa/io-app-design-system"; -import { Body } from "../../../../../components/core/typography/Body"; -import { H1 } from "../../../../../components/core/typography/H1"; -import { IOStyles } from "../../../../../components/core/variables/IOStyles"; -import BaseScreenComponent from "../../../../../components/screens/BaseScreenComponent"; -import I18n from "../../../../../i18n"; -import { - navigateToWalletAddPaymentMethod, - navigateToWalletHome -} from "../../../../../store/actions/navigation"; -import { GlobalState } from "../../../../../store/reducers/types"; -import { emptyContextualHelp } from "../../../../../utils/emptyContextualHelp"; -import { FooterTwoButtons } from "../../../../../components/markdown/FooterTwoButtons"; - -export type Props = ReturnType & - ReturnType; - -const loadLocales = () => ({ - headerTitle: I18n.t("bonus.bpd.title"), - skip: I18n.t("global.buttons.skip"), - addMethod: I18n.t("wallet.newPaymentMethod.addButton"), - title: I18n.t("bonus.bpd.onboarding.noPaymentMethod.title"), - body: I18n.t("bonus.bpd.onboarding.noPaymentMethod.body") -}); - -const NoPaymentMethodsAvailableScreen: React.FunctionComponent< - Props -> = props => { - const { headerTitle, skip, addMethod, title, body } = loadLocales(); - return ( - - - - -

{title}

- - {body} -
- -
-
- ); -}; - -const mapDispatchToProps = (_: Dispatch) => ({ - skip: () => { - navigateToWalletHome(); - }, - addPaymentMethod: () => { - navigateToWalletHome(); - navigateToWalletAddPaymentMethod({ inPayment: O.none }); - } -}); - -const mapStateToProps = (_: GlobalState) => ({}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(NoPaymentMethodsAvailableScreen); diff --git a/ts/features/bonus/bpd/screens/onboarding/declaration/DeclarationComponent.tsx b/ts/features/bonus/bpd/screens/onboarding/declaration/DeclarationComponent.tsx deleted file mode 100644 index a9c79a05606..00000000000 --- a/ts/features/bonus/bpd/screens/onboarding/declaration/DeclarationComponent.tsx +++ /dev/null @@ -1,144 +0,0 @@ -import * as React from "react"; -import { useReducer } from "react"; -import { View, SafeAreaView, ScrollView } from "react-native"; -import { VSpacer } from "@pagopa/io-app-design-system"; -import { InfoBox } from "../../../../../../components/box/InfoBox"; -import { Body } from "../../../../../../components/core/typography/Body"; -import { H1 } from "../../../../../../components/core/typography/H1"; -import { Label } from "../../../../../../components/core/typography/Label"; -import { Link } from "../../../../../../components/core/typography/Link"; -import { IOStyles } from "../../../../../../components/core/variables/IOStyles"; -import BaseScreenComponent from "../../../../../../components/screens/BaseScreenComponent"; -import I18n from "../../../../../../i18n"; -import { openWebUrl } from "../../../../../../utils/url"; -import { FooterTwoButtons } from "../../../../../../components/markdown/FooterTwoButtons"; -import { dpr28Dec2000Url } from "../../../../../../urls"; -import { DeclarationEntry } from "./DeclarationEntry"; - -type OwnProps = { - onCancel: () => void; - onConfirm: () => void; -}; - -type Props = OwnProps & - Pick, "contextualHelp">; -type NormalBold = { - normal: string; - bold: string; -}; - -type InnerAction = "increment" | "decrement"; - -const loadLocales = () => ({ - title: I18n.t("bonus.bpd.title"), - header: I18n.t("bonus.bpd.onboarding.declaration.header"), - age: I18n.t("bonus.bpd.onboarding.declaration.conditions.age"), - owner: { - normal: I18n.t("bonus.bpd.onboarding.declaration.conditions.owner.normal"), - bold: I18n.t("bonus.bpd.onboarding.declaration.conditions.owner.bold") - }, - resident: I18n.t("bonus.bpd.onboarding.declaration.conditions.resident"), - personal_use: { - normal: I18n.t( - "bonus.bpd.onboarding.declaration.conditions.personal_use.normal" - ), - bold: I18n.t( - "bonus.bpd.onboarding.declaration.conditions.personal_use.bold" - ) - }, - disclaimer: { - normal1: I18n.t("bonus.bpd.onboarding.declaration.disclaimer.normal1"), - link: I18n.t("bonus.bpd.onboarding.declaration.disclaimer.link"), - normal2: I18n.t("bonus.bpd.onboarding.declaration.disclaimer.normal2") - }, - cta: I18n.t("bonus.bpd.onboarding.declaration.cta") -}); - -/** - * Need a specific rendering of this component because have some bold parts - * @param personalUse - */ -const normalBoldText = (personalUse: NormalBold) => ( - - {personalUse.normal} - - -); - -const generateRequiredConditions = ( - list: ReadonlyArray, - dispatch: React.Dispatch -) => - list.map((condition, index) => ( - dispatch(newValue ? "increment" : "decrement")} - /> - )); - -function reducer(state: number, action: InnerAction) { - switch (action) { - case "increment": - return state + 1; - case "decrement": - return state - 1; - default: - throw new Error(); - } -} - -/** - * This screen allows the user to declare the required conditions. - * When all the condition are accepted, the continue button will be enabled - */ -export const DeclarationComponent: React.FunctionComponent = props => { - const { title, header, age, owner, resident, personal_use, disclaimer, cta } = - loadLocales(); - - // tracks the condition accepted, used to enabled the "continue" button - const [state, dispatch] = useReducer(reducer, 0); - - // transform the required textual conditions to graphical objects with checkbox - const requiredConditions = [ - age, - resident, - normalBoldText(owner), - normalBoldText(personal_use) - ]; - - return ( - - - - - -

{header}

- - {generateRequiredConditions(requiredConditions, dispatch)} - - - - {disclaimer.normal1} - openWebUrl(dpr28Dec2000Url)}> - {disclaimer.link} - - {disclaimer.normal2} - - -
-
- -
-
- ); -}; diff --git a/ts/features/bonus/bpd/screens/onboarding/declaration/DeclarationScreen.tsx b/ts/features/bonus/bpd/screens/onboarding/declaration/DeclarationScreen.tsx deleted file mode 100644 index c359f17680e..00000000000 --- a/ts/features/bonus/bpd/screens/onboarding/declaration/DeclarationScreen.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import * as React from "react"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { emptyContextualHelp } from "../../../../../../utils/emptyContextualHelp"; -import { - bpdOnboardingAcceptDeclaration, - bpdOnboardingCancel -} from "../../../store/actions/onboarding"; -import { DeclarationComponent } from "./DeclarationComponent"; - -export type Props = ReturnType; - -/** - * This screen allows the user to declare the required conditions - */ - -const DeclarationScreen: React.FunctionComponent = props => ( - -); - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - onCancel: () => dispatch(bpdOnboardingCancel()), - userAcceptDeclaration: () => dispatch(bpdOnboardingAcceptDeclaration()) -}); - -export default connect(undefined, mapDispatchToProps)(DeclarationScreen); diff --git a/ts/features/bonus/bpd/screens/optInPaymentMethods/OptInPaymentMethodsCashbackUpdateScreen.tsx b/ts/features/bonus/bpd/screens/optInPaymentMethods/OptInPaymentMethodsCashbackUpdateScreen.tsx deleted file mode 100644 index e7ea628da1b..00000000000 --- a/ts/features/bonus/bpd/screens/optInPaymentMethods/OptInPaymentMethodsCashbackUpdateScreen.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import { VSpacer } from "@pagopa/io-app-design-system"; -import React from "react"; -import { SafeAreaView, ScrollView, StyleSheet, View } from "react-native"; -import ButtonDefaultOpacity from "../../../../../components/ButtonDefaultOpacity"; -import { confirmButtonProps } from "../../../../../components/buttons/ButtonConfigurations"; -import { IORenderHtml } from "../../../../../components/core/IORenderHtml"; -import { H1 } from "../../../../../components/core/typography/H1"; -import { IOStyles } from "../../../../../components/core/variables/IOStyles"; -import BaseScreenComponent from "../../../../../components/screens/BaseScreenComponent"; -import FooterWithButtons from "../../../../../components/ui/FooterWithButtons"; -import I18n from "../../../../../i18n"; -import { useIODispatch, useIOSelector } from "../../../../../store/hooks"; -import { - getBPDMethodsSelector, - paymentMethodsSelector -} from "../../../../../store/reducers/wallet/wallets"; -import { - optInPaymentMethodsCompleted, - optInPaymentMethodsFailure -} from "../../store/actions/optInPaymentMethods"; - -const styles = StyleSheet.create({ - headerContainer: { - ...IOStyles.row, - justifyContent: "space-between" - } -}); -const OptInPaymentMethodsCashbackUpdateScreen = () => { - const dispatch = useIODispatch(); - const bpdPaymentMethods = useIOSelector(getBPDMethodsSelector); - const paymentMethods = useIOSelector(paymentMethodsSelector); - - // This screen should be shown only if the payment method are correctly loaded - if (paymentMethods.kind !== "PotSome") { - dispatch(optInPaymentMethodsFailure("error in the payment method loading")); - } - - const handleOnContinuePress = () => { - if (bpdPaymentMethods.length > 0) { - throw new Error("bpdPaymentMethods should be empty"); - } else { - dispatch(optInPaymentMethodsCompleted()); - } - }; - - return ( - // The void customGoBack is needed to have a centered header title - true} transparent={true} /> - } - > - - - - -

- {I18n.t("bonus.bpd.optInPaymentMethods.cashbackUpdate.title")} -

-
-
- - -
- -
-
- ); -}; - -export default OptInPaymentMethodsCashbackUpdateScreen; diff --git a/ts/features/bonus/bpd/screens/optInPaymentMethods/OptInPaymentMethodsChoiceScreen.tsx b/ts/features/bonus/bpd/screens/optInPaymentMethods/OptInPaymentMethodsChoiceScreen.tsx deleted file mode 100644 index 1edc207743a..00000000000 --- a/ts/features/bonus/bpd/screens/optInPaymentMethods/OptInPaymentMethodsChoiceScreen.tsx +++ /dev/null @@ -1,206 +0,0 @@ -import { useNavigation } from "@react-navigation/native"; -import React, { useEffect, useMemo, useRef, useState } from "react"; -import { SafeAreaView, ScrollView, View } from "react-native"; -import { VSpacer } from "@pagopa/io-app-design-system"; -import ButtonDefaultOpacity from "../../../../../components/ButtonDefaultOpacity"; -import { IOBadge } from "../../../../../components/core/IOBadge"; -import { - RadioButtonList, - RadioItem -} from "../../../../../components/core/selection/RadioButtonList"; -import { Body } from "../../../../../components/core/typography/Body"; -import { H1 } from "../../../../../components/core/typography/H1"; -import { H4 } from "../../../../../components/core/typography/H4"; -import { LabelSmall } from "../../../../../components/core/typography/LabelSmall"; -import { IOStyles } from "../../../../../components/core/variables/IOStyles"; -import LoadingSpinnerOverlay from "../../../../../components/LoadingSpinnerOverlay"; -import BaseScreenComponent from "../../../../../components/screens/BaseScreenComponent"; -import { BlockButtonProps } from "../../../../../components/ui/BlockButtons"; -import FooterWithButtons from "../../../../../components/ui/FooterWithButtons"; -import I18n from "../../../../../i18n"; -import { useIODispatch, useIOSelector } from "../../../../../store/hooks"; -import { showToast } from "../../../../../utils/showToast"; -import { - confirmButtonProps, - disablePrimaryButtonProps, - errorBorderedButtonProps -} from "../../../../../components/buttons/ButtonConfigurations"; -import { useBottomSheetMethodsToDelete } from "../../components/optInStatus/BottomSheetMethodsToDelete"; -import { - isError, - isReady, - isUndefined -} from "../../../../../common/model/RemoteValue"; -import { optInPaymentMethodsShowChoice } from "../../store/actions/optInPaymentMethods"; -import { showOptInChoiceSelector } from "../../store/reducers/details/activation/ui"; - -type PaymentMethodsChoiceOptions = - | "keepPaymentMethods" - | "deletePaymentMethods"; - -const generateOptionBody = ( - title: string, - body: string, - showBadge?: boolean -) => ( - <> - {showBadge && ( - - - - - )} -

{title}

- - {body} - - -); - -const radioButtonListItems: () => ReadonlyArray< - RadioItem -> = () => [ - { - id: "keepPaymentMethods", - body: generateOptionBody( - I18n.t("bonus.bpd.optInPaymentMethods.choice.options.keepAll.title"), - I18n.t("bonus.bpd.optInPaymentMethods.choice.options.keepAll.body"), - true - ) - }, - { - id: "deletePaymentMethods", - body: generateOptionBody( - I18n.t("bonus.bpd.optInPaymentMethods.choice.options.deleteAll.title"), - I18n.t("bonus.bpd.optInPaymentMethods.choice.options.deleteAll.body") - ) - } -]; - -const disabledButtonProps = () => - disablePrimaryButtonProps( - I18n.t("global.buttons.continue"), - undefined, - "disabledContinueButton" - ); - -const OptInPaymentMethodsChoiceScreen = () => { - const [selectedMethod, setSelectedMethod] = - useState(null); - const navigation = useNavigation(); - const dispatch = useIODispatch(); - const showOptInChoice = useIOSelector(showOptInChoiceSelector); - - const renderingRef = useRef(false); - - const { presentBottomSheet, bottomSheet } = useBottomSheetMethodsToDelete({ - onDeletePress: () => { - throw new Error(); - } - }); - - const bottomButtons: { - [key in PaymentMethodsChoiceOptions]: BlockButtonProps; - } = useMemo( - () => ({ - keepPaymentMethods: confirmButtonProps( - () => { - throw new Error(); - }, - I18n.t("global.buttons.continue"), - undefined, - "continueButton" - ), - - deletePaymentMethods: errorBorderedButtonProps( - presentBottomSheet, - I18n.t("bonus.bpd.optInPaymentMethods.choice.deleteAllButton"), - undefined - ) - }), - [presentBottomSheet] - ); - - const computedBottomButtonProps = useMemo( - () => - selectedMethod === null - ? disabledButtonProps() - : bottomButtons[selectedMethod], - [selectedMethod, bottomButtons] - ); - - useEffect(() => { - if (isUndefined(showOptInChoice)) { - dispatch(optInPaymentMethodsShowChoice.request()); - } - if ( - isError(showOptInChoice) || - (isReady(showOptInChoice) && !showOptInChoice.value) - ) { - navigation.goBack(); - if (isError(showOptInChoice)) { - showToast(I18n.t("global.genericError")); - } - if (!renderingRef.current) { - showToast( - I18n.t("bonus.bpd.optInPaymentMethods.choice.toast"), - "warning" - ); - } - } - - // eslint-disable-next-line functional/immutable-data - renderingRef.current = true; - }, [dispatch, showOptInChoice, navigation]); - - return ( - - {/* The void customGoBack are needed to have a centered header title */} - true} transparent={true} /> - } - > - - -

{I18n.t("bonus.bpd.optInPaymentMethods.choice.title")}

- - - {I18n.t("bonus.bpd.optInPaymentMethods.choice.subtitle")} - - - - - items={radioButtonListItems()} - selectedItem={selectedMethod || undefined} - onPress={setSelectedMethod} - rightSideSelection - /> -
- -
- {bottomSheet} -
-
- ); -}; - -export default OptInPaymentMethodsChoiceScreen; diff --git a/ts/features/bonus/bpd/screens/optInPaymentMethods/OptInPaymentMethodsThankYouDeleteMethodsScreen.tsx b/ts/features/bonus/bpd/screens/optInPaymentMethods/OptInPaymentMethodsThankYouDeleteMethodsScreen.tsx deleted file mode 100644 index fb4241a6ddf..00000000000 --- a/ts/features/bonus/bpd/screens/optInPaymentMethods/OptInPaymentMethodsThankYouDeleteMethodsScreen.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { useNavigation } from "@react-navigation/native"; -import React, { useEffect } from "react"; -import { useDispatch } from "react-redux"; -import I18n from "../../../../../i18n"; -import ROUTES from "../../../../../navigation/routes"; -import { useIOSelector } from "../../../../../store/hooks"; -import { deleteAllPaymentMethodsByFunctionSelector } from "../../../../../store/reducers/wallet/wallets"; -import { showToast } from "../../../../../utils/showToast"; -import { LoadingErrorComponent } from "../../../../../components/LoadingErrorComponent"; -import RetryAfterDeletionFailsComponent from "../../components/optInPaymentMethods/RetryAfterDeletionFailsComponent"; -import ThankYouSuccessComponent from "../../components/optInPaymentMethods/ThankYouSuccessComponent"; -import { - isError, - isLoading, - isReady, - isUndefined -} from "../../../../../common/model/RemoteValue"; -import { optInPaymentMethodsDeletionChoice } from "../../store/actions/optInPaymentMethods"; -import { optInStatusSelector } from "../../store/reducers/details/activation"; - -const OptInPaymentMethodsThankYouDeleteMethodsScreen = () => { - const dispatch = useDispatch(); - const navigation = useNavigation(); - const deleteAllPaymentMethodsByFunctionStatus = useIOSelector( - deleteAllPaymentMethodsByFunctionSelector - ); - const optInStatus = useIOSelector(optInStatusSelector); - - useEffect(() => { - // dispatch saga that delete payment methods and update opt-in field - dispatch(optInPaymentMethodsDeletionChoice()); - }, [dispatch]); - - useEffect(() => { - // if the opt-in choice fails complete the workunit and show an error toast to the user - if ( - pot.isError(optInStatus) && - isReady(deleteAllPaymentMethodsByFunctionStatus) - ) { - showToast(I18n.t("bonus.bpd.optInPaymentMethods.thankYouPage.toast")); - navigation.navigate(ROUTES.WALLET_HOME); - } - }, [ - optInStatus, - deleteAllPaymentMethodsByFunctionStatus, - dispatch, - navigation - ]); - - const isOptInStatusLoading = pot.fold( - optInStatus, - () => true, - () => true, - _ => true, - _ => false, - _ => false, - _ => true, - _ => true, - _ => false - ); - // if the payment methods deletion fails show the retry component - if (isError(deleteAllPaymentMethodsByFunctionStatus)) { - return ; - } - - // if one between deleteAllPaymentMethodsByFunctionStatus and optInStatus is on loading (or undefined) state show loading component - if ( - isUndefined(deleteAllPaymentMethodsByFunctionStatus) || - isLoading(deleteAllPaymentMethodsByFunctionStatus) || - isOptInStatusLoading - ) { - return ( - true} - testID={"loadingComponent"} - /> - ); - } - - if (pot.isError(optInStatus)) { - return null; - } - // both the payment methods deletion and the opt-in update succeed, show the thank you page - return ; -}; - -export default OptInPaymentMethodsThankYouDeleteMethodsScreen; diff --git a/ts/features/bonus/bpd/screens/optInPaymentMethods/OptInPaymentMethodsThankYouKeepMethodsScreen.tsx b/ts/features/bonus/bpd/screens/optInPaymentMethods/OptInPaymentMethodsThankYouKeepMethodsScreen.tsx deleted file mode 100644 index b4fcb82e6f4..00000000000 --- a/ts/features/bonus/bpd/screens/optInPaymentMethods/OptInPaymentMethodsThankYouKeepMethodsScreen.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { useNavigation } from "@react-navigation/native"; -import React, { useEffect } from "react"; -import { useDispatch } from "react-redux"; -import { CitizenOptInStatusEnum } from "../../../../../../definitions/bpd/citizen_v2/CitizenOptInStatus"; -import I18n from "../../../../../i18n"; -import ROUTES from "../../../../../navigation/routes"; -import { useIOSelector } from "../../../../../store/hooks"; -import { showToast } from "../../../../../utils/showToast"; -import { LoadingErrorComponent } from "../../../../../components/LoadingErrorComponent"; -import ThankYouSuccessComponent from "../../components/optInPaymentMethods/ThankYouSuccessComponent"; -import { bpdUpdateOptInStatusMethod } from "../../store/actions/onboarding"; -import { optInStatusSelector } from "../../store/reducers/details/activation"; - -const OptInPaymentMethodsThankYouKeepMethodsScreen = () => { - const dispatch = useDispatch(); - const navigation = useNavigation(); - const optInStatus = useIOSelector(optInStatusSelector); - - useEffect(() => { - // dispatch saga that delete payment methods and update opt-in field - dispatch( - bpdUpdateOptInStatusMethod.request(CitizenOptInStatusEnum.ACCEPTED) - ); - }, [dispatch]); - - useEffect(() => { - // if the opt-in choice fails complete the workunit and show an error toast to the user - if (pot.isError(optInStatus)) { - showToast(I18n.t("bonus.bpd.optInPaymentMethods.thankYouPage.toast")); - navigation.navigate(ROUTES.WALLET_HOME); - } - }, [optInStatus, dispatch, navigation]); - - const isOptInStatusLoading = pot.fold( - optInStatus, - () => true, - () => true, - _ => true, - _ => false, - _ => false, - _ => true, - _ => true, - _ => false - ); - - // if optInStatus is on loading (or undefined) state show loading component - if (isOptInStatusLoading) { - return ( - true} - testID={"loadingComponent"} - /> - ); - } - - if (pot.isError(optInStatus)) { - return null; - } - - // the opt-in update succeed, show the thank you page - return ; -}; - -export default OptInPaymentMethodsThankYouKeepMethodsScreen; diff --git a/ts/features/bonus/bpd/store/actions/details.ts b/ts/features/bonus/bpd/store/actions/details.ts deleted file mode 100644 index deed80aa2b0..00000000000 --- a/ts/features/bonus/bpd/store/actions/details.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { ActionType, createAsyncAction } from "typesafe-actions"; -import { CitizenOptInStatusEnum } from "../../../../../../definitions/bpd/citizen_v2/CitizenOptInStatus"; - -/** - * This file contains all the action related to the bpd details like the activation status, value, etc. - */ - -export type ActivationStatus = "never" | "unsubscribed" | "subscribed"; -// TODO change payload for loadBpdActivationStatus with this one -export type BpdActivationPayload = { - enabled: boolean; - activationStatus: ActivationStatus; - payoffInstr: string | undefined; - optInStatus?: CitizenOptInStatusEnum; - technicalAccount?: string; -}; - -/** - * Request the activation status for the user to the bpd program - */ -export const bpdLoadActivationStatus = createAsyncAction( - "BPD_LOAD_ACTIVATION_STATUS_REQUEST", - "BPD_LOAD_ACTIVATION_STATUS_SUCCESS", - "BPD_LOAD_ACTIVATION_STATUS_FAILURE" -)(); - -export const bpdAllData = createAsyncAction( - "BPD_ALL_DATA_REQUEST", - "BPD_ALL_DATA_SUCCESS", - "BPD_ALL_DATA_FAILURE" -)(); - -export type BpdDetailsActions = - | ActionType - | ActionType; diff --git a/ts/features/bonus/bpd/store/actions/iban.ts b/ts/features/bonus/bpd/store/actions/iban.ts deleted file mode 100644 index 28c08ec9377..00000000000 --- a/ts/features/bonus/bpd/store/actions/iban.ts +++ /dev/null @@ -1,54 +0,0 @@ -// represent the outcome of iban upsert -import { - ActionType, - createAsyncAction, - createStandardAction -} from "typesafe-actions"; -import { Iban } from "../../../../../../definitions/backend/Iban"; -import { IbanStatus } from "../../saga/networking/patchCitizenIban"; - -/** - * This file contains all the action related to the iban insertion / change workflow. - */ - -export type IbanUpsertResult = { payoffInstr?: Iban; status: IbanStatus }; -/** - * Request the upsert of the citizen iban - */ -export const bpdUpsertIban = createAsyncAction( - "BPD_UPSERT_IBAN_REQUEST", - "BPD_UPSERT_IBAN_SUCCESS", - "BPD_UPSERT_IBAN_FAILURE" -)(); - -/** - * The action that triggers the start of insertion / modification of the IBAN - */ -export const bpdIbanInsertionStart = createStandardAction( - "BPD_IBAN_INSERTION_START" -)(); - -/** - * The user choose to go forward and terminate the iban insertion - */ -export const bpdIbanInsertionContinue = createStandardAction( - "BPD_IBAN_INSERTION_CONTINUE" -)(); - -/** - * The user choose to cancel the iban insertion - */ -export const bpdIbanInsertionCancel = createStandardAction( - "BPD_IBAN_INSERTION_CANCEL" -)(); - -export const bpdIbanInsertionResetScreen = createStandardAction( - "BPD_IBAN_INSERTION_RESET_SCREEN" -)(); - -export type BpdIbanActions = - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType; diff --git a/ts/features/bonus/bpd/store/actions/index.ts b/ts/features/bonus/bpd/store/actions/index.ts deleted file mode 100644 index 2276e91ae2a..00000000000 --- a/ts/features/bonus/bpd/store/actions/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { BpdDetailsActions } from "./details"; -import { BpdIbanActions } from "./iban"; -import { BpdOnboardingActions } from "./onboarding"; -import { BpdPaymentMethodActions } from "./paymentMethods"; -import { BpdPeriodsAction } from "./periods"; -import { BpdSelectPeriodAction } from "./selectedPeriod"; -import { BpdTransactionsAction } from "./transactions"; -import { OptInPaymentMethodsActions } from "./optInPaymentMethods"; - -export type BpdActions = - | BpdOnboardingActions - | BpdDetailsActions - | BpdIbanActions - | BpdPaymentMethodActions - | BpdPeriodsAction - | BpdTransactionsAction - | BpdSelectPeriodAction - | OptInPaymentMethodsActions; diff --git a/ts/features/bonus/bpd/store/actions/onboarding.ts b/ts/features/bonus/bpd/store/actions/onboarding.ts deleted file mode 100644 index 17f6e59e11b..00000000000 --- a/ts/features/bonus/bpd/store/actions/onboarding.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { - ActionType, - createAsyncAction, - createStandardAction -} from "typesafe-actions"; -import { BpdActivationPayload } from "./details"; - -/** - * Enroll the user to the bpd program - */ -// TODO: change CitizenResource with boolean? -export const bpdEnrollUserToProgram = createAsyncAction( - "BPD_ENROLL_REQUEST", - "BPD_ENROLL_SUCCESS", - "BPD_ENROLL_FAILURE" -)(); - -/** - * update the optInStatus of an enrolled citizen - */ -export const bpdUpdateOptInStatusMethod = createAsyncAction( - "BPD_UPDATE_OPT_IN_STATUS_REQUEST", - "BPD_UPDATE_OPT_IN_STATUS_SUCCESS", - "BPD_UPDATE_OPT_IN_STATUS_FAILURE" -)< - NonNullable, - NonNullable, - Error ->(); - -/** - * Delete user from bpd program - */ -export const bpdDeleteUserFromProgram = createAsyncAction( - "BPD_DELETE_REQUEST", - "BPD_DELETE_SUCCESS", - "BPD_DELETE_FAILURE" -)(); - -/** - * The user ends the unsubscribe workflow - */ -export const bpdUnsubscribeCompleted = createStandardAction( - "BPD_UNSUBSCRIBE_COMPLETED" -)(); - -/** - * The user complete the unsubscribe workflow with a failure - */ -export const bpdUnsubscribeFailure = createStandardAction( - "BPD_UNSUBSCRIBE_COMPLETED_WITH_FAILURE" -)(); - -/** - * Start the onboarding workflow - */ -export const bpdOnboardingStart = createStandardAction( - "BPD_ONBOARDING_START" -)(); - -/** - * The user choose to activate the bpd - */ -export const bpdUserActivate = createStandardAction( - "BPD_ONBOARDING_USER_ACTIVATE" -)(); - -/** - * Cancel the onboarding workflow - */ -export const bpdOnboardingCancel = createStandardAction( - "BPD_ONBOARDING_CANCEL" -)(); - -/** - * The user completed the final step of the onboarding (choose to add iban) - */ -export const bpdOnboardingCompleted = createStandardAction( - "BPD_ONBOARDING_COMPLETED" -)(); - -/** - * The user accepts and confirms the declaration - */ -export const bpdOnboardingAcceptDeclaration = createStandardAction( - "BPD_ONBOARDING_ACCEPT_DECLARATION" -)(); - -export type BpdOnboardingActions = - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType; diff --git a/ts/features/bonus/bpd/store/actions/optInPaymentMethods.ts b/ts/features/bonus/bpd/store/actions/optInPaymentMethods.ts deleted file mode 100644 index 0a7bdb5d761..00000000000 --- a/ts/features/bonus/bpd/store/actions/optInPaymentMethods.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { - ActionType, - createAsyncAction, - createStandardAction -} from "typesafe-actions"; - -/** - * The user starts the workflow for making a decision regarding the opt-in of payment methods - */ -export const optInPaymentMethodsStart = createStandardAction( - "OPT_IN_PAYMENT_METHODS_START" -)(); -/** - * The user completes the workflow for making a decision regarding the opt-in of payment methods - */ -export const optInPaymentMethodsCompleted = createStandardAction( - "OPT_IN_PAYMENT_METHODS_COMPLETED" -)(); - -/** - * The user can't choose to cancel the opt-in choice, this action is needed for the workunit - */ -export const optInPaymentMethodsCancel = createStandardAction( - "OPT_IN_PAYMENT_METHODS_CANCEL" -)(); - -/** - * The user can't choose to press the back button, this action is needed for the workunit - */ -export const optInPaymentMethodsBack = createStandardAction( - "OPT_IN_PAYMENT_METHODS_BACK" -)(); - -/** - * The workflow fails - */ -export const optInPaymentMethodsFailure = createStandardAction( - "OPT_IN_PAYMENT_METHODS_FAILURE" -)(); - -/** - * Triggers the saga that deletes the user's payment methods and update the opt-in choice - */ -export const optInPaymentMethodsDeletionChoice = createStandardAction( - "OPT_IN_PAYMENT_METHODS_DELETION_CHOICE" -)(); - -export const optInPaymentMethodsShowChoice = createAsyncAction( - "OPT_IN_PAYMENT_METHODS_SHOW_CHOICE_REQUEST", - "OPT_IN_PAYMENT_METHODS_SHOW_CHOICE_SUCCESS", - "OPT_IN_PAYMENT_METHODS_SHOW_CHOICE_FAILURE" -)(); - -export type OptInPaymentMethodsActions = - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType; diff --git a/ts/features/bonus/bpd/store/actions/paymentMethods.ts b/ts/features/bonus/bpd/store/actions/paymentMethods.ts deleted file mode 100644 index 862132f1102..00000000000 --- a/ts/features/bonus/bpd/store/actions/paymentMethods.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { IUnitTag } from "@pagopa/ts-commons/lib/units"; -import { ActionType, createAsyncAction } from "typesafe-actions"; - -// Temp type to ensure that only HPan from walled are used to query the bpd pm activation -export type HPan = string & IUnitTag<"HPan">; - -/** - * The possible bpd activation status on a payment method - * - active: bpd is active on the payment method - * - inactive: bpd is not active on the payment method - * - notActivable: bpd activate on the payment method by someone else, cannot be activated - */ -export type BpdPmActivationStatus = "active" | "inactive" | "notActivable"; - -/** - * The response payload that represents the actual state of bpd on a payment method - */ -export type BpdPaymentMethodActivation = { - hPan: HPan; - activationStatus: BpdPmActivationStatus; - activationDate?: string; - deactivationDate?: string; -}; - -/** - * The failure payload when trying to know the actual state or update bpd on a payment method - */ -type BpdPaymentMethodFailure = { - hPan: HPan; - error: Error; -}; - -/** - * The request payload to update the activation status on a payment method - */ -type BpdUpdatePaymentMethodActivationPayload = { - hPan: HPan; - value: boolean; -}; - -/** - * The remote request to find out the current activation status of a payment method. - */ -export const bpdPaymentMethodActivation = createAsyncAction( - "BPD_PAYMENT_METHOD_ACTIVATION_REQUEST", - "BPD_PAYMENT_METHOD_ACTIVATION_SUCCESS", - "BPD_PAYMENT_METHOD_ACTIVATION_FAILURE" -)(); - -/** - * The remote request to change the bpd activation on a payment method - */ -export const bpdUpdatePaymentMethodActivation = createAsyncAction( - "BPD_PAYMENT_METHOD_ACTIVATION_UPDATE_REQUEST", - "BPD_PAYMENT_METHOD_ACTIVATION_UPDATE_SUCCESS", - "BPD_PAYMENT_METHOD_ACTIVATION_UPDATE_FAILURE" -)< - BpdUpdatePaymentMethodActivationPayload, - BpdPaymentMethodActivation, - BpdPaymentMethodFailure ->(); - -export type BpdPaymentMethodActions = - | ActionType - | ActionType; diff --git a/ts/features/bonus/bpd/store/actions/periods.ts b/ts/features/bonus/bpd/store/actions/periods.ts deleted file mode 100644 index ee9b978442b..00000000000 --- a/ts/features/bonus/bpd/store/actions/periods.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { IUnitTag } from "@pagopa/ts-commons/lib/units"; -import { ActionType, createAsyncAction } from "typesafe-actions"; -import { BpdPeriodWithInfo } from "../reducers/details/periods"; - -export type AwardPeriodId = number & IUnitTag<"AwardPeriodId">; - -/** - * The possible state for a period: - * - Active: the current period, can be only one at time - * - Closed: a past period that has ended - * - Inactive: a future period not yet active - */ -export type BpdPeriodStatus = "Active" | "Inactive" | "Closed"; - -/** - * This type contains all the information related to a specific cashback period. - * TODO: use this or the remote data? - */ -export type BpdPeriod = WithAwardPeriodId & { - startDate: Date; - endDate: Date; - status: BpdPeriodStatus; - // minimum transaction number required to be eligible for the cashback - minTransactionNumber: number; - // maxAmount in the API, the max super cashback amount - superCashbackAmount: number; - // last valid position in the ranking to win the super cashback - minPosition: number; - // the max amount of cashback that can be accumulated for the single transaction - maxTransactionCashback: number; - // the max amount of cashback that can be accumulated in the period - maxPeriodCashback: number; - // the cashback % applied foreach transaction - cashbackPercentage: number; - // number of days required from the end of a period to consider the ranking confirmed - gracePeriod: number; -}; - -export type WithAwardPeriodId = { - awardPeriodId: AwardPeriodId; -}; - -/** - * Request the period list & amount - */ -export const bpdPeriodsAmountLoad = createAsyncAction( - "BPD_PERIODS_AMOUNT_REQUEST", - "BPD_PERIODS_AMOUNT_SUCCESS", - "BPD_PERIODS_AMOUNT_FAILURE" -), Error>(); - -export type BpdPeriodsAction = ActionType; diff --git a/ts/features/bonus/bpd/store/actions/selectedPeriod.ts b/ts/features/bonus/bpd/store/actions/selectedPeriod.ts deleted file mode 100644 index c9729e6fa73..00000000000 --- a/ts/features/bonus/bpd/store/actions/selectedPeriod.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ActionType, createStandardAction } from "typesafe-actions"; -import { BpdPeriodWithInfo } from "../reducers/details/periods"; - -/** - * Request the period list - */ -export const bpdSelectPeriod = - createStandardAction("BPD_SELECT_PERIOD")(); - -export type BpdSelectPeriodAction = ActionType; diff --git a/ts/features/bonus/bpd/store/actions/transactions.ts b/ts/features/bonus/bpd/store/actions/transactions.ts deleted file mode 100644 index 6c49ed179cc..00000000000 --- a/ts/features/bonus/bpd/store/actions/transactions.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { IUnitTag } from "@pagopa/ts-commons/lib/units"; -import { ActionType, createAsyncAction } from "typesafe-actions"; -import { TrxCountByDayResourceArray } from "../../../../../../definitions/bpd/winning_transactions_v2/TrxCountByDayResourceArray"; -import { WinningTransactionPageResource } from "../../../../../../definitions/bpd/winning_transactions_v2/WinningTransactionPageResource"; -import { BpdPivotTransaction } from "../reducers/details/transactionsv2/entities"; -import { HPan } from "./paymentMethods"; -import { AwardPeriodId, WithAwardPeriodId } from "./periods"; - -// TODO: placeholder, TBD how to map the circuit Type -export type CircuitType = - | "PagoBancomat" - | "Visa" - | "Mastercard / Maestro" - | "Amex" - | "JCB" - | "UnionPay" - | "Diners" - | "PostePay" - | "BancomatPay" - | "Private" - | "Unknown"; - -/** - * The single transaction acquired in a cashback period - */ -export type BpdTransaction = WithAwardPeriodId & { - // The hashPan of the payment method used for the transaction - hashPan: HPan; - // id acquirer - idTrxAcquirer: string; - // id issuer - idTrxIssuer: string; - // total amount of the transaction, if negative the operation has been canceled - amount: number; - trxDate: Date; - // cashback received from the transaction, if negative the operation has been canceled and also the cashback - cashback: number; - circuitType: CircuitType; -}; - -/** - * Unique Id for BpdTransactions - */ -export type BpdTransactionId = string & IUnitTag<"BpdTransactionId">; - -// TODO: integrate in BpdTransaction after removing the feature flag -export type BpdTransactionV2 = BpdTransaction & { - idTrx: BpdTransactionId; - validForCashback: boolean; - isPivot: boolean; -}; - -export type BpdTransactions = WithAwardPeriodId & { - results: ReadonlyArray; -}; - -export type BpdTransactionsError = WithAwardPeriodId & { - error: Error; -}; - -/** - * Request all the transactions for a specific period - */ -export const bpdTransactionsLoad = createAsyncAction( - "BPD_TRANSACTIONS_REQUEST", - "BPD_TRANSACTIONS_SUCCESS", - "BPD_TRANSACTIONS_FAILURE" -)(); - -type BpdTransactionPageRequestPayload = WithAwardPeriodId & { - nextCursor?: number; -}; - -export type BpdTransactionPageSuccessPayload = WithAwardPeriodId & { - results: WinningTransactionPageResource; -}; - -/** - * Load a page of transactions for a specific period - */ -export const bpdTransactionsLoadPage = createAsyncAction( - "BPD_TRANSACTIONS_PAGE_REQUEST", - "BPD_TRANSACTIONS_PAGE_SUCCESS", - "BPD_TRANSACTIONS_PAGE_FAILURE" -)< - BpdTransactionPageRequestPayload, - BpdTransactionPageSuccessPayload, - BpdTransactionsError ->(); - -export type TrxCountByDayResource = WithAwardPeriodId & { - results: TrxCountByDayResourceArray; -}; - -/** - * Load the countByDay stats for a specific period - */ -export const bpdTransactionsLoadCountByDay = createAsyncAction( - "BPD_TRANSACTIONS_COUNT_BY_DAY_REQUEST", - "BPD_TRANSACTIONS_COUNT_BY_DAY_SUCCESS", - "BPD_TRANSACTIONS_COUNT_BY_DAY_FAILURE" -)(); - -export type TrxMilestonePayload = WithAwardPeriodId & { - result: BpdPivotTransaction | undefined; -}; - -/** - * Load the milestone pivot for a specific period - */ -export const bpdTransactionsLoadMilestone = createAsyncAction( - "BPD_TRANSACTIONS_MILESTONE_REQUEST", - "BPD_TRANSACTIONS_MILESTONE_SUCCESS", - "BPD_TRANSACTIONS_MILESTONE_FAILURE" -)(); - -/** - * Load the required data to render the transaction screen - */ -export const bpdTransactionsLoadRequiredData = createAsyncAction( - "BPD_TRANSACTIONS_LOAD_REQUIRED_DATA_REQUEST", - "BPD_TRANSACTIONS_LOAD_REQUIRED_DATA_SUCCESS", - "BPD_TRANSACTIONS_LOAD_REQUIRED_DATA_FAILURE" -)(); - -export type BpdTransactionsAction = - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType; diff --git a/ts/features/bonus/bpd/store/reducers/__mock__/amount.ts b/ts/features/bonus/bpd/store/reducers/__mock__/amount.ts deleted file mode 100644 index bf903e9d48f..00000000000 --- a/ts/features/bonus/bpd/store/reducers/__mock__/amount.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { BpdAmount } from "../../../saga/networking/amount"; -import { AwardPeriodId } from "../../actions/periods"; - -export const zeroAmount: BpdAmount = { - awardPeriodId: 2 as AwardPeriodId, - totalCashback: 0, - transactionNumber: 0 -}; - -export const notEligibleAmount: BpdAmount = { - awardPeriodId: 2 as AwardPeriodId, - totalCashback: 10.25, - transactionNumber: 3 -}; - -export const eligibleAmount: BpdAmount = { - awardPeriodId: 2 as AwardPeriodId, - totalCashback: 83.52, - transactionNumber: 50 -}; - -export const eligibleMaxAmount: BpdAmount = { - awardPeriodId: 2 as AwardPeriodId, - totalCashback: 150, - transactionNumber: 50 -}; diff --git a/ts/features/bonus/bpd/store/reducers/__mock__/bancomat.ts b/ts/features/bonus/bpd/store/reducers/__mock__/bancomat.ts deleted file mode 100644 index 19d42551128..00000000000 --- a/ts/features/bonus/bpd/store/reducers/__mock__/bancomat.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { WalletTypeEnum } from "../../../../../../../definitions/pagopa/WalletV2"; -import { PatchedWalletV2 } from "../../../../../../types/pagopa"; -import { EnableableFunctionsEnum } from "../../../../../../../definitions/pagopa/EnableableFunctions"; - -export const bancomat = { - walletType: WalletTypeEnum.Bancomat, - createDate: "2021-04-05", - enableableFunctions: [EnableableFunctionsEnum.BPD], - favourite: false, - idWallet: 24415, - info: { - blurredNumber: "0003", - brand: "MASTERCARD", - brandLogo: - "https://wisp2.pagopa.gov.it/wallet/assets/img/creditcard/carta_mc.png", - expireMonth: "4", - expireYear: "2021", - hashPan: "e105a87731025d54181d8e4c4c04ff344ce82e57d6a3d6c6911e8eadb0348d7b", - holder: "Maria Rossi", - htokenList: ["token1", "token2"], - issuerAbiCode: "00123", - type: "PP" - }, - onboardingChannel: "I", - pagoPA: true, - updateDate: "2021-04-05" -} as PatchedWalletV2; diff --git a/ts/features/bonus/bpd/store/reducers/__mock__/periods.ts b/ts/features/bonus/bpd/store/reducers/__mock__/periods.ts deleted file mode 100644 index 7814f38fdf5..00000000000 --- a/ts/features/bonus/bpd/store/reducers/__mock__/periods.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { - AwardPeriodId, - BpdPeriod, - WithAwardPeriodId -} from "../../actions/periods"; - -export const closedPeriod: BpdPeriod = { - awardPeriodId: 0 as AwardPeriodId, - minTransactionNumber: 50, - maxPeriodCashback: 150, - gracePeriod: 5, - startDate: new Date("2020-10-01"), - endDate: new Date("2020-10-31"), - cashbackPercentage: 0.1, - maxTransactionCashback: 10, - minPosition: 100000, - status: "Closed", - superCashbackAmount: 1500 -}; - -export const activePeriod: BpdPeriod = { - awardPeriodId: 1 as AwardPeriodId, - minTransactionNumber: 50, - maxPeriodCashback: 150, - gracePeriod: 5, - startDate: new Date("2020-11-01"), - endDate: new Date("2020-11-30"), - cashbackPercentage: 0.1, - maxTransactionCashback: 10, - minPosition: 100000, - status: "Active", - superCashbackAmount: 1500 -}; - -export const inactivePeriod: BpdPeriod = { - awardPeriodId: 2 as AwardPeriodId, - minTransactionNumber: 50, - maxPeriodCashback: 150, - gracePeriod: 5, - startDate: new Date("2021-01-01"), - endDate: new Date("2021-06-30"), - cashbackPercentage: 0.1, - maxTransactionCashback: 10, - minPosition: 100000, - status: "Inactive", - superCashbackAmount: 1500 -}; - -export const withAwardPeriodId = ( - value: T, - newId: AwardPeriodId -): T => ({ ...value, awardPeriodId: newId }); diff --git a/ts/features/bonus/bpd/store/reducers/__mock__/ranking.ts b/ts/features/bonus/bpd/store/reducers/__mock__/ranking.ts deleted file mode 100644 index 57ecedc2cb8..00000000000 --- a/ts/features/bonus/bpd/store/reducers/__mock__/ranking.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { AwardPeriodId } from "../../actions/periods"; -import { BpdRankingNotReady, BpdRankingReady } from "../details/periods"; - -export const notReadyRanking: BpdRankingNotReady = { - awardPeriodId: 0 as AwardPeriodId, - kind: "notReady" -}; - -export const readyRanking: BpdRankingReady = { - kind: "ready", - awardPeriodId: 1 as AwardPeriodId, - maxTransactionNumber: 150, - minTransactionNumber: 0, - ranking: 1500, - totalParticipants: 30000, - transactionNumber: 5 -}; diff --git a/ts/features/bonus/bpd/store/reducers/__test__/combiner.test.ts b/ts/features/bonus/bpd/store/reducers/__test__/combiner.test.ts deleted file mode 100644 index 2d2ff48da20..00000000000 --- a/ts/features/bonus/bpd/store/reducers/__test__/combiner.test.ts +++ /dev/null @@ -1,342 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { AwardPeriodId } from "../../actions/periods"; -import { eligibleAmount, zeroAmount } from "../__mock__/amount"; -import { - activePeriod, - closedPeriod, - inactivePeriod -} from "../__mock__/periods"; -import { readyRanking } from "../__mock__/ranking"; -import { bpdPeriodsAmountWalletVisibleSelector } from "../details/combiner"; -import { BpdPeriodWithInfo } from "../details/periods"; - -const inactivePeriodA: BpdPeriodWithInfo = { - amount: zeroAmount, - ...{ - ...inactivePeriod, - startDate: new Date("2025-01-01"), - awardPeriodId: 55 as AwardPeriodId - }, - ranking: readyRanking -}; -const inactivePeriodB: BpdPeriodWithInfo = { - amount: zeroAmount, - ...inactivePeriod, - ranking: readyRanking -}; -const inactivePeriodC: BpdPeriodWithInfo = { - amount: zeroAmount, - ...{ - ...inactivePeriod, - startDate: new Date("2022-01-01"), - awardPeriodId: 56 as AwardPeriodId - }, - ranking: readyRanking -}; - -const activePeriodAmount: BpdPeriodWithInfo = { - amount: zeroAmount, - ...activePeriod, - ranking: readyRanking -}; - -const closedPeriodZeroAmount: BpdPeriodWithInfo = { - amount: zeroAmount, - ...closedPeriod, - ranking: readyRanking -}; - -const closedPeriodWithAmount: BpdPeriodWithInfo = { - amount: eligibleAmount, - ...closedPeriod, - ranking: readyRanking -}; - -describe("test bpdPeriodsAmountWalletVisibleSelector when bpd is enabled", () => { - it("one inactive period should return one inactive period", () => { - const visiblePeriods = bpdPeriodsAmountWalletVisibleSelector.resultFunc( - pot.some([inactivePeriodB]), - pot.some(true) - ); - - expect(pot.isSome(visiblePeriods)).toBeTruthy(); - if (pot.isSome(visiblePeriods)) { - expect(visiblePeriods.value.length).toBe(1); - expect(visiblePeriods.value[0].awardPeriodId).toBe( - inactivePeriod.awardPeriodId - ); - } - }); - it("with multiple inactive period should return the most recent inactive period", () => { - const visiblePeriods = bpdPeriodsAmountWalletVisibleSelector.resultFunc( - pot.some([inactivePeriodA, inactivePeriodB, inactivePeriodC]), - pot.some(true) - ); - - expect(pot.isSome(visiblePeriods)).toBeTruthy(); - if (pot.isSome(visiblePeriods)) { - expect(visiblePeriods.value.length).toBe(1); - expect(visiblePeriods.value[0].awardPeriodId).toBe( - inactivePeriodB.awardPeriodId - ); - } - }); - - it( - "with multiple inactive period and an active period " + - "should return the active period", - () => { - const visiblePeriods = bpdPeriodsAmountWalletVisibleSelector.resultFunc( - pot.some([ - activePeriodAmount, - inactivePeriodA, - inactivePeriodB, - inactivePeriodC - ]), - pot.some(true) - ); - - expect(pot.isSome(visiblePeriods)).toBeTruthy(); - if (pot.isSome(visiblePeriods)) { - expect(visiblePeriods.value.length).toBe(1); - expect(visiblePeriods.value[0].awardPeriodId).toBe( - activePeriodAmount.awardPeriodId - ); - } - } - ); - - it( - "with multiple inactive period, an active period and a closed period (with amount zero) " + - "should return the active period", - () => { - const visiblePeriods = bpdPeriodsAmountWalletVisibleSelector.resultFunc( - pot.some([ - closedPeriodZeroAmount, - activePeriodAmount, - inactivePeriodA, - inactivePeriodB, - inactivePeriodC - ]), - pot.some(true) - ); - - expect(pot.isSome(visiblePeriods)).toBeTruthy(); - if (pot.isSome(visiblePeriods)) { - expect(visiblePeriods.value.length).toBe(1); - expect(visiblePeriods.value[0].awardPeriodId).toBe( - activePeriodAmount.awardPeriodId - ); - } - } - ); - - it( - "with multiple inactive period, an active period and a closed period (with amount > zero) " + - "should return the active period and the closed period", - () => { - const visiblePeriods = bpdPeriodsAmountWalletVisibleSelector.resultFunc( - pot.some([ - closedPeriodWithAmount, - activePeriodAmount, - inactivePeriodA, - inactivePeriodB, - inactivePeriodC - ]), - pot.some(true) - ); - - expect(pot.isSome(visiblePeriods)).toBeTruthy(); - if (pot.isSome(visiblePeriods)) { - expect(visiblePeriods.value.length).toBe(2); - expect(visiblePeriods.value[0].awardPeriodId).toBe( - closedPeriodWithAmount.awardPeriodId - ); - expect(visiblePeriods.value[1].awardPeriodId).toBe( - activePeriodAmount.awardPeriodId - ); - } - } - ); - - it( - "with multiple inactive period and a closed period (with amount > zero) " + - "should return the closed period", - () => { - const visiblePeriods = bpdPeriodsAmountWalletVisibleSelector.resultFunc( - pot.some([ - closedPeriodWithAmount, - inactivePeriodA, - inactivePeriodB, - inactivePeriodC - ]), - pot.some(true) - ); - - expect(pot.isSome(visiblePeriods)).toBeTruthy(); - if (pot.isSome(visiblePeriods)) { - expect(visiblePeriods.value.length).toBe(1); - expect(visiblePeriods.value[0].awardPeriodId).toBe( - closedPeriodWithAmount.awardPeriodId - ); - } - } - ); - it( - "with multiple inactive period and a closed period (with amount zero) " + - "should return no periods", - () => { - const visiblePeriods = bpdPeriodsAmountWalletVisibleSelector.resultFunc( - pot.some([ - closedPeriodZeroAmount, - inactivePeriodA, - inactivePeriodB, - inactivePeriodC - ]), - pot.some(true) - ); - - expect(pot.isSome(visiblePeriods)).toBeTruthy(); - if (pot.isSome(visiblePeriods)) { - expect(visiblePeriods.value.length).toBe(0); - } - } - ); -}); - -describe("test bpdPeriodsAmountWalletVisibleSelector when bpd is disabled", () => { - it("one inactive period should return no periods", () => { - const visiblePeriods = bpdPeriodsAmountWalletVisibleSelector.resultFunc( - pot.some([inactivePeriodB]), - pot.some(false) - ); - - expect(pot.isSome(visiblePeriods)).toBeTruthy(); - if (pot.isSome(visiblePeriods)) { - expect(visiblePeriods.value.length).toBe(0); - } - }); - it("with multiple inactive period should return no periods", () => { - const visiblePeriods = bpdPeriodsAmountWalletVisibleSelector.resultFunc( - pot.some([inactivePeriodA, inactivePeriodB, inactivePeriodC]), - pot.some(false) - ); - - expect(pot.isSome(visiblePeriods)).toBeTruthy(); - if (pot.isSome(visiblePeriods)) { - expect(visiblePeriods.value.length).toBe(0); - } - }); - - it( - "with multiple inactive period and an active period " + - "should return no periods", - () => { - const visiblePeriods = bpdPeriodsAmountWalletVisibleSelector.resultFunc( - pot.some([ - activePeriodAmount, - inactivePeriodA, - inactivePeriodB, - inactivePeriodC - ]), - pot.some(false) - ); - - expect(pot.isSome(visiblePeriods)).toBeTruthy(); - if (pot.isSome(visiblePeriods)) { - expect(visiblePeriods.value.length).toBe(0); - } - } - ); - - it( - "with multiple inactive period, an active period and a closed period (with amount zero) " + - "should return no periods", - () => { - const visiblePeriods = bpdPeriodsAmountWalletVisibleSelector.resultFunc( - pot.some([ - closedPeriodZeroAmount, - activePeriodAmount, - inactivePeriodA, - inactivePeriodB, - inactivePeriodC - ]), - pot.some(false) - ); - - expect(pot.isSome(visiblePeriods)).toBeTruthy(); - if (pot.isSome(visiblePeriods)) { - expect(visiblePeriods.value.length).toBe(0); - } - } - ); - - it( - "with multiple inactive period, an active period and a closed period (with amount > zero) " + - "should return the closed period", - () => { - const visiblePeriods = bpdPeriodsAmountWalletVisibleSelector.resultFunc( - pot.some([ - closedPeriodWithAmount, - activePeriodAmount, - inactivePeriodA, - inactivePeriodB, - inactivePeriodC - ]), - pot.some(false) - ); - - expect(pot.isSome(visiblePeriods)).toBeTruthy(); - if (pot.isSome(visiblePeriods)) { - expect(visiblePeriods.value.length).toBe(1); - expect(visiblePeriods.value[0].awardPeriodId).toBe( - closedPeriodWithAmount.awardPeriodId - ); - } - } - ); - - it( - "with multiple inactive period and a closed period (with amount > zero) " + - "should return the closed period", - () => { - const visiblePeriods = bpdPeriodsAmountWalletVisibleSelector.resultFunc( - pot.some([ - closedPeriodWithAmount, - inactivePeriodA, - inactivePeriodB, - inactivePeriodC - ]), - pot.some(false) - ); - - expect(pot.isSome(visiblePeriods)).toBeTruthy(); - if (pot.isSome(visiblePeriods)) { - expect(visiblePeriods.value.length).toBe(1); - expect(visiblePeriods.value[0].awardPeriodId).toBe( - closedPeriodWithAmount.awardPeriodId - ); - } - } - ); - it( - "with multiple inactive period and a closed period (with amount zero) " + - "should return no periods", - () => { - const visiblePeriods = bpdPeriodsAmountWalletVisibleSelector.resultFunc( - pot.some([ - closedPeriodZeroAmount, - inactivePeriodA, - inactivePeriodB, - inactivePeriodC - ]), - pot.some(false) - ); - - expect(pot.isSome(visiblePeriods)).toBeTruthy(); - if (pot.isSome(visiblePeriods)) { - expect(visiblePeriods.value.length).toBe(0); - } - } - ); -}); diff --git a/ts/features/bonus/bpd/store/reducers/__test__/payoffInstruments.test.ts b/ts/features/bonus/bpd/store/reducers/__test__/payoffInstruments.test.ts deleted file mode 100644 index 765754400fd..00000000000 --- a/ts/features/bonus/bpd/store/reducers/__test__/payoffInstruments.test.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { Iban } from "../../../../../../../definitions/backend/Iban"; -import { applicationChangeState } from "../../../../../../store/actions/application"; -import { appReducer } from "../../../../../../store/reducers"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { - remoteError, - remoteLoading, - remoteReady, - remoteUndefined -} from "../../../../../../common/model/RemoteValue"; -import { IbanStatus } from "../../../saga/networking/patchCitizenIban"; -import { bpdUpsertIban } from "../../actions/iban"; - -jest.mock("@react-native-async-storage/async-storage", () => ({ - AsyncStorage: jest.fn() -})); - -jest.mock("react-native-share", () => ({ - open: jest.fn() -})); - -describe("test state with action bpdUpsertIban", () => { - const globalState: GlobalState = appReducer( - undefined, - applicationChangeState("active") - ); - - expect( - globalState.bonus.bpd.details.activation.payoffInstr.enrolledValue - ).toBe(remoteUndefined); - - expect( - globalState.bonus.bpd.details.activation.payoffInstr.upsert.outcome - ).toBe(remoteUndefined); - - it("check the state after a bpdUpsertIban.request", () => { - const upsertResult = appReducer( - globalState, - bpdUpsertIban.request("IT12313" as Iban) - ); - expect( - upsertResult.bonus.bpd.details.activation.payoffInstr.enrolledValue - ).toBe(remoteUndefined); - expect( - upsertResult.bonus.bpd.details.activation.payoffInstr.upsert.outcome - ).toBe(remoteLoading); - }); - it("check the state after a bpdUpsertIban.success CANT_VERIFY", () => { - const newIban = "IT123" as Iban; - const result = appReducer( - globalState, - bpdUpsertIban.success({ - status: IbanStatus.CANT_VERIFY, - payoffInstr: newIban - }) - ); - - expect( - result.bonus.bpd.details.activation.payoffInstr.enrolledValue - ).toStrictEqual(remoteReady(newIban)); - expect( - result.bonus.bpd.details.activation.payoffInstr.upsert - ).toStrictEqual({ - outcome: remoteReady("CANT_VERIFY" as IbanStatus), - value: newIban - }); - }); - it("check the state after a bpdUpsertIban.success OK", () => { - const newIban = "IT123" as Iban; - const result = appReducer( - globalState, - bpdUpsertIban.success({ - status: IbanStatus.OK, - payoffInstr: newIban - }) - ); - - expect( - result.bonus.bpd.details.activation.payoffInstr.enrolledValue - ).toStrictEqual(remoteReady(newIban)); - expect( - result.bonus.bpd.details.activation.payoffInstr.upsert - ).toStrictEqual({ - outcome: remoteReady("OK" as IbanStatus), - value: newIban - }); - }); - it("check the state after a bpdUpsertIban.success NOT_VALID", () => { - const result = appReducer( - globalState, - bpdUpsertIban.success({ - status: IbanStatus.NOT_VALID, - payoffInstr: undefined - }) - ); - - expect( - result.bonus.bpd.details.activation.payoffInstr.enrolledValue - ).toStrictEqual(remoteUndefined); - expect( - result.bonus.bpd.details.activation.payoffInstr.upsert - ).toStrictEqual({ - outcome: remoteReady("NOT_VALID" as IbanStatus), - value: undefined - }); - }); - it("check the state after a bpdUpsertIban.success NOT_OWNED", () => { - const result = appReducer( - globalState, - bpdUpsertIban.success({ - status: IbanStatus.NOT_OWNED, - payoffInstr: undefined - }) - ); - - expect( - result.bonus.bpd.details.activation.payoffInstr.enrolledValue - ).toStrictEqual(remoteUndefined); - expect( - result.bonus.bpd.details.activation.payoffInstr.upsert - ).toStrictEqual({ - outcome: remoteReady("NOT_OWNED" as IbanStatus), - value: undefined - }); - }); - it("check the state after a bpdUpsertIban.failure", () => { - const error = new Error("Error!"); - const result = appReducer(globalState, bpdUpsertIban.failure(error)); - - expect( - result.bonus.bpd.details.activation.payoffInstr.enrolledValue - ).toStrictEqual(remoteUndefined); - expect( - result.bonus.bpd.details.activation.payoffInstr.upsert - ).toStrictEqual({ - outcome: remoteError(error), - value: undefined - }); - }); -}); diff --git a/ts/features/bonus/bpd/store/reducers/details/__test__/paymentMethods.test.ts b/ts/features/bonus/bpd/store/reducers/details/__test__/paymentMethods.test.ts deleted file mode 100644 index 15b63ee7429..00000000000 --- a/ts/features/bonus/bpd/store/reducers/details/__test__/paymentMethods.test.ts +++ /dev/null @@ -1,109 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { - areAnyPaymentMethodsActiveSelector, - BpdPotPaymentMethodActivation -} from "../paymentMethods"; -import { IndexedById } from "../../../../../../../store/helpers/indexer"; -import { HPan } from "../../../actions/paymentMethods"; -import { fromPatchedWalletV2ToRawPaymentMethod } from "../../../../../../../utils/walletv2"; -import { bancomat } from "../../__mock__/bancomat"; -import { BancomatPaymentMethod } from "../../../../../../../types/pagopa"; - -const indexedPaymentMethods: IndexedById = { - id1: pot.some({ - hPan: "hpan1" as HPan, - activationStatus: "active" - }), - id2: pot.some({ - hPan: "hpan2" as HPan, - activationStatus: "active" - }), - id3: pot.some({ - hPan: "hpan2" as HPan, - activationStatus: "active" - }) -}; - -const paymentMethod = fromPatchedWalletV2ToRawPaymentMethod( - bancomat -) as BancomatPaymentMethod; -const paymentMethodBancomat = { - ...paymentMethod, - info: { ...paymentMethod.info, hashPan: "id1" } -}; - -describe("payment methods reducer tests", () => { - it("should return false since no methods are provided", () => { - expect( - areAnyPaymentMethodsActiveSelector([]).resultFunc(indexedPaymentMethods) - ).toBeFalsy(); - }); - - it("should return true (id1 - active)", () => { - expect( - areAnyPaymentMethodsActiveSelector([paymentMethodBancomat]).resultFunc( - indexedPaymentMethods - ) - ).toBeTruthy(); - }); - - it("should return false (id1 - not active)", () => { - expect( - areAnyPaymentMethodsActiveSelector([paymentMethodBancomat]).resultFunc({ - ...indexedPaymentMethods, - id1: pot.some({ - hPan: "hpan1" as HPan, - activationStatus: "inactive" - }) - }) - ).toBeFalsy(); - }); - - it("should return true, even if in error (some error)", () => { - expect( - areAnyPaymentMethodsActiveSelector([paymentMethodBancomat]).resultFunc({ - ...indexedPaymentMethods, - id1: pot.toError( - pot.some({ - hPan: "hpan1" as HPan, - activationStatus: "active" - }), - new Error("some error") - ) - }) - ).toBeTruthy(); - }); - - it("should return false (none error)", () => { - expect( - areAnyPaymentMethodsActiveSelector([paymentMethodBancomat]).resultFunc({ - ...indexedPaymentMethods, - id1: pot.toError(pot.none, new Error("some error")) - }) - ).toBeFalsy(); - }); - - it("should return true (at least one active)", () => { - const indexedPaymentMethodsOneActive: IndexedById = - { - id1: pot.some({ - hPan: "hpan1" as HPan, - activationStatus: "active" - }), - id2: pot.some({ - hPan: "hpan2" as HPan, - activationStatus: "inactive" - }), - id3: pot.some({ - hPan: "hpan2" as HPan, - activationStatus: "notActivable" - }), - id4: pot.none - }; - expect( - areAnyPaymentMethodsActiveSelector([paymentMethodBancomat]).resultFunc( - indexedPaymentMethodsOneActive - ) - ).toBeTruthy(); - }); -}); diff --git a/ts/features/bonus/bpd/store/reducers/details/activation/index.ts b/ts/features/bonus/bpd/store/reducers/details/activation/index.ts deleted file mode 100644 index 016572cdb6e..00000000000 --- a/ts/features/bonus/bpd/store/reducers/details/activation/index.ts +++ /dev/null @@ -1,233 +0,0 @@ -import * as O from "fp-ts/lib/Option"; -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { combineReducers } from "redux"; -import { createSelector } from "reselect"; -import { getType } from "typesafe-actions"; -import { pipe } from "fp-ts/lib/function"; -import { Action } from "../../../../../../../store/actions/types"; -import { GlobalState } from "../../../../../../../store/reducers/types"; -import { - getValue, - remoteError, - remoteLoading, - remoteReady, - remoteUndefined, - RemoteValue -} from "../../../../../../../common/model/RemoteValue"; -import { - ActivationStatus, - bpdLoadActivationStatus -} from "../../../actions/details"; -import { - bpdDeleteUserFromProgram, - bpdEnrollUserToProgram, - bpdUnsubscribeCompleted, - bpdUnsubscribeFailure, - bpdUpdateOptInStatusMethod -} from "../../../actions/onboarding"; -import { CitizenOptInStatusEnum } from "../../../../../../../../definitions/bpd/citizen_v2/CitizenOptInStatus"; -import { optInPaymentMethodsStart } from "../../../actions/optInPaymentMethods"; -import paymentInstrumentReducer, { - bpdUpsertIbanSelector, - PayoffInstrumentType -} from "./payoffInstrument"; -import technicalAccountReducer, { - bpdTechnicalAccountSelector -} from "./technicalAccount"; -import { bpdActivationUiReducer, BpdActivationUiState } from "./ui"; - -export type BpdActivation = { - enabled: pot.Pot; - activationStatus: RemoteValue; - payoffInstr: PayoffInstrumentType; - unsubscription: RemoteValue; - technicalAccount: RemoteValue; - optInStatus: pot.Pot; - ui: BpdActivationUiState; -}; - -/** - * This reducer keeps the enabled status sync with the remote endpoint. - * This value can change when: - * - The application loads the first time the value - * - The user chooses to activate the bpd program - * - The user chooses to unsubscribe from the bpd program - * @param state - * @param action - */ -// TODO: check if the logic is ok -const enabledReducer = ( - state: pot.Pot = pot.none, - action: Action -): pot.Pot => { - switch (action.type) { - case getType(bpdLoadActivationStatus.request): - case getType(bpdEnrollUserToProgram.request): - return pot.toLoading(state); - case getType(bpdLoadActivationStatus.success): - case getType(bpdEnrollUserToProgram.success): - return pot.some(action.payload.enabled); - case getType(bpdDeleteUserFromProgram.success): - return pot.none; - case getType(bpdLoadActivationStatus.failure): - case getType(bpdEnrollUserToProgram.failure): - return pot.toError(state, action.payload); - } - return state; -}; - -const OPT_IN_INITIAL_STATE = pot.none; -const optInStatusReducer = ( - state: pot.Pot = OPT_IN_INITIAL_STATE, - action: Action -): pot.Pot => { - switch (action.type) { - case getType(optInPaymentMethodsStart): - return OPT_IN_INITIAL_STATE; - case getType(bpdLoadActivationStatus.request): - return pot.toLoading(state); - case getType(bpdUpdateOptInStatusMethod.request): - return pot.toUpdating(state, action.payload); - case getType(bpdLoadActivationStatus.success): - return action.payload.optInStatus - ? pot.some(action.payload.optInStatus) - : pot.none; - case getType(bpdUpdateOptInStatusMethod.success): - return pot.some(action.payload); - case getType(bpdLoadActivationStatus.failure): - case getType(bpdEnrollUserToProgram.failure): - case getType(bpdUpdateOptInStatusMethod.failure): - return pot.toError(state, action.payload); - } - return state; -}; - -/** - * Keep the state of "unsubscribe" from bpd outcome - * @param state - * @param action - */ -const unsubscriptionReducer = ( - state: RemoteValue = remoteUndefined, - action: Action -): RemoteValue => { - switch (action.type) { - case getType(bpdDeleteUserFromProgram.request): - return remoteLoading; - case getType(bpdDeleteUserFromProgram.success): - return remoteReady(true); - case getType(bpdDeleteUserFromProgram.failure): - return remoteError(action.payload); - // reset the state when return to wallet - case getType(bpdUnsubscribeCompleted): - case getType(bpdUnsubscribeFailure): - return remoteUndefined; - } - return state; -}; - -const activationStatusReducer = ( - state: RemoteValue = remoteUndefined, - action: Action -): RemoteValue => { - switch (action.type) { - case getType(bpdLoadActivationStatus.request): - case getType(bpdEnrollUserToProgram.request): - return remoteLoading; - case getType(bpdLoadActivationStatus.success): - case getType(bpdEnrollUserToProgram.success): - return remoteReady(action.payload.activationStatus); - case getType(bpdDeleteUserFromProgram.success): - return remoteUndefined; - case getType(bpdLoadActivationStatus.failure): - case getType(bpdEnrollUserToProgram.failure): - return remoteError(action.payload); - } - return state; -}; - -const bpdActivationReducer = combineReducers({ - enabled: enabledReducer, - activationStatus: activationStatusReducer, - payoffInstr: paymentInstrumentReducer, - unsubscription: unsubscriptionReducer, - technicalAccount: technicalAccountReducer, - optInStatus: optInStatusReducer, - ui: bpdActivationUiReducer -}); - -/** - * Return the optInStatus value related to the bpd program - * @param state - */ -export const optInStatusSelector = ( - state: GlobalState -): pot.Pot => - state.bonus.bpd.details.activation.optInStatus; - -/** - * Return the enabled value related to the bpd program - * @param state - */ -export const bpdEnabledSelector = ( - state: GlobalState -): pot.Pot => state.bonus.bpd.details.activation.enabled; - -/** - * Return the enabled value related to the bpd activation status - * @param state - */ -export const activationStatusSelector = ( - state: GlobalState -): RemoteValue => - state.bonus.bpd.details.activation.activationStatus; - -/** - * Return the Iban that the user has entered to receive the cashback amount - * @return {RemoteValue} - */ -export const bpdIbanSelector = createSelector< - GlobalState, - RemoteValue, - RemoteValue ->( - [ - (state: GlobalState) => - state.bonus.bpd.details.activation.payoffInstr.enrolledValue - ], - iban => iban -); - -/** - * Return the unsubscription state, memoized - */ -export const bpdUnsubscriptionSelector = createSelector( - [(state: GlobalState) => state.bonus.bpd.details.activation.unsubscription], - unsubscription => unsubscription -); - -/** - * Return the prefill text based on the iban upsert or iban inserted by user - * if the user tried to add a new iban (upsertIban.value !== undefined) return that iban - * else try to return the actual iban getValue(iban) - * - */ -export const bpdIbanPrefillSelector = createSelector( - [bpdIbanSelector, bpdUpsertIbanSelector, bpdTechnicalAccountSelector], - (iban, upsertIban, technicalAccount): string => - pipe( - upsertIban.value as string, - O.fromNullable, - O.alt(() => - pipe( - getValue(technicalAccount), - O.fromNullable, - O.map(_ => "") - ) - ), - O.alt(() => O.fromNullable(getValue(iban))), - O.getOrElse(() => "") - ) -); - -export default bpdActivationReducer; diff --git a/ts/features/bonus/bpd/store/reducers/details/activation/payoffInstrument.ts b/ts/features/bonus/bpd/store/reducers/details/activation/payoffInstrument.ts deleted file mode 100644 index a194e4cd0ba..00000000000 --- a/ts/features/bonus/bpd/store/reducers/details/activation/payoffInstrument.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { combineReducers } from "redux"; -import { getType } from "typesafe-actions"; -import { Iban } from "../../../../../../../../definitions/backend/Iban"; -import { Action } from "../../../../../../../store/actions/types"; -import { GlobalState } from "../../../../../../../store/reducers/types"; -import { - isError, - remoteError, - remoteLoading, - remoteReady, - remoteUndefined, - RemoteValue -} from "../../../../../../../common/model/RemoteValue"; -import { IbanStatus } from "../../../../saga/networking/patchCitizenIban"; -import { bpdLoadActivationStatus } from "../../../actions/details"; -import { - bpdIbanInsertionResetScreen, - bpdIbanInsertionStart, - bpdUpsertIban -} from "../../../actions/iban"; -import { bpdDeleteUserFromProgram } from "../../../actions/onboarding"; - -export type UpsertIBAN = { - // the results of the upsert operation - outcome: RemoteValue; - // the value the user is trying to enter - value: Iban | undefined; -}; - -export type PayoffInstrumentType = { - enrolledValue: RemoteValue; - upsert: UpsertIBAN; -}; - -/** - * This reducer keeps the latest valid paymentInstrument (IBAN) for the user. - * This value can change when: - * - The application load the first time the value - * - The user choose to edit / add a new paymentInstrument and the operation is completed with success. - * @param state - * @param action - */ -const paymentInstrumentValueReducer = ( - state: RemoteValue = remoteUndefined, - action: Action -): RemoteValue => { - switch (action.type) { - case getType(bpdDeleteUserFromProgram.success): - return remoteUndefined; - case getType(bpdLoadActivationStatus.request): - return remoteLoading; - case getType(bpdLoadActivationStatus.success): - return remoteReady(action.payload.payoffInstr); - // Update the effective value only if the upsert is OK or CANT_VERIFY - case getType(bpdUpsertIban.success): - return action.payload.status === IbanStatus.OK || - action.payload.status === IbanStatus.CANT_VERIFY - ? remoteReady(action.payload.payoffInstr) - : state; - case getType(bpdLoadActivationStatus.failure): - return remoteError(action.payload); - } - return state; -}; - -const INITIAL_UPSERT: UpsertIBAN = { - outcome: remoteUndefined, - value: undefined -}; - -/** - * This reducer updates the state of the upsert operation. - * @param state - * @param action - */ -const paymentInstrumentUpsertReducer = ( - state: UpsertIBAN = INITIAL_UPSERT, - action: Action -): UpsertIBAN => { - switch (action.type) { - case getType(bpdUpsertIban.request): - return { - value: action.payload, - outcome: remoteLoading - }; - case getType(bpdUpsertIban.success): - return { - value: action.payload.payoffInstr ?? state.value, - outcome: remoteReady(action.payload.status) - }; - case getType(bpdUpsertIban.failure): - return { - ...state, - outcome: remoteError(action.payload) - }; - case getType(bpdIbanInsertionResetScreen): - return { - ...state, - outcome: remoteUndefined - }; - case getType(bpdIbanInsertionStart): - return INITIAL_UPSERT; - } - return state; -}; - -/** - * Return the Iban that is currently used for an upsert operation. - * This is not the current Iban associated to the bpd program, but only a candidate. - * @param state - */ -export const bpdUpsertIbanSelector = (state: GlobalState): UpsertIBAN => - state.bonus.bpd.details.activation.payoffInstr.upsert; - -/** - * Return true if the iban upsertion received errors - * @param state - */ -export const bpdUpsertIbanIsError = (state: GlobalState): boolean => - isError(state.bonus.bpd.details.activation.payoffInstr.upsert.outcome); - -const paymentInstrumentReducer = combineReducers({ - // value is the effective value of the iban enrolled to the bpd program. - // it's the remote saved value. - enrolledValue: paymentInstrumentValueReducer, - // all the information related to the try to upsert a new iban. - upsert: paymentInstrumentUpsertReducer -}); - -export default paymentInstrumentReducer; diff --git a/ts/features/bonus/bpd/store/reducers/details/activation/technicalAccount.ts b/ts/features/bonus/bpd/store/reducers/details/activation/technicalAccount.ts deleted file mode 100644 index f6045a18507..00000000000 --- a/ts/features/bonus/bpd/store/reducers/details/activation/technicalAccount.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { getType } from "typesafe-actions"; -import { createSelector } from "reselect"; -import { - remoteError, - remoteLoading, - remoteReady, - remoteUndefined, - RemoteValue -} from "../../../../../../../common/model/RemoteValue"; -import { Action } from "../../../../../../../store/actions/types"; -import { bpdDeleteUserFromProgram } from "../../../actions/onboarding"; -import { bpdLoadActivationStatus } from "../../../actions/details"; -import { bpdUpsertIban } from "../../../actions/iban"; -import { IbanStatus } from "../../../../saga/networking/patchCitizenIban"; -import { GlobalState } from "../../../../../../../store/reducers/types"; - -/** - * This reducer keeps the latest valid technicalAccount (technical IBAN) for the user. - * This value can change when: - * - The application loads the first time the value - * - The user chooses to edit / add a new paymentInstrument and the operation is completed with success. - * - The user chooses to unsubscribe from the cashback - * - * If the action `bpdLoadActivationStatus.success` is dispatched after a call to citizen v1 API, so the - * the technical account data is not available, the state will be remoteUndefined. - * @param state - * @param action - */ -const technicalAccountReducer = ( - state: RemoteValue = remoteUndefined, - action: Action -): RemoteValue => { - switch (action.type) { - case getType(bpdDeleteUserFromProgram.success): - return remoteUndefined; - case getType(bpdLoadActivationStatus.request): - return remoteLoading; - case getType(bpdLoadActivationStatus.success): - return remoteReady(action.payload.technicalAccount); - // Update the effective value only if the upsert is OK or CANT_VERIFY - case getType(bpdUpsertIban.success): - return action.payload.status === IbanStatus.OK || - action.payload.status === IbanStatus.CANT_VERIFY - ? remoteReady(undefined) - : state; - case getType(bpdLoadActivationStatus.failure): - return remoteError(action.payload); - } - return state; -}; - -export default technicalAccountReducer; - -/** - * Returns the technical account string to show to the user if he has one - * @return {RemoteValue} - */ -export const bpdTechnicalAccountSelector = createSelector< - GlobalState, - RemoteValue, - RemoteValue ->( - [(state: GlobalState) => state.bonus.bpd.details.activation.technicalAccount], - technicalAccount => technicalAccount -); diff --git a/ts/features/bonus/bpd/store/reducers/details/activation/ui.ts b/ts/features/bonus/bpd/store/reducers/details/activation/ui.ts deleted file mode 100644 index 26ff69147a2..00000000000 --- a/ts/features/bonus/bpd/store/reducers/details/activation/ui.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { getType } from "typesafe-actions"; -import { combineReducers } from "redux"; -import { - remoteError, - remoteLoading, - remoteReady, - remoteUndefined, - RemoteValue -} from "../../../../../../../common/model/RemoteValue"; -import { Action } from "../../../../../../../store/actions/types"; -import { optInPaymentMethodsShowChoice } from "../../../actions/optInPaymentMethods"; -import { GlobalState } from "../../../../../../../store/reducers/types"; - -export type ShowOptInChoice = RemoteValue; -export type BpdActivationUiState = { - showOptInChoice: ShowOptInChoice; -}; - -const SHOW_OPT_IN_CHOICE_INITIAL_STATE: ShowOptInChoice = remoteUndefined; -const showOptInChoiceReducer = ( - state: ShowOptInChoice = SHOW_OPT_IN_CHOICE_INITIAL_STATE, - action: Action -): ShowOptInChoice => { - switch (action.type) { - case getType(optInPaymentMethodsShowChoice.request): - return remoteLoading; - case getType(optInPaymentMethodsShowChoice.success): - return remoteReady(action.payload); - case getType(optInPaymentMethodsShowChoice.failure): - return remoteError(action.payload); - } - return state; -}; - -export const bpdActivationUiReducer = combineReducers< - BpdActivationUiState, - Action ->({ - showOptInChoice: showOptInChoiceReducer -}); - -/** - * Return the optInStatus value related to the bpd program - * @param state - */ -export const showOptInChoiceSelector = (state: GlobalState): ShowOptInChoice => - state.bonus.bpd.details.activation.ui.showOptInChoice; diff --git a/ts/features/bonus/bpd/store/reducers/details/combiner.ts b/ts/features/bonus/bpd/store/reducers/details/combiner.ts deleted file mode 100644 index 424f3a7db63..00000000000 --- a/ts/features/bonus/bpd/store/reducers/details/combiner.ts +++ /dev/null @@ -1,211 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import { createSelector } from "reselect"; -import { cardIcons } from "../../../../../../components/wallet/card/Logo"; -import { paymentMethodsSelector } from "../../../../../../store/reducers/wallet/wallets"; -import { PaymentMethod } from "../../../../../../types/pagopa"; -import { getPaymentMethodHash } from "../../../../../../utils/paymentMethod"; -import { FOUR_UNICODE_CIRCLES } from "../../../../../../utils/wallet"; -import { EnhancedBpdTransaction } from "../../../components/transactionItem/BpdTransactionItem"; -import { BpdPaymentMethodActivation } from "../../actions/paymentMethods"; -import { BpdTransaction } from "../../actions/transactions"; -import { bpdEnabledSelector } from "./activation"; -import { bpdPaymentMethodActivationSelector } from "./paymentMethods"; -import { bpdPeriodsSelector, BpdPeriodWithInfo } from "./periods"; -import { bpdSelectedPeriodSelector } from "./selectedPeriod"; -import { bpdTransactionsForSelectedPeriod } from "./transactions"; - -/** - * A period is visible in the wallet if the bpd is enabled AND the period is active OR - * the period is closed and the transactionNumber > 0 OR - * bpd is enabled AND the period is inactive (future) AND there are no active or closed period - * @param periodList - * @param periodAmount should be a period in periodList - * @param bpdEnabled - */ -const isPeriodAmountWalletVisible = ( - periodList: ReadonlyArray, - periodAmount: BpdPeriodWithInfo, - bpdEnabled: pot.Pot -) => - pot.isSome(bpdEnabled) && - ((periodAmount.status === "Active" && bpdEnabled.value) || - (periodAmount.status === "Closed" && - periodAmount.amount.transactionNumber > 0) || - // All the periods are inactive - (periodList.every(p => p.status === "Inactive") && - // This is the first inactive period - periodList.indexOf(periodAmount) === 0 && - periodAmount.status === "Inactive" && - bpdEnabled.value)); - -/** - * Return the {@link BpdPeriodWithInfo} that can be visible in the wallet - */ -export const bpdPeriodsAmountWalletVisibleSelector = createSelector( - [bpdPeriodsSelector, bpdEnabledSelector], - (potPeriodsAmount, bpdEnabled) => - pot.map(potPeriodsAmount, periodsAmountList => { - const periodsOrderedByDate = periodsAmountList - // create a sorted copy of the array - .concat() - .sort((pa1, pa2) => - pa1.startDate < pa2.startDate - ? -1 - : pa1.startDate > pa2.startDate - ? 1 - : 0 - ); - return periodsOrderedByDate.filter(periodAmount => - isPeriodAmountWalletVisible( - periodsOrderedByDate, - periodAmount, - bpdEnabled - ) - ); - }) -); - -/** - * The period should be visible in the snapped list only if: - * state === Closed (a closed period is always visible) - * bpdEnabled === true (a inactive or current period is visible only if bpd is Enabled) - * @param periodAmount - * @param bpdEnabled - */ -const isPeriodAmountSnappedVisible = ( - periodAmount: BpdPeriodWithInfo, - bpdEnabled: pot.Pot -) => periodAmount.status === "Closed" || pot.getOrElse(bpdEnabled, false); - -/** - * Return the {@link BpdPeriodWithInfo} that should be visible in the snapped List selector - */ -export const bpdPeriodsAmountSnappedListSelector = createSelector( - [bpdPeriodsSelector, bpdEnabledSelector], - (potPeriodsAmount, bpdEnabled) => - pot.map(potPeriodsAmount, periodsAmountList => - periodsAmountList.filter(periodAmount => - isPeriodAmountSnappedVisible(periodAmount, bpdEnabled) - ) - ) -); - -/** - * Pick a payment instrument from the wallet, using the provided hashpan - * @param hashPan - * @param potPaymentMethods - */ -export const pickPaymentMethodFromHashpan = ( - hashPan: string, - potPaymentMethods: pot.Pot, Error> -): O.Option => - pot.getOrElse( - pot.map(potPaymentMethods, paymentMethods => - O.fromNullable( - paymentMethods.find(w => getPaymentMethodHash(w) === hashPan) - ) - ), - O.none - ); - -const getId = (transaction: BpdTransaction) => - `${transaction.awardPeriodId}${transaction.trxDate}${transaction.hashPan}${transaction.idTrxAcquirer}${transaction.idTrxIssuer}${transaction.amount}`; - -/** - * Enhance a {@link BpdTransaction}, trying to found the payment method in the wallet, - * in order to associate a caption and an icon - */ -export const bpdDisplayTransactionsSelector = createSelector( - [ - bpdTransactionsForSelectedPeriod, - paymentMethodsSelector, - bpdSelectedPeriodSelector - ], - ( - potTransactions, - paymentMethod, - period - ): pot.Pot, Error> => - pot.map(potTransactions, transactions => - transactions.map( - t => - ({ - ...t, - image: pipe( - pickPaymentMethodFromHashpan(t.hashPan, paymentMethod), - O.map(pm => pm.icon), - O.getOrElse(() => cardIcons.UNKNOWN) - ), - title: pipe( - pickPaymentMethodFromHashpan(t.hashPan, paymentMethod), - O.map(pm => pm.caption), - O.getOrElse(() => FOUR_UNICODE_CIRCLES) - ), - keyId: getId(t), - maxCashbackForTransactionAmount: period?.maxTransactionCashback - } as EnhancedBpdTransaction) - ) - ) -); - -/** - * There is at least one payment method with bpd enabled? - */ -export const atLeastOnePaymentMethodHasBpdEnabledSelector = createSelector( - [paymentMethodsSelector, bpdPaymentMethodActivationSelector], - (paymentMethodsPot, bpdActivations): boolean => - pot.getOrElse( - pot.map(paymentMethodsPot, paymentMethods => - paymentMethods.some(pm => - pipe( - getPaymentMethodHash(pm), - O.fromNullable, - O.map(hpan => bpdActivations[hpan]), - O.map( - potActivation => - potActivation && - pot.isSome(potActivation) && - potActivation.value.activationStatus === "active" - ), - O.getOrElseW(() => false) - ) - ) - ), - false - ) -); - -export type PaymentMethodWithActivation = PaymentMethod & - Partial>; - -/** - * Add the information of activationStatus to a PatchedWalletV2 - * in order to group the elements "notActivable" - */ -export const paymentMethodsWithActivationStatusSelector = createSelector( - [paymentMethodsSelector, bpdPaymentMethodActivationSelector], - (paymentMethodsPot, bpdActivations) => - pot.map(paymentMethodsPot, paymentMethods => - paymentMethods.map(pm => { - // try to extract the activation status to enhance the wallet - const activationStatus = pipe( - getPaymentMethodHash(pm), - O.fromNullable, - O.chain(hp => O.fromNullable(bpdActivations[hp])), - O.map(paymentMethodActivation => - pot.getOrElse( - pot.map( - paymentMethodActivation, - activationStatus => activationStatus.activationStatus - ), - undefined - ) - ), - O.toUndefined - ); - return { ...pm, activationStatus }; - }) - ) -); diff --git a/ts/features/bonus/bpd/store/reducers/details/index.ts b/ts/features/bonus/bpd/store/reducers/details/index.ts deleted file mode 100644 index ae35d8a0e55..00000000000 --- a/ts/features/bonus/bpd/store/reducers/details/index.ts +++ /dev/null @@ -1,45 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { Action, combineReducers } from "redux"; -import { IndexedById } from "../../../../../../store/helpers/indexer"; -import { BpdTransaction } from "../../actions/transactions"; -import bpdActivationReducer, { BpdActivation } from "./activation"; -import { bpdLastUpdateReducer, lastUpdate } from "./lastUpdate"; -import { - bpdPaymentMethodsReducer, - BpdPotPaymentMethodActivation -} from "./paymentMethods"; -import { bpdPeriodsReducer, BpdPeriodWithInfo } from "./periods"; -import { bpdSelectedPeriodsReducer } from "./selectedPeriod"; -import { bpdTransactionsReducer } from "./transactions"; -import { - bpdTransactionsV2Reducer, - BpdTransactionsV2State -} from "./transactionsv2"; - -export type BpdDetailsState = { - activation: BpdActivation; - paymentMethods: IndexedById; - periods: pot.Pot, Error>; - selectedPeriod: BpdPeriodWithInfo | null; - transactions: IndexedById, Error>>; - transactionsV2: BpdTransactionsV2State; - lastUpdate: lastUpdate; -}; - -const bpdDetailsReducer = combineReducers({ - // The information related to the activation (enabled / IBAN) - activation: bpdActivationReducer, - // The state of cashback on each payment method in the wallet - paymentMethods: bpdPaymentMethodsReducer, - // All the periods of the cashback - periods: bpdPeriodsReducer, - // the current period displayed, selected by the user - selectedPeriod: bpdSelectedPeriodsReducer, - transactions: bpdTransactionsReducer, - // TODO: replace with transactions when completed - transactionsV2: bpdTransactionsV2Reducer, - // the last time we received updated data - lastUpdate: bpdLastUpdateReducer -}); - -export default bpdDetailsReducer; diff --git a/ts/features/bonus/bpd/store/reducers/details/lastUpdate.ts b/ts/features/bonus/bpd/store/reducers/details/lastUpdate.ts deleted file mode 100644 index 0e80d7892c0..00000000000 --- a/ts/features/bonus/bpd/store/reducers/details/lastUpdate.ts +++ /dev/null @@ -1,27 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { getType } from "typesafe-actions"; -import { Action } from "../../../../../../store/actions/types"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { bpdAllData } from "../../actions/details"; - -export type lastUpdate = pot.Pot; - -export const bpdLastUpdateReducer = ( - state: lastUpdate = pot.none, - action: Action -): lastUpdate => { - switch (action.type) { - // If the bpd info load succeed set a new date - case getType(bpdAllData.success): - return pot.some(new Date()); - case getType(bpdAllData.request): - return pot.toLoading(state); - case getType(bpdAllData.failure): - return pot.toError(state, action.payload); - } - - return state; -}; - -export const bpdLastUpdateSelector = (state: GlobalState) => - state.bonus.bpd.details.lastUpdate; diff --git a/ts/features/bonus/bpd/store/reducers/details/paymentMethods.ts b/ts/features/bonus/bpd/store/reducers/details/paymentMethods.ts deleted file mode 100644 index 25ae05ea504..00000000000 --- a/ts/features/bonus/bpd/store/reducers/details/paymentMethods.ts +++ /dev/null @@ -1,155 +0,0 @@ -import * as O from "fp-ts/lib/Option"; -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { createSelector } from "reselect"; -import { getType } from "typesafe-actions"; -import { pipe } from "fp-ts/lib/function"; -import { Action } from "../../../../../../store/actions/types"; -import { deleteWalletSuccess } from "../../../../../../store/actions/wallet/wallets"; -import { IndexedById } from "../../../../../../store/helpers/indexer"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { bpdDeleteUserFromProgram } from "../../actions/onboarding"; -import { - BpdPaymentMethodActivation, - bpdPaymentMethodActivation, - BpdPmActivationStatus, - bpdUpdatePaymentMethodActivation, - HPan -} from "../../actions/paymentMethods"; -import { PaymentMethod } from "../../../../../../types/pagopa"; -import { getPaymentMethodHash } from "../../../../../../utils/paymentMethod"; - -export type BpdPotPaymentMethodActivation = pot.Pot< - BpdPaymentMethodActivation, - Error ->; - -const readPot = ( - hPan: HPan, - data: IndexedById -): BpdPotPaymentMethodActivation => - pipe( - data[hPan], - O.fromNullable, - O.getOrElseW(() => pot.none) - ); - -export const getPaymentStatus = (value: boolean): BpdPmActivationStatus => - value ? "active" : "inactive"; - -/** - * This reducer keep the activation state and the upsert request foreach payment method, - * grouped by hPan. - * Foreach hPan there is a {@link BpdPotPaymentMethodActivation} containing the related bpd activation information. - * TODO: refactor with the common function in IndexedByIdPot - * @param state - * @param action - */ -export const bpdPaymentMethodsReducer = ( - state: IndexedById = {}, - action: Action -): IndexedById => { - switch (action.type) { - case getType(bpdPaymentMethodActivation.request): - return { ...state, [action.payload]: pot.noneLoading }; - case getType(bpdPaymentMethodActivation.success): - return { - ...state, - [action.payload.hPan]: pot.some(action.payload) - }; - case getType(bpdPaymentMethodActivation.failure): - return { - ...state, - [action.payload.hPan]: pot.toError( - readPot(action.payload.hPan, state), - action.payload.error - ) - }; - case getType(bpdUpdatePaymentMethodActivation.request): - const updateRequest = readPot(action.payload.hPan, state); - // write the candidate activationStatus, preserving all the others fields - return { - ...state, - [action.payload.hPan]: pot.toUpdating(updateRequest, { - ...(pot.isSome(updateRequest) - ? updateRequest.value - : { hPan: action.payload.hPan }), - activationStatus: getPaymentStatus(action.payload.value) - }) - }; - case getType(bpdUpdatePaymentMethodActivation.success): - return { ...state, [action.payload.hPan]: pot.some(action.payload) }; - case getType(bpdUpdatePaymentMethodActivation.failure): - const updateFailure = readPot(action.payload.hPan, state); - return { - ...state, - [action.payload.hPan]: pot.toError(updateFailure, action.payload.error) - }; - case getType(bpdDeleteUserFromProgram.success): - case getType(deleteWalletSuccess): - // if the user remove a payment method, we need to invalidate all the store - // because deleteWalletSuccess have Walletv1 as payload (without hash) - return {}; - } - return state; -}; - -/** - * The raw selection of the bpd activation status for a payment method - * @param state - * @param hPan - */ -const bpdPaymentMethodActivationByHPanValue = ( - state: GlobalState, - hPan: HPan -): pot.Pot | undefined => - state.bonus.bpd.details.paymentMethods[hPan]; - -/** - * Return all the activation states for payment methods, memoized - */ -export const bpdPaymentMethodActivationSelector = createSelector< - GlobalState, - IndexedById, - IndexedById ->( - [(state: GlobalState) => state.bonus.bpd.details.paymentMethods], - paymentMethod => paymentMethod -); - -/** - * Return the pot representing the bpd activation status for a payment method. - * It's wrapped with createSelector in order to memoize the value and avoid recalculation - * when the state change. - */ -export const bpdPaymentMethodValueSelector = createSelector( - [bpdPaymentMethodActivationByHPanValue], - potValue => potValue ?? pot.none -); - -/** - * Return true if at least one method from the given ones is BPD active - * @param paymentMethods - */ -export const areAnyPaymentMethodsActiveSelector = ( - paymentMethods: ReadonlyArray -) => - createSelector( - [bpdPaymentMethodActivationSelector], - (bpdPaymentMethodsActivation): boolean => { - const paymentMethodsHash = paymentMethods.map(getPaymentMethodHash); - return paymentMethodsHash.some(pmh => - pipe( - pmh, - O.fromNullable, - O.chainNullableK(h => bpdPaymentMethodsActivation[h]), - O.map(potActivation => - pot.getOrElse( - pot.map(potActivation, p => p.activationStatus === "active"), - false - ) - ), - O.getOrElse(() => false) - ) - ); - } - ); diff --git a/ts/features/bonus/bpd/store/reducers/details/periods.ts b/ts/features/bonus/bpd/store/reducers/details/periods.ts deleted file mode 100644 index 8daa16f2e10..00000000000 --- a/ts/features/bonus/bpd/store/reducers/details/periods.ts +++ /dev/null @@ -1,137 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { createSelector } from "reselect"; -import { getType } from "typesafe-actions"; -import { Action } from "../../../../../../store/actions/types"; -import { IndexedById } from "../../../../../../store/helpers/indexer"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { isDefined } from "../../../../../../utils/guards"; -import { BpdAmount } from "../../../saga/networking/amount"; -import { bpdUnsubscribeCompleted } from "../../actions/onboarding"; -import { - AwardPeriodId, - BpdPeriod, - bpdPeriodsAmountLoad, - WithAwardPeriodId -} from "../../actions/periods"; -import { BpdPivotTransaction } from "./transactionsv2/entities"; - -// The ranking is ready for a specific period -export type BpdRankingReady = WithAwardPeriodId & { - kind: "ready"; - // total number of citizens enrolled in bpd - totalParticipants: number; - // ranking position of the citizen for the specified period - ranking: number; - // number of first-ranked transactions - maxTransactionNumber: number; - // number of last-ranked transactions - minTransactionNumber: number; - // number of transactions made by the citizen for the period - transactionNumber: number; - // new v2 field, containing the trx pivot information - pivot?: BpdPivotTransaction; -}; - -// The ranking is still not ready for a period (eg. a period is just started -// and no transaction has been recorded) -// TODO: we should move all the types definition in the /types/* instead that in the state /action -export type BpdRankingNotReady = WithAwardPeriodId & { kind: "notReady" }; - -export const bpdRankingNotReady = ( - awardPeriodId: AwardPeriodId -): BpdRankingNotReady => ({ - awardPeriodId, - kind: "notReady" -}); - -export type BpdRanking = BpdRankingReady | BpdRankingNotReady; - -export const isBpdRankingReady = (r: BpdRanking): r is BpdRankingReady => - r.kind === "ready"; - -export const isBpdRankingNotReady = (r: BpdRanking): r is BpdRankingNotReady => - r.kind === "notReady"; - -/** - * Combine the period & amount - */ -export type BpdPeriodWithInfo = BpdPeriod & { - amount: BpdAmount; - ranking: BpdRanking; -}; - -/** - * A temporary implementation, based on the actual date - * TODO: remove this method when the new state "Waiting" will be ready - * @param period - */ -export const isGracePeriod = (period: BpdPeriod) => { - if (period.status === "Active" || period.status === "Inactive") { - return false; - } - const today = new Date(); - const endGracePeriod = new Date(period.endDate); - endGracePeriod.setDate(period.endDate.getDate() + period.gracePeriod); - // we are still in the grace period and warns the user that some transactions - // may still be pending - return today <= endGracePeriod && today >= period.endDate; -}; - -/** - * Store all the cashback periods with amounts - * @param state - * @param action - */ -export const bpdPeriodsReducer = ( - state: pot.Pot, Error> = pot.none, - action: Action -): pot.Pot, Error> => { - switch (action.type) { - case getType(bpdPeriodsAmountLoad.request): - return pot.toLoading(state); - case getType(bpdPeriodsAmountLoad.success): - return pot.some( - action.payload.reduce( - (acc: IndexedById, curr: BpdPeriodWithInfo) => ({ - ...acc, - [curr.awardPeriodId]: curr - }), - {} - ) - ); - case getType(bpdPeriodsAmountLoad.failure): - return pot.toError(state, action.payload); - case getType(bpdUnsubscribeCompleted): - return pot.none; - } - - return state; -}; - -/** - * Return the pot.Pot for IndexedById, memoized to avoid recalculations - */ -export const bpdPeriodsSelector = createSelector( - [(state: GlobalState) => state.bonus.bpd.details.periods], - (potValue): pot.Pot, Error> => - pot.map(potValue, potValue => Object.values(potValue).filter(isDefined)) -); - -/** - * Raw selector for a specific period (if exists) - * @param state - * @param id - */ -const bpdPeriodByIdRawSelector = ( - state: GlobalState, - id: AwardPeriodId -): pot.Pot => - pot.map(state.bonus.bpd.details.periods, periodList => periodList[id]); - -/** - * Return a specific period (if exists) - */ -export const bpdPeriodByIdSelector = createSelector( - [bpdPeriodByIdRawSelector], - period => period -); diff --git a/ts/features/bonus/bpd/store/reducers/details/selectedPeriod.ts b/ts/features/bonus/bpd/store/reducers/details/selectedPeriod.ts deleted file mode 100644 index 2635700ee11..00000000000 --- a/ts/features/bonus/bpd/store/reducers/details/selectedPeriod.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { createSelector } from "reselect"; -import { getType } from "typesafe-actions"; -import { Action } from "../../../../../../store/actions/types"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { bpdSelectPeriod } from "../../actions/selectedPeriod"; -import { BpdPeriodWithInfo } from "./periods"; - -/** - * Store the current period selected by the user (current displayed) - * @param state - * @param action - */ -export const bpdSelectedPeriodsReducer = ( - state: BpdPeriodWithInfo | null = null, - action: Action -): BpdPeriodWithInfo | null => { - switch (action.type) { - // The user manually selected a specific period - case getType(bpdSelectPeriod): - return action.payload; - } - return state; -}; - -/** - * Return current period selected by the user (current displayed) - * TODO: if null(generic navigation to cashback), return the current period ( start_date < today < end_date ) - * if no period match the date interval, return the nearest - */ -export const bpdSelectedPeriodSelector = createSelector( - [(state: GlobalState) => state.bonus.bpd.details.selectedPeriod], - (period): BpdPeriodWithInfo | undefined => - period === null ? undefined : period -); diff --git a/ts/features/bonus/bpd/store/reducers/details/transactions.ts b/ts/features/bonus/bpd/store/reducers/details/transactions.ts deleted file mode 100644 index a86ffede937..00000000000 --- a/ts/features/bonus/bpd/store/reducers/details/transactions.ts +++ /dev/null @@ -1,83 +0,0 @@ -import * as O from "fp-ts/lib/Option"; -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { createSelector } from "reselect"; -import { getType } from "typesafe-actions"; -import { pipe } from "fp-ts/lib/function"; -import { Action } from "../../../../../../store/actions/types"; -import { IndexedById } from "../../../../../../store/helpers/indexer"; -import { - toError, - toLoading, - toSome -} from "../../../../../../store/reducers/IndexedByIdPot"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { bpdDeleteUserFromProgram } from "../../actions/onboarding"; -import { AwardPeriodId } from "../../actions/periods"; -import { - BpdTransaction, - bpdTransactionsLoad -} from "../../actions/transactions"; -import { bpdSelectedPeriodSelector } from "./selectedPeriod"; - -/** - * Store the transactions foreach period - * @param state - * @param action - */ -export const bpdTransactionsReducer = ( - state: IndexedById, Error>> = {}, - action: Action -): IndexedById, Error>> => { - switch (action.type) { - case getType(bpdTransactionsLoad.request): - return toLoading(action.payload, state); - case getType(bpdTransactionsLoad.success): - return toSome( - action.payload.awardPeriodId, - state, - action.payload.results - ); - case getType(bpdTransactionsLoad.failure): - return toError(action.payload.awardPeriodId, state, action.payload.error); - case getType(bpdDeleteUserFromProgram.success): - return {}; - } - - return state; -}; - -/** - * The raw selector to read a specific period of transactions - * @param state - * @param periodId - */ -const bpdTransactionsByPeriodSelector = ( - state: GlobalState, - periodId: AwardPeriodId -): pot.Pot, Error> | undefined => - state.bonus.bpd.details.transactions[periodId]; - -/** - * Return the pot.Pot for IndexedById>, memoized to avoid recalculations - */ -export const bpdTransactionsSelector = createSelector( - [bpdTransactionsByPeriodSelector], - maybePotTransactions => maybePotTransactions ?? pot.none -); - -/** - * Return the list of transactions for the selected period (current displayed) - */ -export const bpdTransactionsForSelectedPeriod = createSelector( - [ - (state: GlobalState) => state.bonus.bpd.details.transactions, - bpdSelectedPeriodSelector - ], - (transactions, period) => - pipe( - period, - O.fromNullable, - O.chain(p => O.fromNullable(transactions[p.awardPeriodId])), - O.getOrElseW(() => pot.none) - ) -); diff --git a/ts/features/bonus/bpd/store/reducers/details/transactionsv2/__mock__/transactions.ts b/ts/features/bonus/bpd/store/reducers/details/transactionsv2/__mock__/transactions.ts deleted file mode 100644 index 64bd54b1a5f..00000000000 --- a/ts/features/bonus/bpd/store/reducers/details/transactionsv2/__mock__/transactions.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { WinningTransactionMilestoneResource } from "../../../../../../../../../definitions/bpd/winning_transactions_v2/WinningTransactionMilestoneResource"; -import { AwardPeriodId, WithAwardPeriodId } from "../../../../actions/periods"; - -export const awardPeriodTemplate: WithAwardPeriodId = { - awardPeriodId: 1 as AwardPeriodId -}; - -export const transactionTemplate: WinningTransactionMilestoneResource = { - awardPeriodId: awardPeriodTemplate.awardPeriodId, - idTrx: "1", - idTrxIssuer: "idTrxIssuer", - idTrxAcquirer: "idTrxIssuer", - cashback: 15, - hashPan: "hPan", - circuitType: "01", - trxDate: new Date("2021-01-01"), - amount: 150 -}; diff --git a/ts/features/bonus/bpd/store/reducers/details/transactionsv2/__test__/normalization.test.ts b/ts/features/bonus/bpd/store/reducers/details/transactionsv2/__test__/normalization.test.ts deleted file mode 100644 index 663a8a4ad88..00000000000 --- a/ts/features/bonus/bpd/store/reducers/details/transactionsv2/__test__/normalization.test.ts +++ /dev/null @@ -1,375 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { createStore } from "redux"; -import { WinningTransactionsOfTheDayResource } from "../../../../../../../../../definitions/bpd/winning_transactions_v2/WinningTransactionsOfTheDayResource"; -import { applicationChangeState } from "../../../../../../../../store/actions/application"; -import { appReducer } from "../../../../../../../../store/reducers"; -import { - BpdTransactionId, - bpdTransactionsLoadMilestone, - bpdTransactionsLoadPage, - TrxMilestonePayload -} from "../../../../actions/transactions"; -import { - awardPeriodTemplate, - transactionTemplate -} from "../__mock__/transactions"; -import { BpdTransactionsEntityState } from "../entities"; - -const pageOne: WinningTransactionsOfTheDayResource = { - date: new Date("2021-01-01"), - transactions: [ - transactionTemplate, - { ...transactionTemplate, idTrx: "2", cashback: 0 }, - { ...transactionTemplate, idTrx: "3" } - ] -}; - -const pageTwo: WinningTransactionsOfTheDayResource = { - date: new Date("2021-01-01"), - transactions: [ - { ...transactionTemplate, idTrx: "A" }, - { ...transactionTemplate, idTrx: "B", cashback: 0 }, - { ...transactionTemplate, idTrx: "C" } - ] -}; - -const pageThree: WinningTransactionsOfTheDayResource = { - date: new Date("2021-01-02"), - transactions: [ - { ...transactionTemplate, idTrx: "D" }, - { ...transactionTemplate, idTrx: "E", cashback: 15 } - ] -}; - -const templateMilestone: TrxMilestonePayload = { - ...awardPeriodTemplate, - result: { amount: 0.5, idTrx: "A" as BpdTransactionId } -}; - -type TransactionTestCheck = { - idTrx: string; - cashback: number; - validForCashback: boolean; -}; - -describe("Test the paginated transaction normalization", () => { - it("When no pivot is present, the cashback amount should not be normalized", () => { - const globalState = appReducer(undefined, applicationChangeState("active")); - const store = createStore(appReducer, globalState as any); - store.dispatch(bpdTransactionsLoadPage.request(awardPeriodTemplate)); - store.dispatch( - bpdTransactionsLoadPage.success({ - ...awardPeriodTemplate, - results: { - transactions: [pageOne] - } - }) - ); - - const transactionsEntities = - store.getState().bonus.bpd.details.transactionsV2.entitiesByPeriod[ - awardPeriodTemplate.awardPeriodId - ]; - - const expectedResults = [ - { - idTrx: "1", - cashback: transactionTemplate.cashback, - validForCashback: true - }, - { - idTrx: "2", - cashback: 0, - validForCashback: true - }, - { - idTrx: "3", - cashback: transactionTemplate.cashback, - validForCashback: true - } - ]; - - expect(transactionsEntities).toBeDefined(); - expect(transactionsEntities?.pivot).toBe(pot.none); - if (transactionsEntities) { - verifyTransactions(expectedResults, transactionsEntities); - } - }); - it( - "When a pivot is present and the pivot transaction has not yet been found," + - " the cashback amount should be normalized to zero for all the transactions", - () => { - const globalState = appReducer( - undefined, - applicationChangeState("active") - ); - const store = createStore(appReducer, globalState as any); - - store.dispatch( - bpdTransactionsLoadMilestone.request(awardPeriodTemplate.awardPeriodId) - ); - store.dispatch(bpdTransactionsLoadMilestone.success(templateMilestone)); - - store.dispatch(bpdTransactionsLoadPage.request(awardPeriodTemplate)); - store.dispatch( - bpdTransactionsLoadPage.success({ - ...awardPeriodTemplate, - results: { - transactions: [pageOne] - } - }) - ); - - const transactionsEntities = - store.getState().bonus.bpd.details.transactionsV2.entitiesByPeriod[ - awardPeriodTemplate.awardPeriodId - ]; - - const expectedResults: ReadonlyArray = [ - { - idTrx: "1", - cashback: 0, - validForCashback: false - }, - { - idTrx: "2", - cashback: 0, - validForCashback: false - }, - { - idTrx: "3", - cashback: 0, - validForCashback: false - } - ]; - - expect(transactionsEntities).toBeDefined(); - expect(transactionsEntities?.pivot).toStrictEqual( - pot.some(templateMilestone.result) - ); - if (transactionsEntities) { - verifyTransactions(expectedResults, transactionsEntities); - } - } - ); - - it( - "When a pivot is present and the pivot transaction has been found," + - " the cashback amount should be must be normalized appropriately (same day transactions in multiple pages)", - () => { - const globalState = appReducer( - undefined, - applicationChangeState("active") - ); - const store = createStore(appReducer, globalState as any); - - store.dispatch( - bpdTransactionsLoadMilestone.request(awardPeriodTemplate.awardPeriodId) - ); - store.dispatch(bpdTransactionsLoadMilestone.success(templateMilestone)); - - store.dispatch(bpdTransactionsLoadPage.request(awardPeriodTemplate)); - store.dispatch( - bpdTransactionsLoadPage.success({ - ...awardPeriodTemplate, - results: { - transactions: [pageOne] - } - }) - ); - store.dispatch(bpdTransactionsLoadPage.request(awardPeriodTemplate)); - store.dispatch( - bpdTransactionsLoadPage.success({ - ...awardPeriodTemplate, - results: { - transactions: [pageTwo] - } - }) - ); - store.dispatch(bpdTransactionsLoadPage.request(awardPeriodTemplate)); - store.dispatch( - bpdTransactionsLoadPage.success({ - ...awardPeriodTemplate, - results: { - transactions: [pageThree] - } - }) - ); - - const transactionsEntities = - store.getState().bonus.bpd.details.transactionsV2.entitiesByPeriod[ - awardPeriodTemplate.awardPeriodId - ]; - - const expectedResults: ReadonlyArray = [ - { - idTrx: "1", - cashback: 0, - validForCashback: false - }, - { - idTrx: "2", - cashback: 0, - validForCashback: false - }, - { - idTrx: "3", - cashback: 0, - validForCashback: false - }, - { - idTrx: "A", - cashback: templateMilestone.result?.amount ?? 0, - validForCashback: true - }, - { - idTrx: "B", - cashback: 0, - validForCashback: true - }, - { - idTrx: "C", - cashback: transactionTemplate.cashback, - validForCashback: true - }, - { - idTrx: "D", - cashback: transactionTemplate.cashback, - validForCashback: true - }, - { - idTrx: "E", - cashback: 15, - validForCashback: true - } - ]; - - expect(transactionsEntities).toBeDefined(); - expect(transactionsEntities?.pivot).toStrictEqual( - pot.some(templateMilestone.result) - ); - if (transactionsEntities) { - verifyTransactions(expectedResults, transactionsEntities); - } - } - ); - it( - "When a pivot is present and the pivot transaction has been found," + - " the cashback amount should be must be normalized appropriately (pivot with multiple days in same page)", - () => { - const globalState = appReducer( - undefined, - applicationChangeState("active") - ); - const store = createStore(appReducer, globalState as any); - - store.dispatch( - bpdTransactionsLoadMilestone.request(awardPeriodTemplate.awardPeriodId) - ); - store.dispatch(bpdTransactionsLoadMilestone.success(templateMilestone)); - - store.dispatch(bpdTransactionsLoadPage.request(awardPeriodTemplate)); - store.dispatch( - bpdTransactionsLoadPage.success({ - ...awardPeriodTemplate, - results: { - transactions: [pageOne] - } - }) - ); - store.dispatch(bpdTransactionsLoadPage.request(awardPeriodTemplate)); - store.dispatch( - bpdTransactionsLoadPage.success({ - ...awardPeriodTemplate, - results: { - transactions: [pageTwo, pageThree] - } - }) - ); - - const transactionsEntities = - store.getState().bonus.bpd.details.transactionsV2.entitiesByPeriod[ - awardPeriodTemplate.awardPeriodId - ]; - - const expectedResults: ReadonlyArray = [ - { - idTrx: "1", - cashback: 0, - validForCashback: false - }, - { - idTrx: "2", - cashback: 0, - validForCashback: false - }, - { - idTrx: "3", - cashback: 0, - validForCashback: false - }, - { - idTrx: "A", - cashback: templateMilestone.result?.amount ?? 0, - validForCashback: true - }, - { - idTrx: "B", - cashback: 0, - validForCashback: true - }, - { - idTrx: "C", - cashback: transactionTemplate.cashback, - validForCashback: true - }, - { - idTrx: "D", - cashback: transactionTemplate.cashback, - validForCashback: true - }, - { - idTrx: "E", - cashback: 15, - validForCashback: true - } - ]; - - expect(transactionsEntities).toBeDefined(); - expect(transactionsEntities?.pivot).toStrictEqual( - pot.some(templateMilestone.result) - ); - if (transactionsEntities) { - verifyTransactions(expectedResults, transactionsEntities); - } - } - ); -}); - -expect.extend({ - toBeAsd(received, validator) { - if (validator(received)) { - return { - message: () => `Email ${received} should NOT be valid`, - pass: true - }; - } else { - return { - message: () => `Email ${received} should be valid`, - pass: false - }; - } - } -}); - -const verifyTransactions = ( - trxCheckList: ReadonlyArray, - state: BpdTransactionsEntityState -) => { - trxCheckList.map(x => { - expect(state.byId[x.idTrx]).toBeDefined(); - expect(state.byId[x.idTrx]?.idTrx.toString()).toBe(x.idTrx); - expect(state.byId[x.idTrx]?.cashback).toBe(x.cashback); - expect(state.byId[x.idTrx]?.validForCashback).toBe(x.validForCashback); - }); -}; diff --git a/ts/features/bonus/bpd/store/reducers/details/transactionsv2/__test__/transactions.test.ts b/ts/features/bonus/bpd/store/reducers/details/transactionsv2/__test__/transactions.test.ts deleted file mode 100644 index c4f37db13c2..00000000000 --- a/ts/features/bonus/bpd/store/reducers/details/transactionsv2/__test__/transactions.test.ts +++ /dev/null @@ -1,199 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { createStore } from "redux"; -import { WinningTransactionsOfTheDayResource } from "../../../../../../../../../definitions/bpd/winning_transactions_v2/WinningTransactionsOfTheDayResource"; -import { applicationChangeState } from "../../../../../../../../store/actions/application"; -import { appReducer } from "../../../../../../../../store/reducers"; -import { bpdTransactionsLoadPage } from "../../../../actions/transactions"; -import { - awardPeriodTemplate, - transactionTemplate -} from "../__mock__/transactions"; - -const pageOne: WinningTransactionsOfTheDayResource = { - date: new Date("2021-01-01"), - transactions: [ - transactionTemplate, - { ...transactionTemplate, idTrx: "2" }, - { ...transactionTemplate, idTrx: "3" } - ] -}; - -const pageTwo: WinningTransactionsOfTheDayResource = { - date: new Date("2021-01-01"), - transactions: [ - { ...transactionTemplate, idTrx: "4" }, - { ...transactionTemplate, idTrx: "5" } - ] -}; - -const pageThree: WinningTransactionsOfTheDayResource = { - date: new Date("2021-01-02"), - transactions: [ - { ...transactionTemplate, idTrx: "6" }, - { ...transactionTemplate, idTrx: "7" } - ] -}; - -describe("Test BpdTransactionsV2State store", () => { - it("When the action bpdTransactionsLoadPage.request is dispatched, the store should have the right shape", () => { - const globalState = appReducer(undefined, applicationChangeState("active")); - const store = createStore(appReducer, globalState as any); - store.dispatch(bpdTransactionsLoadPage.request(awardPeriodTemplate)); - - const transactionsStore = store.getState().bonus.bpd.details.transactionsV2; - - expect(transactionsStore.ui.sectionItems).toStrictEqual(pot.noneLoading); - - expect(transactionsStore.ui.awardPeriodId).toStrictEqual( - awardPeriodTemplate.awardPeriodId - ); - }); - it("When the action bpdTransactionsLoadPage.failure is dispatched, the store should have the right shape", () => { - const globalState = appReducer(undefined, applicationChangeState("active")); - const store = createStore(appReducer, globalState as any); - const testError = new Error("Test Error"); - store.dispatch(bpdTransactionsLoadPage.request(awardPeriodTemplate)); - store.dispatch( - bpdTransactionsLoadPage.failure({ - ...awardPeriodTemplate, - error: new Error("Test Error") - }) - ); - - const transactionsStore = store.getState().bonus.bpd.details.transactionsV2; - - expect(transactionsStore.ui.sectionItems).toStrictEqual({ - error: testError, - kind: "PotNoneError" - }); - - expect(transactionsStore.ui.awardPeriodId).toStrictEqual( - awardPeriodTemplate.awardPeriodId - ); - }); - it("When the action bpdTransactionsLoadPage.success is dispatched, the store should have the right shape", () => { - const globalState = appReducer(undefined, applicationChangeState("active")); - const store = createStore(appReducer, globalState as any); - store.dispatch(bpdTransactionsLoadPage.request(awardPeriodTemplate)); - store.dispatch( - bpdTransactionsLoadPage.success({ - ...awardPeriodTemplate, - results: { - transactions: [pageOne] - } - }) - ); - - const expectedTrxId = ["1", "2", "3"]; - const transactionsStore = store.getState().bonus.bpd.details.transactionsV2; - - expect(transactionsStore.ui.sectionItems.kind).toStrictEqual("PotSome"); - - if (pot.isSome(transactionsStore.ui.sectionItems)) { - const dateId = new Date("2021-01-01").toISOString(); - expect(transactionsStore.ui.sectionItems.value[dateId]?.dayInfoId).toBe( - dateId - ); - - expect( - transactionsStore.ui.sectionItems.value[dateId]?.data - ).toStrictEqual(expectedTrxId); - } - expect(transactionsStore.ui.awardPeriodId).toStrictEqual( - awardPeriodTemplate.awardPeriodId - ); - expect(transactionsStore.ui.nextCursor).toBeNull(); - - const entities = - transactionsStore.entitiesByPeriod[awardPeriodTemplate.awardPeriodId]; - - expect(entities).toBeDefined(); - - if (entities) { - expect(entities.foundPivot).toBe(false); - - expect(Object.keys(entities.byId).length).toBe(expectedTrxId.length); - - expectedTrxId.forEach(x => { - expect(entities.byId[x]).toBeDefined(); - expect(entities.byId[x]?.idTrx.toString()).toBe(x); - }); - } - }); - - it("When multiple action bpdTransactionsLoadPage.success are dispatched, the store should have the right shape and add merge correctly the new data", () => { - const globalState = appReducer(undefined, applicationChangeState("active")); - const store = createStore(appReducer, globalState as any); - store.dispatch(bpdTransactionsLoadPage.request(awardPeriodTemplate)); - store.dispatch( - bpdTransactionsLoadPage.success({ - ...awardPeriodTemplate, - results: { - nextCursor: 1, - transactions: [pageOne] - } - }) - ); - store.dispatch( - bpdTransactionsLoadPage.request({ ...awardPeriodTemplate, nextCursor: 1 }) - ); - store.dispatch( - bpdTransactionsLoadPage.success({ - ...awardPeriodTemplate, - results: { - nextCursor: 2, - transactions: [pageTwo, pageThree] - } - }) - ); - - const expectedTrxDayOne = ["1", "2", "3", "4", "5"]; - const expectedTrxDayTwo = ["6", "7"]; - const transactionsStore = store.getState().bonus.bpd.details.transactionsV2; - - expect(transactionsStore.ui.sectionItems.kind).toStrictEqual("PotSome"); - - expect(transactionsStore.ui.awardPeriodId).toStrictEqual( - awardPeriodTemplate.awardPeriodId - ); - - if (pot.isSome(transactionsStore.ui.sectionItems)) { - const dateIdDayOne = new Date("2021-01-01").toISOString(); - const dateIdDayTwo = new Date("2021-01-02").toISOString(); - expect( - transactionsStore.ui.sectionItems.value[dateIdDayOne]?.dayInfoId - ).toBe(dateIdDayOne); - expect( - transactionsStore.ui.sectionItems.value[dateIdDayTwo]?.dayInfoId - ).toBe(dateIdDayTwo); - - expect( - transactionsStore.ui.sectionItems.value[dateIdDayOne]?.data - ).toStrictEqual(expectedTrxDayOne); - - expect( - transactionsStore.ui.sectionItems.value[dateIdDayTwo]?.data - ).toStrictEqual(expectedTrxDayTwo); - } - - expect(transactionsStore.ui.nextCursor).toBe(2); - - const entities = - transactionsStore.entitiesByPeriod[awardPeriodTemplate.awardPeriodId]; - - expect(entities).toBeDefined(); - - if (entities) { - const expectedEntitiesId = expectedTrxDayOne.concat(expectedTrxDayTwo); - - expect(entities.foundPivot).toBe(false); - - expect(Object.keys(entities.byId).length).toBe(expectedEntitiesId.length); - - expectedEntitiesId.forEach(x => { - expect(entities.byId[x]).toBeDefined(); - expect(entities.byId[x]?.idTrx.toString()).toBe(x); - }); - } - }); -}); diff --git a/ts/features/bonus/bpd/store/reducers/details/transactionsv2/daysInfo.ts b/ts/features/bonus/bpd/store/reducers/details/transactionsv2/daysInfo.ts deleted file mode 100644 index 66cb350bb96..00000000000 --- a/ts/features/bonus/bpd/store/reducers/details/transactionsv2/daysInfo.ts +++ /dev/null @@ -1,145 +0,0 @@ -import * as O from "fp-ts/lib/Option"; -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { createSelector } from "reselect"; -import { getType } from "typesafe-actions"; -import { pipe } from "fp-ts/lib/function"; -import { Action } from "../../../../../../../store/actions/types"; -import { - IndexedById, - toArray, - toIndexed -} from "../../../../../../../store/helpers/indexer"; -import { GlobalState } from "../../../../../../../store/reducers/types"; -import { AwardPeriodId } from "../../../actions/periods"; -import { - bpdTransactionsLoadCountByDay, - bpdTransactionsLoadRequiredData -} from "../../../actions/transactions"; -import { bpdSelectedPeriodSelector } from "../selectedPeriod"; - -export type BpdTransactionsDayInfo = { - trxDate: Date; - count: number; -}; - -export type BpdTransactionsDaysInfoState = { - // the DaysInfo are requested in bulk for a specific period, for this reason all the IndexedById object is a pot - byId: pot.Pot, Error>; -}; - -const initState: BpdTransactionsDaysInfoState = { - byId: pot.none -}; - -/** - * Update the byId entry for the selected period - * @param input - * @param period - * @param newVal - */ -const updateById = ( - input: IndexedById, - period: AwardPeriodId, - newVal: pot.Pot, Error> -): IndexedById => ({ - ...input, - [period]: { - byId: newVal - } -}); - -/** - * Get the BpdTransactionsDaysInfoState for a specific period - * @param state - * @param id - */ -const getPeriodEntry = ( - state: IndexedById, - id: AwardPeriodId -): BpdTransactionsDaysInfoState => state[id] ?? initState; - -export const bpdTransactionsDaysInfoReducer = ( - state: IndexedById = {}, - action: Action -): IndexedById => { - switch (action.type) { - case getType(bpdTransactionsLoadRequiredData.request): - return {}; - case getType(bpdTransactionsLoadCountByDay.request): - return updateById( - state, - action.payload, - pot.toLoading(getPeriodEntry(state, action.payload).byId) - ); - case getType(bpdTransactionsLoadCountByDay.success): - return updateById( - state, - action.payload.awardPeriodId, - pot.some( - toIndexed(action.payload.results, x => x.trxDate.toISOString()) - ) - ); - case getType(bpdTransactionsLoadCountByDay.failure): - const periodIdError = action.payload.awardPeriodId; - return updateById( - state, - periodIdError, - pot.toError( - getPeriodEntry(state, periodIdError).byId, - action.payload.error - ) - ); - } - return state; -}; - -/** - * Return the pot.Pot, Error>, for the selected period - */ -export const bpdDaysInfoForSelectedPeriodSelector = createSelector( - [ - (state: GlobalState) => - state.bonus.bpd.details.transactionsV2.daysInfoByPeriod, - bpdSelectedPeriodSelector - ], - ( - daysInfoByPeriod, - selectedPeriod - ): pot.Pot, Error> => - pot.map( - pipe( - selectedPeriod, - O.fromNullable, - O.chain(periodId => - O.fromNullable(daysInfoByPeriod[periodId.awardPeriodId]?.byId) - ), - O.getOrElseW(() => pot.none) - ), - byId => toArray(byId) - ) -); - -/** - * From id to Option - */ -export const bpdDaysInfoByIdSelector = createSelector( - [ - (state: GlobalState) => - state.bonus.bpd.details.transactionsV2.ui.awardPeriodId, - (state: GlobalState) => - state.bonus.bpd.details.transactionsV2.daysInfoByPeriod, - (_: GlobalState, daysInfoId: string) => daysInfoId - ], - ( - awardPeriodId, - daysInfoByPeriod, - daysInfoId - ): O.Option => - pipe( - awardPeriodId, - O.fromNullable, - O.chain(periodId => O.fromNullable(daysInfoByPeriod[periodId]?.byId)), - O.chain(pot.toOption), - O.chain(daysInfoById => O.fromNullable(daysInfoById[daysInfoId])) - ) -); diff --git a/ts/features/bonus/bpd/store/reducers/details/transactionsv2/entities.ts b/ts/features/bonus/bpd/store/reducers/details/transactionsv2/entities.ts deleted file mode 100644 index 265b6291bbd..00000000000 --- a/ts/features/bonus/bpd/store/reducers/details/transactionsv2/entities.ts +++ /dev/null @@ -1,238 +0,0 @@ -import * as O from "fp-ts/lib/Option"; -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { createSelector } from "reselect"; -import { getType } from "typesafe-actions"; -import { pipe } from "fp-ts/lib/function"; -import { WinningTransactionMilestoneResource } from "../../../../../../../../definitions/bpd/winning_transactions_v2/WinningTransactionMilestoneResource"; -import { WinningTransactionPageResource } from "../../../../../../../../definitions/bpd/winning_transactions_v2/WinningTransactionPageResource"; -import { Action } from "../../../../../../../store/actions/types"; -import { - IndexedById, - toArray, - toIndexed -} from "../../../../../../../store/helpers/indexer"; -import { GlobalState } from "../../../../../../../store/reducers/types"; -import { convertCircuitTypeCode } from "../../../../saga/networking/transactions"; -import { HPan } from "../../../actions/paymentMethods"; -import { AwardPeriodId } from "../../../actions/periods"; -import { - BpdTransactionId, - bpdTransactionsLoadMilestone, - bpdTransactionsLoadPage, - bpdTransactionsLoadRequiredData, - BpdTransactionV2 -} from "../../../actions/transactions"; -import { bpdSelectedPeriodSelector } from "../selectedPeriod"; - -export type BpdPivotTransaction = { - idTrx: BpdTransactionId; - amount: number; -}; - -export type BpdTransactionsEntityState = { - pivot: pot.Pot; - byId: IndexedById; - foundPivot: boolean; -}; -type NormalizedTransactions = { - data: ReadonlyArray; - found: boolean; -}; - -const initState: BpdTransactionsEntityState = { - pivot: pot.none, - foundPivot: false, - byId: {} -}; - -const getPeriodEntry = ( - state: IndexedById, - id: AwardPeriodId -): BpdTransactionsEntityState => state[id] ?? initState; - -const updatePeriodEntry = ( - input: IndexedById, - period: AwardPeriodId, - newVal: BpdTransactionsEntityState -): IndexedById => ({ - ...input, - [period]: newVal -}); - -/** - * Normalize the cashback amount for the transactions, using as ref the pivot transaction - * - pivot === null, the user did not reach the max cashback amount and all the transactions are valid - * - pivot !== null && not found, the transactions have 0 value - * - pivot !== null && not found && idTrx === pivot.id, pivot transaction found, the pivot transaction have pivot.amount - * - pivot !== null && found, the cashback transaction has the complete value - * @param transactions - * @param pivot - * @param foundPivot - */ -const normalizeCashback = ( - transactions: ReadonlyArray, - pivot: BpdPivotTransaction | null, - foundPivot: boolean -): NormalizedTransactions => { - // eslint-disable-next-line functional/no-let - let found = foundPivot; - return { - data: transactions.map(x => { - // prepare the base BpdTransactionV2, with the right types - const trxV2WithCircuit: BpdTransactionV2 = { - ...x, - circuitType: convertCircuitTypeCode(x.circuitType), - awardPeriodId: x.awardPeriodId as AwardPeriodId, - hashPan: x.hashPan as HPan, - validForCashback: false, - idTrx: x.idTrx as BpdTransactionId, - isPivot: false - }; - - if (found || pivot === null) { - return { ...trxV2WithCircuit, validForCashback: true }; - } - if (x.idTrx === pivot.idTrx) { - found = true; - return { - ...trxV2WithCircuit, - cashback: pivot.amount, - validForCashback: true, - isPivot: true - }; - } - - return { ...trxV2WithCircuit, cashback: 0, validForCashback: false }; - }), - found - }; -}; - -/** - * Add a new page data to BpdTransactionsEntityState for a specific period, updating it - * @param state - * @param newPage - */ -const updateTransactions = ( - state: BpdTransactionsEntityState, - newPage: WinningTransactionPageResource -): BpdTransactionsEntityState => { - // eslint-disable-next-line functional/no-let - let foundPivot = state.foundPivot; - - const pivot = pot.getOrElse(state.pivot, null); - const flatTransactions = newPage.transactions.reduce< - IndexedById - >((acc, val) => { - // calculate the normalization for the new page - const transactionsNormalized = normalizeCashback( - val.transactions, - pivot, - foundPivot - ); - // update the foundPivot - if (!foundPivot && transactionsNormalized.found) { - foundPivot = true; - } - return { - ...acc, - ...toIndexed(transactionsNormalized.data, x => x.idTrx) - } as IndexedById; - }, {} as IndexedById); - - // append the new normalized transactions to the previously calculated and update the foundPivot - return { - ...state, - foundPivot, - byId: { ...state.byId, ...flatTransactions } - }; -}; - -export const bpdTransactionsEntityReducer = ( - state: IndexedById = {}, - action: Action -): IndexedById => { - switch (action.type) { - case getType(bpdTransactionsLoadRequiredData.request): - return {}; - case getType(bpdTransactionsLoadPage.success): - return updatePeriodEntry( - state, - action.payload.awardPeriodId, - updateTransactions( - getPeriodEntry(state, action.payload.awardPeriodId), - action.payload.results - ) - ); - - case getType(bpdTransactionsLoadMilestone.request): - const periodMilestoneRequest = getPeriodEntry(state, action.payload); - return { - ...state, - [action.payload]: { - ...periodMilestoneRequest, - pivot: pot.toLoading(periodMilestoneRequest.pivot) - } - }; - case getType(bpdTransactionsLoadMilestone.success): - // When receive a new pivot, should refresh any existing transaction - // This should never happens but is present in order to avoid inconsistent state - const periodMilestoneSuccess = getPeriodEntry( - state, - action.payload.awardPeriodId - ); - const { data, found } = normalizeCashback( - toArray(periodMilestoneSuccess.byId), - action.payload.result ?? null, - periodMilestoneSuccess.foundPivot - ); - return { - ...state, - [action.payload.awardPeriodId]: { - ...periodMilestoneSuccess, - pivot: pot.some(action.payload.result ?? null), - byId: toIndexed(data, t => t.idTrx), - foundPivot: found - } - }; - - case getType(bpdTransactionsLoadMilestone.failure): - const periodMilestoneFailure = getPeriodEntry( - state, - action.payload.awardPeriodId - ); - return { - ...state, - [action.payload.awardPeriodId]: { - ...periodMilestoneFailure, - pivot: pot.toError(periodMilestoneFailure.pivot, action.payload.error) - } - }; - } - - return state; -}; - -/** - * Return the pot.Pot, for the selected period - */ -export const bpdTransactionsPivotForSelectedPeriodSelector = createSelector( - [ - (state: GlobalState) => - state.bonus.bpd.details.transactionsV2.entitiesByPeriod, - bpdSelectedPeriodSelector - ], - ( - bpdTransactionsEntity, - maybeSelectedPeriod - ): pot.Pot => - pipe( - maybeSelectedPeriod, - O.fromNullable, - O.chain(selectedPeriod => - O.fromNullable(bpdTransactionsEntity[selectedPeriod.awardPeriodId]) - ), - O.map(x => x.pivot), - O.getOrElseW(() => pot.none) - ) -); diff --git a/ts/features/bonus/bpd/store/reducers/details/transactionsv2/index.ts b/ts/features/bonus/bpd/store/reducers/details/transactionsv2/index.ts deleted file mode 100644 index 29c278b0f02..00000000000 --- a/ts/features/bonus/bpd/store/reducers/details/transactionsv2/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { combineReducers } from "redux"; -import { Action } from "../../../../../../../store/actions/types"; -import { IndexedById } from "../../../../../../../store/helpers/indexer"; -import { - bpdTransactionsDaysInfoReducer, - BpdTransactionsDaysInfoState -} from "./daysInfo"; -import { - bpdTransactionsEntityReducer, - BpdTransactionsEntityState -} from "./entities"; -import { bpdTransactionsUiReducer, BpdTransactionsUiState } from "./ui"; - -export type BpdTransactionsV2State = { - // daysInfoState, groupBy period - daysInfoByPeriod: IndexedById; - // transactionsEntitiesState, groupBy period - entitiesByPeriod: IndexedById; - // Ui state for transactions details - ui: BpdTransactionsUiState; -}; - -export const bpdTransactionsV2Reducer = combineReducers< - BpdTransactionsV2State, - Action ->({ - daysInfoByPeriod: bpdTransactionsDaysInfoReducer, - entitiesByPeriod: bpdTransactionsEntityReducer, - ui: bpdTransactionsUiReducer -}); diff --git a/ts/features/bonus/bpd/store/reducers/details/transactionsv2/ui.ts b/ts/features/bonus/bpd/store/reducers/details/transactionsv2/ui.ts deleted file mode 100644 index c00e6219644..00000000000 --- a/ts/features/bonus/bpd/store/reducers/details/transactionsv2/ui.ts +++ /dev/null @@ -1,268 +0,0 @@ -import * as AR from "fp-ts/lib/Array"; -import * as O from "fp-ts/lib/Option"; -import * as pot from "@pagopa/ts-commons/lib/pot"; -import _ from "lodash"; -import { createSelector } from "reselect"; -import { getType } from "typesafe-actions"; -import { pipe } from "fp-ts/lib/function"; -import { WinningTransactionPageResource } from "../../../../../../../../definitions/bpd/winning_transactions_v2/WinningTransactionPageResource"; -import { cardIcons } from "../../../../../../../components/wallet/card/Logo"; -import { Action } from "../../../../../../../store/actions/types"; -import { - IndexedById, - toArray -} from "../../../../../../../store/helpers/indexer"; -import { GlobalState } from "../../../../../../../store/reducers/types"; -import { paymentMethodsSelector } from "../../../../../../../store/reducers/wallet/wallets"; -import { FOUR_UNICODE_CIRCLES } from "../../../../../../../utils/wallet"; -import { BpdTransactionDetailRepresentationV2 } from "../../../../screens/details/transaction/detail/BpdTransactionDetailComponent"; -import { AwardPeriodId } from "../../../actions/periods"; -import { - BpdTransactionId, - bpdTransactionsLoadPage, - bpdTransactionsLoadRequiredData -} from "../../../actions/transactions"; -import { pickPaymentMethodFromHashpan } from "../combiner"; -import { bpdSelectedPeriodSelector } from "../selectedPeriod"; - -/** - * The type that represents a section item that will be rendered with a SectionList. - * Each section contains an header (dayInfoId) and a list of trxId (data) - */ -export type BpdTransactionsSectionItem = { - dayInfoId: string; - data: ReadonlyArray; -}; - -/** - * Describe this store section, dedicated to rendering paginated transactions. - */ -export type BpdTransactionsUiState = { - nextCursor: number | null; - awardPeriodId: AwardPeriodId | null; - requiredDataLoaded: pot.Pot; - sectionItems: pot.Pot, Error>; - // Is the date of the first transaction of the first page (aka: the most recent transaction date) - lastTransactionDate: Date | null; -}; - -/** - * Extract the ids from the received payload - * @param page - */ -const fromWinningTransactionPageResourceToBpdTransactionsSectionItem = ( - page: WinningTransactionPageResource -): IndexedById => - page.transactions.reduce>( - (acc, val) => ({ - ...acc, - [val.date.toISOString()]: { - dayInfoId: val.date.toISOString(), - data: val.transactions.map(trx => trx.idTrx as BpdTransactionId) - } - }), - {} - ); - -/** - * Helper function for mergeWith, in order to merge the inner list of transactions - * @param obj - * @param dst - */ -const customizer = ( - obj: BpdTransactionsSectionItem | undefined, - dst: BpdTransactionsSectionItem | undefined -): BpdTransactionsSectionItem | undefined => { - if (obj !== undefined && dst !== undefined) { - return { - dayInfoId: dst.dayInfoId, - data: obj.data.concat(dst.data) - }; - } - - if (obj === undefined && dst !== undefined) { - return dst; - } - return undefined; -}; - -const initState: BpdTransactionsUiState = { - awardPeriodId: null, - sectionItems: pot.none, - requiredDataLoaded: pot.none, - nextCursor: null, - lastTransactionDate: null -}; - -export const bpdTransactionsUiReducer = ( - state: BpdTransactionsUiState = initState, - action: Action -): BpdTransactionsUiState => { - switch (action.type) { - case getType(bpdTransactionsLoadPage.request): - return { - ...state, - awardPeriodId: action.payload.awardPeriodId, - sectionItems: - action.payload.awardPeriodId !== state.awardPeriodId - ? pot.noneLoading - : pot.toLoading(state.sectionItems) - }; - case getType(bpdTransactionsLoadPage.success): - const currentSectionItems = pot.getOrElse(state.sectionItems, {}); - const newSectionItems = - fromWinningTransactionPageResourceToBpdTransactionsSectionItem( - action.payload.results - ); - - return { - ...state, - awardPeriodId: action.payload.awardPeriodId, - // If lastTransactionDate is null, pick the first transaction date - lastTransactionDate: - state.lastTransactionDate ?? - pipe( - AR.head([...action.payload.results.transactions]), - O.chain(x => AR.head([...x.transactions])), - O.map(y => y.trxDate), - O.toNullable - ), - nextCursor: action.payload.results.nextCursor ?? null, - sectionItems: pot.some( - _.mergeWith(currentSectionItems, newSectionItems, customizer) - ) - }; - case getType(bpdTransactionsLoadPage.failure): - return { - ...state, - awardPeriodId: action.payload.awardPeriodId, - nextCursor: state.nextCursor, - sectionItems: pot.toError(state.sectionItems, action.payload.error) - }; - - case getType(bpdTransactionsLoadRequiredData.request): - return { ...initState, awardPeriodId: action.payload }; - case getType(bpdTransactionsLoadRequiredData.success): - return { - ...state, - requiredDataLoaded: pot.some(true) - }; - case getType(bpdTransactionsLoadRequiredData.failure): - return { - ...state, - requiredDataLoaded: pot.toError( - state.requiredDataLoaded, - action.payload.error - ) - }; - } - - return state; -}; - -/** - * Return the remote state for all the required data to load the transaction screen. - * Return always pot.none if the selectedPeriod is different from the local currentPeriod (a different period is chosen, need to reload) - */ -export const bpdTransactionsRequiredDataLoadStateSelector = createSelector( - [ - (state: GlobalState) => - state.bonus.bpd.details.transactionsV2.ui.requiredDataLoaded, - (state: GlobalState) => - state.bonus.bpd.details.transactionsV2.ui.awardPeriodId, - bpdSelectedPeriodSelector - ], - ( - potRequiredData, - currentPeriodId, - maybeSelectedPeriod - ): pot.Pot => - pipe( - maybeSelectedPeriod, - O.fromNullable, - O.chain(selectedPeriod => - selectedPeriod.awardPeriodId === currentPeriodId - ? O.some(potRequiredData) - : O.none - ), - O.getOrElseW(() => pot.none) - ) -); - -/** - * Return the {@link Date} of the most recent transaction - */ -export const bpdLastTransactionUpdateSelector = createSelector( - [ - (state: GlobalState) => - state.bonus.bpd.details.transactionsV2.ui.lastTransactionDate - ], - (lastTransactionDate): O.Option => O.fromNullable(lastTransactionDate) -); - -/** - * Return the Pot List of BpdTransactionsSectionItem - */ -export const bpdTransactionsSelector = createSelector( - [ - (state: GlobalState) => - state.bonus.bpd.details.transactionsV2.ui.sectionItems - ], - (sectionItems): pot.Pot, Error> => - pot.map(sectionItems, si => toArray(si)) -); - -export const bpdTransactionsGetNextCursor = createSelector( - [ - (state: GlobalState) => state.bonus.bpd.details.transactionsV2.ui.nextCursor - ], - cursor => cursor -); - -/** - * From BpdTransactionId to Option - */ -export const bpdTransactionByIdSelector = createSelector( - [ - (state: GlobalState) => - state.bonus.bpd.details.transactionsV2.ui.awardPeriodId, - (state: GlobalState) => - state.bonus.bpd.details.transactionsV2.entitiesByPeriod, - paymentMethodsSelector, - bpdSelectedPeriodSelector, - (_: GlobalState, trxId: BpdTransactionId) => trxId - ], - ( - awardPeriodId, - entitiesByPeriod, - paymentMethods, - selectedPeriod, - trxId - ): O.Option => - pipe( - awardPeriodId, - O.fromNullable, - O.chain(periodId => - pipe( - entitiesByPeriod[periodId]?.byId[trxId], - O.fromNullable, - O.map(trx => ({ - ...trx, - image: pipe( - pickPaymentMethodFromHashpan(trx.hashPan, paymentMethods), - O.map(pm => pm.icon), - O.getOrElse(() => cardIcons.UNKNOWN) - ), - title: pipe( - pickPaymentMethodFromHashpan(trx.hashPan, paymentMethods), - O.map(pm => pm.caption), - O.getOrElse(() => FOUR_UNICODE_CIRCLES) - ), - keyId: trx.idTrx, - maxCashbackForTransactionAmount: - selectedPeriod?.maxTransactionCashback - })) - ) - ) - ) -); diff --git a/ts/features/bonus/bpd/store/reducers/index.ts b/ts/features/bonus/bpd/store/reducers/index.ts deleted file mode 100644 index 869ac998fd9..00000000000 --- a/ts/features/bonus/bpd/store/reducers/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Action, combineReducers } from "redux"; -import bpdDetailsReducer, { BpdDetailsState } from "./details"; -import bpdOnboardingReducer, { BpdOnboardingState } from "./onboarding"; - -export type BpdState = { - details: BpdDetailsState; - onboarding: BpdOnboardingState; -}; - -const bpdReducer = combineReducers({ - details: bpdDetailsReducer, - onboarding: bpdOnboardingReducer -}); - -export default bpdReducer; diff --git a/ts/features/bonus/bpd/store/reducers/onboarding/enroll.ts b/ts/features/bonus/bpd/store/reducers/onboarding/enroll.ts deleted file mode 100644 index bc86ae7a47d..00000000000 --- a/ts/features/bonus/bpd/store/reducers/onboarding/enroll.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { getType } from "typesafe-actions"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { - remoteError, - remoteLoading, - remoteReady, - remoteUndefined, - RemoteValue -} from "../../../../../../common/model/RemoteValue"; -import { bpdEnrollUserToProgram } from "../../actions/onboarding"; -import { Action } from "../../../../../../store/actions/types"; - -/** - * This reducers use the action {@link bpdEnrollUserToProgram} to save&update the result of the enrollment operation - * @param state - * @param action - */ -const bpdEnrollUserReducer = ( - state: RemoteValue = remoteUndefined, - action: Action -): RemoteValue => { - switch (action.type) { - case getType(bpdEnrollUserToProgram.request): - return remoteLoading; - case getType(bpdEnrollUserToProgram.success): - return remoteReady(action.payload.enabled); - case getType(bpdEnrollUserToProgram.failure): - return remoteError(action.payload); - } - return state; -}; - -export const bpdEnrollSelector = ( - state: GlobalState -): RemoteValue => state.bonus.bpd.onboarding.enrollment; - -export default bpdEnrollUserReducer; diff --git a/ts/features/bonus/bpd/store/reducers/onboarding/index.ts b/ts/features/bonus/bpd/store/reducers/onboarding/index.ts deleted file mode 100644 index 987899a2643..00000000000 --- a/ts/features/bonus/bpd/store/reducers/onboarding/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Action, combineReducers } from "redux"; -import { RemoteValue } from "../../../../../../common/model/RemoteValue"; -import bpdEnrollUserReducer from "./enroll"; -import ongoingOnboardingReducer from "./ongoing"; - -export type BpdOnboardingState = { - enrollment: RemoteValue; - ongoing: boolean; -}; - -const bpdOnboardingReducer = combineReducers({ - enrollment: bpdEnrollUserReducer, - ongoing: ongoingOnboardingReducer -}); - -export default bpdOnboardingReducer; diff --git a/ts/features/bonus/bpd/store/reducers/onboarding/ongoing.ts b/ts/features/bonus/bpd/store/reducers/onboarding/ongoing.ts deleted file mode 100644 index 48f75af59e2..00000000000 --- a/ts/features/bonus/bpd/store/reducers/onboarding/ongoing.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { getType } from "typesafe-actions"; -import { Action } from "../../../../../../store/actions/types"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { bpdIbanInsertionCancel } from "../../actions/iban"; -import { - bpdOnboardingCancel, - bpdOnboardingCompleted, - bpdUserActivate -} from "../../actions/onboarding"; - -/** - * This reducers is used to know if a current onboarding is ongoing - * @param state - * @param action - */ -const ongoingOnboardingReducer = ( - state: boolean = false, - action: Action -): boolean => { - switch (action.type) { - case getType(bpdUserActivate): - return true; - case getType(bpdOnboardingCancel): - case getType(bpdOnboardingCompleted): - case getType(bpdIbanInsertionCancel): - return false; - } - return state; -}; - -/** - * Return true if the user is the ongoing workflow - * @param state - */ -export const isBpdOnboardingOngoing = (state: GlobalState) => - state.bonus.bpd.onboarding.ongoing; - -export default ongoingOnboardingReducer; diff --git a/ts/features/bonus/bpd/types/PatchedWinningTransactionResource.ts b/ts/features/bonus/bpd/types/PatchedWinningTransactionResource.ts deleted file mode 100644 index b9eaeee29b2..00000000000 --- a/ts/features/bonus/bpd/types/PatchedWinningTransactionResource.ts +++ /dev/null @@ -1,46 +0,0 @@ -import * as t from "io-ts"; -import { DateFromISOString } from "../../../../utils/dates"; - -/** - * Patched version of bpd/winning_transactions/WinningTransactionResource - * - trxDate uses DateFromISOStringType instead of UTCISODateFromString (can't decode datetime with offset, ex 2020-10-23T11:13:07.442+02:00) - */ - -// required attributes -const PatchedWinningTransactionResourceR = t.interface({ - amount: t.number, - - awardPeriodId: t.Integer, - - cashback: t.number, - - circuitType: t.string, - - hashPan: t.string, - - idTrxAcquirer: t.string, - - idTrxIssuer: t.string, - - trxDate: DateFromISOString -}); - -// optional attributes -const PatchedWinningTransactionResourceO = t.partial({}); - -export const PatchedWinningTransactionResource = t.intersection( - [PatchedWinningTransactionResourceR, PatchedWinningTransactionResourceO], - "PatchedWinningTransactionResource" -); - -export type PatchedWinningTransactionResource = t.TypeOf< - typeof PatchedWinningTransactionResource ->; - -export const PatchedBpdWinningTransactions = t.readonlyArray( - PatchedWinningTransactionResource -); - -export type PatchedBpdWinningTransactions = t.TypeOf< - typeof PatchedBpdWinningTransactions ->; diff --git a/ts/features/bonus/bpd/utils/__test__/dates.test.ts b/ts/features/bonus/bpd/utils/__test__/dates.test.ts deleted file mode 100644 index 5a63330563e..00000000000 --- a/ts/features/bonus/bpd/utils/__test__/dates.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import MockDate from "mockdate"; -import { isInGracePeriod } from "../dates"; - -describe("isInGracePeriod", () => { - it("should return true if actual date is between endDate and gracePeriod date", () => { - const endDate = new Date(2020, 11, 31); - MockDate.set("2021-01-08"); - expect(isInGracePeriod(endDate, 10)).toBeTruthy(); - }); - - it("should return false if actual date is before endDate", () => { - const endDate = new Date(2020, 11, 31); - MockDate.set("2020-12-29"); - expect(isInGracePeriod(endDate, 10)).toBeFalsy(); - }); - - it("should return false if actual date is after endDate + gracePeriod", () => { - const endDate = new Date(2020, 11, 31); - MockDate.set("2021-01-29"); - expect(isInGracePeriod(endDate, 10)).toBeFalsy(); - }); - - it("should return false if invalid endDate is passed", () => { - const endDate = new Date(NaN); - MockDate.set("2021-01-29"); - expect(isInGracePeriod(endDate, 10)).toBeFalsy(); - }); - - it("should return false if invalid gracePeriod value is passed", () => { - const endDate = new Date(2020, 11, 31); - MockDate.set("2021-01-29"); - expect(isInGracePeriod(endDate, NaN)).toBeFalsy(); - }); -}); diff --git a/ts/features/bonus/bpd/utils/dates.ts b/ts/features/bonus/bpd/utils/dates.ts deleted file mode 100644 index 4ef0d3a12a8..00000000000 --- a/ts/features/bonus/bpd/utils/dates.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Utility function to determine if we are currently in the grace time of a cashback period - * @param endDate - * @param gracePeriod - */ -export const isInGracePeriod = (endDate: Date, gracePeriod: number) => { - if (isNaN(endDate.getTime()) || isNaN(gracePeriod)) { - return false; - } - - const actualDate = new Date(); - const gracePeriodDate = new Date(endDate.getTime()); - gracePeriodDate.setDate(endDate.getDate() + gracePeriod); - - return ( - actualDate.getTime() >= endDate.getTime() && - actualDate.getTime() <= gracePeriodDate.getTime() - ); -}; diff --git a/ts/features/bonus/cgn/store/reducers/unsubscribe.ts b/ts/features/bonus/cgn/store/reducers/unsubscribe.ts index dac49367fea..8e76d74766d 100644 --- a/ts/features/bonus/cgn/store/reducers/unsubscribe.ts +++ b/ts/features/bonus/cgn/store/reducers/unsubscribe.ts @@ -15,7 +15,7 @@ import { cgnActivationComplete } from "../actions/activation"; export type CgnUnsubscribeState = RemoteValue; /** - * Keep the state of "unsubscribe" from bpd outcome + * Keep the state of "unsubscribe" from bonus outcome * @param state * @param action */ diff --git a/ts/features/bonus/bpd/screens/onboarding/declaration/DeclarationEntry.tsx b/ts/features/bonus/common/components/DeclarationEntry.tsx similarity index 86% rename from ts/features/bonus/bpd/screens/onboarding/declaration/DeclarationEntry.tsx rename to ts/features/bonus/common/components/DeclarationEntry.tsx index 64055b62486..fea7b09b00d 100644 --- a/ts/features/bonus/bpd/screens/onboarding/declaration/DeclarationEntry.tsx +++ b/ts/features/bonus/common/components/DeclarationEntry.tsx @@ -1,9 +1,9 @@ import * as React from "react"; import { View, StyleSheet, TouchableWithoutFeedback } from "react-native"; import { HSpacer, VSpacer } from "@pagopa/io-app-design-system"; -import { Body } from "../../../../../../components/core/typography/Body"; -import { XOR } from "../../../../../../types/utils"; -import { RawCheckBox } from "../../../../../../components/core/selection/checkbox/RawCheckBox"; +import { Body } from "../../../../components/core/typography/Body"; +import { XOR } from "../../../../types/utils"; +import { RawCheckBox } from "../../../../components/core/selection/checkbox/RawCheckBox"; const styles = StyleSheet.create({ main: { flex: 1, flexDirection: "row", flexWrap: "nowrap" }, diff --git a/ts/features/bonus/bpd/screens/details/components/summary/base/ProgressBar.tsx b/ts/features/bonus/common/components/ProgressBar.tsx similarity index 100% rename from ts/features/bonus/bpd/screens/details/components/summary/base/ProgressBar.tsx rename to ts/features/bonus/common/components/ProgressBar.tsx diff --git a/ts/features/bonus/common/screens/AvailableBonusScreen.tsx b/ts/features/bonus/common/screens/AvailableBonusScreen.tsx index d4720d971d6..ec4b6bf18ef 100644 --- a/ts/features/bonus/common/screens/AvailableBonusScreen.tsx +++ b/ts/features/bonus/common/screens/AvailableBonusScreen.tsx @@ -43,7 +43,6 @@ import { GlobalState } from "../../../../store/reducers/types"; import variables from "../../../../theme/variables"; import { storeUrl } from "../../../../utils/appVersion"; import { showToast } from "../../../../utils/showToast"; -import { bpdOnboardingStart } from "../../bpd/store/actions/onboarding"; import { cgnActivationStart } from "../../cgn/store/actions/activation"; import { AvailableBonusItem, @@ -261,7 +260,6 @@ const mapStateToProps = (state: GlobalState) => ({ const mapDispatchToProps = (dispatch: Dispatch) => ({ navigateBack: () => navigateBack(), loadAvailableBonuses: () => dispatch(loadAvailableBonuses.request()), - startBpdOnboarding: () => dispatch(bpdOnboardingStart()), startCgnActivation: () => dispatch(cgnActivationStart()), navigateToServiceDetailsScreen: ( params: ServiceDetailsScreenNavigationParams diff --git a/ts/features/bonus/common/store/actions/index.ts b/ts/features/bonus/common/store/actions/index.ts index 2b1426148da..645de11800f 100644 --- a/ts/features/bonus/common/store/actions/index.ts +++ b/ts/features/bonus/common/store/actions/index.ts @@ -1,4 +1,3 @@ -import { BpdActions } from "../../../bpd/store/actions"; import { CdcActions } from "../../../cdc/store/actions"; import { CgnActions } from "../../../cgn/store/actions"; import { SvActions } from "../../../siciliaVola/store/actions"; @@ -6,7 +5,6 @@ import { AvailableBonusesActions } from "./availableBonusesTypes"; export type BonusActions = | AvailableBonusesActions - | BpdActions | CgnActions | SvActions | CdcActions; diff --git a/ts/features/bonus/common/store/reducers/index.ts b/ts/features/bonus/common/store/reducers/index.ts index 27615e8d347..31659451b06 100644 --- a/ts/features/bonus/common/store/reducers/index.ts +++ b/ts/features/bonus/common/store/reducers/index.ts @@ -1,6 +1,5 @@ import { combineReducers } from "redux"; import { Action } from "../../../../../store/actions/types"; -import bpdReducer, { BpdState } from "../../../bpd/store/reducers"; import cdcReducer, { CdcState } from "../../../cdc/store/reducers"; import cgnReducer, { CgnState } from "../../../cgn/store/reducers"; import svReducer, { SvState } from "../../../siciliaVola/store/reducers"; @@ -10,7 +9,6 @@ import availableBonusesReducer, { export type BonusState = Readonly<{ availableBonusTypes: AvailableBonusTypesState; - bpd: BpdState; cgn: CgnState; sv: SvState; cdc: CdcState; @@ -18,7 +16,6 @@ export type BonusState = Readonly<{ const bonusReducer = combineReducers({ availableBonusTypes: availableBonusesReducer, - bpd: bpdReducer, cgn: cgnReducer, sv: svReducer, cdc: cdcReducer diff --git a/ts/features/bonus/siciliaVola/screens/voucherGeneration/DisabledAdditionalInfoScreen.tsx b/ts/features/bonus/siciliaVola/screens/voucherGeneration/DisabledAdditionalInfoScreen.tsx index d384800bcce..9b5fb9226e7 100644 --- a/ts/features/bonus/siciliaVola/screens/voucherGeneration/DisabledAdditionalInfoScreen.tsx +++ b/ts/features/bonus/siciliaVola/screens/voucherGeneration/DisabledAdditionalInfoScreen.tsx @@ -15,7 +15,7 @@ import I18n from "../../../../../i18n"; import { GlobalState } from "../../../../../store/reducers/types"; import { emptyContextualHelp } from "../../../../../utils/emptyContextualHelp"; import { openWebUrl } from "../../../../../utils/url"; -import { DeclarationEntry } from "../../../bpd/screens/onboarding/declaration/DeclarationEntry"; +import { DeclarationEntry } from "../../../common/components/DeclarationEntry"; import SV_ROUTES from "../../navigation/routes"; import { svGenerateVoucherBack, diff --git a/ts/features/design-system/core/DSLegacyPictograms.tsx b/ts/features/design-system/core/DSLegacyPictograms.tsx index d06d9ff0764..dbeb2167b77 100644 --- a/ts/features/design-system/core/DSLegacyPictograms.tsx +++ b/ts/features/design-system/core/DSLegacyPictograms.tsx @@ -1,39 +1,38 @@ -import * as React from "react"; -import { View, StyleSheet } from "react-native"; import { IOColors, useIOTheme } from "@pagopa/io-app-design-system"; +import * as React from "react"; +import { StyleSheet, View } from "react-native"; +import { H2 } from "../../../components/core/typography/H2"; import { DSAssetViewerBox, assetItemGutter, renderRasterImage } from "../components/DSAssetViewerBox"; -import { H2 } from "../../../components/core/typography/H2"; /* PICTOGRAMS */ -import Fireworks from "../../../../img/pictograms/fireworks.png"; -import FireworksWhite from "../../../../img/bonus/bpd/fireworks.png"; -import Question from "../../../../img/pictograms/doubt.png"; -import Hourglass from "../../../../img/pictograms/hourglass.png"; import AirBaloon from "../../../../img/bonus/siciliaVola/emptyVoucherList.svg"; -import AirBaloonRaster from "../../../../img/landing/session_expired.png"; -import AirBaloonArrow from "../../../../img/messages/empty-message-list-icon.png"; import Timeout from "../../../../img/bonus/siciliaVola/generateVoucherTimeout.svg"; +import BrokenLink from "../../../../img/broken-link.png"; +import AirBaloonRaster from "../../../../img/landing/session_expired.png"; import Baloons from "../../../../img/messages/empty-due-date-list-icon.png"; +import AirBaloonArrow from "../../../../img/messages/empty-message-list-icon.png"; import PiggyBank from "../../../../img/messages/empty-transaction-list-icon.png"; import Error from "../../../../img/messages/error-message-detail-icon.png"; +import Question from "../../../../img/pictograms/doubt.png"; +import Fireworks from "../../../../img/pictograms/fireworks.png"; +import Hourglass from "../../../../img/pictograms/hourglass.png"; +import CompletedRaster from "../../../../img/pictograms/payment-completed.png"; +import Completed from "../../../../img/pictograms/payment-completed.svg"; import BeerMug from "../../../../img/search/beer-mug.png"; import Search from "../../../../img/search/search-icon.png"; import Puzzle from "../../../../img/services/icon-loading-services.png"; +import ABILogo from "../../../../img/wallet/cards-icons/abiLogoFallback.png"; import Castle from "../../../../img/wallet/errors/domain-unknown-icon.png"; +import Umbrella from "../../../../img/wallet/errors/generic-error-icon.png"; import Abacus from "../../../../img/wallet/errors/invalid-amount-icon.png"; +import InProgress from "../../../../img/wallet/errors/missing-payment-id-icon.png"; import Vespa from "../../../../img/wallet/errors/payment-ongoing-icon.png"; import NotAvailable from "../../../../img/wallet/errors/payment-unavailable-icon.png"; -import InProgress from "../../../../img/wallet/errors/missing-payment-id-icon.png"; import Unrecognized from "../../../../img/wallet/errors/payment-unknown-icon.png"; -import Umbrella from "../../../../img/wallet/errors/generic-error-icon.png"; -import ABILogo from "../../../../img/wallet/cards-icons/abiLogoFallback.png"; -import CompletedRaster from "../../../../img/pictograms/payment-completed.png"; -import Completed from "../../../../img/pictograms/payment-completed.svg"; -import BrokenLink from "../../../../img/broken-link.png"; /* EU Covid Certificate */ import CertificateExpired from "../../../../img/features/euCovidCert/certificate_expired.png"; import CertificateNotFound from "../../../../img/features/euCovidCert/certificate_not_found.png"; @@ -66,11 +65,6 @@ export const DSLegacyPictograms = () => { name={"Fireworks"} image={renderRasterImage(Fireworks)} /> - { const aBPay: BPayPaymentMethod = { walletType: "BPay", createDate: "2021-07-08", - enableableFunctions: ["FA", "pagoPA", "BPD"], + enableableFunctions: ["FA", "pagoPA"], favourite: false, idWallet: 25572, info: { diff --git a/ts/features/wallet/cobadge/component/__tests__/CobadgeWalletPreview.test.tsx b/ts/features/wallet/cobadge/component/__tests__/CobadgeWalletPreview.test.tsx index a7cfe7bffc7..207a4ea77c8 100644 --- a/ts/features/wallet/cobadge/component/__tests__/CobadgeWalletPreview.test.tsx +++ b/ts/features/wallet/cobadge/component/__tests__/CobadgeWalletPreview.test.tsx @@ -23,7 +23,7 @@ describe("CobadgeWalletPreview component", () => { const aCobadgeCard: CreditCardPaymentMethod = { walletType: "Card", createDate: "2021-07-08", - enableableFunctions: ["FA", "pagoPA", "BPD"], + enableableFunctions: ["FA", "pagoPA"], favourite: false, idWallet: 25572, info: { diff --git a/ts/features/wallet/component/__test__/PagoPaPaymentCapability.test.tsx b/ts/features/wallet/component/__test__/PagoPaPaymentCapability.test.tsx index 0bb40920dbd..f7f55983d65 100644 --- a/ts/features/wallet/component/__test__/PagoPaPaymentCapability.test.tsx +++ b/ts/features/wallet/component/__test__/PagoPaPaymentCapability.test.tsx @@ -61,7 +61,7 @@ describe("PagoPaPaymentCapability", () => { const aPaymentMethod = { ...aBancomat, kind: "Bancomat", - enableableFunctions: [EnableableFunctionsEnum.BPD] + enableableFunctions: [] } as PaymentMethod; const globalState = appReducer(undefined, applicationChangeState("active")); @@ -88,7 +88,7 @@ describe("PagoPaPaymentCapability", () => { ...aNonMaestroCreditCard, kind: "CreditCard", pagoPA: false, - enableableFunctions: [EnableableFunctionsEnum.BPD] + enableableFunctions: [] } as PaymentMethod; const globalState = appReducer(undefined, applicationChangeState("active")); diff --git a/ts/features/wallet/component/card/FeaturedCardCarousel.tsx b/ts/features/wallet/component/card/FeaturedCardCarousel.tsx index 2dbf494a850..d8ccc7c1fb4 100644 --- a/ts/features/wallet/component/card/FeaturedCardCarousel.tsx +++ b/ts/features/wallet/component/card/FeaturedCardCarousel.tsx @@ -8,7 +8,6 @@ import { useEffect } from "react"; import { ScrollView, StyleSheet, View } from "react-native"; import { connect } from "react-redux"; import { BonusAvailable } from "../../../../../definitions/content/BonusAvailable"; -import cashbackLogo from "../../../../../img/bonus/bpd/logo_cashback_blue.png"; import cgnLogo from "../../../../../img/bonus/cgn/cgn_logo.png"; import { H3 } from "../../../../components/core/typography/H3"; import { IOStyles } from "../../../../components/core/variables/IOStyles"; @@ -31,8 +30,6 @@ import { import { GlobalState } from "../../../../store/reducers/types"; import { showToast } from "../../../../utils/showToast"; import { ID_CDC_TYPE, ID_CGN_TYPE } from "../../../bonus/common/utils"; -import { bpdOnboardingStart } from "../../../bonus/bpd/store/actions/onboarding"; -import { bpdEnabledSelector } from "../../../bonus/bpd/store/reducers/details/activation"; import { cgnActivationStart } from "../../../bonus/cgn/store/actions/activation"; import { isCgnEnrolledSelector } from "../../../bonus/cgn/store/reducers/details"; import { @@ -47,7 +44,7 @@ type Props = ReturnType & ReturnType; type BonusUtils = { - logo?: typeof cashbackLogo; + logo?: typeof cgnLogo; handler: (bonus: BonusAvailable) => void; }; @@ -193,13 +190,11 @@ const FeaturedCardCarousel: React.FunctionComponent = (props: Props) => { }; const mapStateToProps = (state: GlobalState) => ({ - bpdActiveBonus: bpdEnabledSelector(state), cgnActiveBonus: isCgnEnrolledSelector(state), availableBonusesList: supportedAvailableBonusSelector(state) }); const mapDispatchToProps = (dispatch: Dispatch) => ({ - startBpdOnboarding: () => dispatch(bpdOnboardingStart()), startCgnActivation: () => dispatch(cgnActivationStart()) }); diff --git a/ts/features/wallet/creditCard/screen/__tests__/CreditCardDetailScreen.test.tsx b/ts/features/wallet/creditCard/screen/__tests__/CreditCardDetailScreen.test.tsx index a342cad4c30..58bfeeb6cb9 100644 --- a/ts/features/wallet/creditCard/screen/__tests__/CreditCardDetailScreen.test.tsx +++ b/ts/features/wallet/creditCard/screen/__tests__/CreditCardDetailScreen.test.tsx @@ -23,10 +23,7 @@ import CreditCardDetailScreen from "../CreditCardDetailScreen"; const creditCard: CreditCardPaymentMethod = { walletType: WalletTypeEnum.Card, createDate: "2021-07-08", - enableableFunctions: [ - EnableableFunctionsEnum.BPD, - EnableableFunctionsEnum.pagoPA - ], + enableableFunctions: [EnableableFunctionsEnum.pagoPA], favourite: false, idWallet: 23216, info: { @@ -50,9 +47,6 @@ const creditCard: CreditCardPaymentMethod = { icon: 37 }; -jest.mock("../../../../../config", () => ({ - bpdEnabled: true -})); const mockInitiative = { initiativeId: "idpay", initiativeName: "idpay", diff --git a/ts/features/wallet/onboarding/bancomatPay/analytics/index.ts b/ts/features/wallet/onboarding/bancomatPay/analytics/index.ts index 39b6cc7c62b..02d709bae49 100644 --- a/ts/features/wallet/onboarding/bancomatPay/analytics/index.ts +++ b/ts/features/wallet/onboarding/bancomatPay/analytics/index.ts @@ -6,7 +6,7 @@ import { isTimeoutError } from "../../../../../utils/errors"; import { - addBPayToWallet, + addBPayToWalletAction, searchUserBPay, walletAddBPayBack, walletAddBPayCancel, @@ -23,8 +23,8 @@ export const trackBPayAction = case getType(walletAddBPayCompleted): case getType(walletAddBPayCancel): case getType(walletAddBPayBack): - case getType(addBPayToWallet.request): - case getType(addBPayToWallet.success): + case getType(addBPayToWalletAction.request): + case getType(addBPayToWalletAction.success): return mp.track(action.type); case getType(searchUserBPay.request): return mp.track(action.type, { abi: action.payload ?? "all" }); @@ -36,7 +36,7 @@ export const trackBPayAction = ) }); - case getType(addBPayToWallet.failure): + case getType(addBPayToWalletAction.failure): return mp.track(action.type, { reason: getNetworkErrorMessage(action.payload) }); diff --git a/ts/features/wallet/onboarding/bancomatPay/navigation/action.ts b/ts/features/wallet/onboarding/bancomatPay/navigation/action.ts index ea272d96f02..20a7c40ef06 100644 --- a/ts/features/wallet/onboarding/bancomatPay/navigation/action.ts +++ b/ts/features/wallet/onboarding/bancomatPay/navigation/action.ts @@ -44,17 +44,3 @@ export const navigateToOnboardingBPaySearchAvailableUserAccount = () => } }) ); - -/** - * @deprecated Do not use this method when you have access to a navigation prop or useNavigation since it will behave differently, - * and many helper methods specific to screens won't be available. - */ -export const navigateToActivateBpdOnNewBPay = () => - NavigationService.dispatchNavigationAction( - CommonActions.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: WALLET_ONBOARDING_BPAY_ROUTES.MAIN, - params: { - screen: WALLET_ONBOARDING_BPAY_ROUTES.ACTIVATE_BPD_NEW - } - }) - ); diff --git a/ts/features/wallet/onboarding/bancomatPay/navigation/routes.ts b/ts/features/wallet/onboarding/bancomatPay/navigation/routes.ts index 1a6a6ecf8fd..5ab61b1bbef 100644 --- a/ts/features/wallet/onboarding/bancomatPay/navigation/routes.ts +++ b/ts/features/wallet/onboarding/bancomatPay/navigation/routes.ts @@ -4,9 +4,7 @@ const WALLET_ONBOARDING_BPAY_ROUTES = { START: "WALLET_ONBOARDING_BPAY_START", CHOOSE_BANK: "WALLET_ONBOARDING_BPAY_CHOOSE_BANK_SCREEN", SEARCH_AVAILABLE_USER_ACCOUNT: - "WALLET_ONBOARDING_BPAY_SEARCH_AVAILABLE_USER_ACCOUNT", - - ACTIVATE_BPD_NEW: "WALLET_ONBOARDING_BPAY_ACTIVATE_BPD_NEW" + "WALLET_ONBOARDING_BPAY_SEARCH_AVAILABLE_USER_ACCOUNT" } as const; export default WALLET_ONBOARDING_BPAY_ROUTES; diff --git a/ts/features/wallet/onboarding/bancomatPay/saga/networking/index.ts b/ts/features/wallet/onboarding/bancomatPay/saga/networking/index.ts index 7941564b873..0d7ea80b960 100644 --- a/ts/features/wallet/onboarding/bancomatPay/saga/networking/index.ts +++ b/ts/features/wallet/onboarding/bancomatPay/saga/networking/index.ts @@ -18,7 +18,7 @@ import { readablePrivacyReport } from "../../../../../../utils/reporters"; import { SessionManager } from "../../../../../../utils/SessionManager"; import { fromPatchedWalletV2ToRawBPay } from "../../../../../../utils/walletv2"; import { - addBPayToWallet as addBpayToWalletAction, + addBPayToWalletAction as addBpayToWalletAction, searchUserBPay } from "../../store/actions"; diff --git a/ts/features/wallet/onboarding/bancomatPay/saga/orchestration/addBPayToWallet.ts b/ts/features/wallet/onboarding/bancomatPay/saga/orchestration/addBPayToWallet.ts index 4119d8361c9..09d507bb984 100644 --- a/ts/features/wallet/onboarding/bancomatPay/saga/orchestration/addBPayToWallet.ts +++ b/ts/features/wallet/onboarding/bancomatPay/saga/orchestration/addBPayToWallet.ts @@ -1,19 +1,15 @@ -import { call, put, select } from "typed-redux-saga/macro"; +import { call, put } from "typed-redux-saga/macro"; import NavigationService from "../../../../../../navigation/NavigationService"; import ROUTES from "../../../../../../navigation/routes"; import { + WorkUnitHandler, executeWorkUnit, - withResetNavigationStack, - WorkUnitHandler + withResetNavigationStack } from "../../../../../../sagas/workUnit"; import { navigateToWalletHome } from "../../../../../../store/actions/navigation"; import { fetchWalletsRequest } from "../../../../../../store/actions/wallet/wallets"; -import { activateBpdOnNewPaymentMethods } from "../../../../../bonus/bpd/saga/orchestration/activateBpdOnNewAddedPaymentMethods"; -import { - navigateToActivateBpdOnNewBPay, - navigateToOnboardingBPaySearchStartScreen -} from "../../navigation/action"; +import { navigateToOnboardingBPaySearchStartScreen } from "../../navigation/action"; import WALLET_ONBOARDING_BPAY_ROUTES from "../../navigation/routes"; import { walletAddBPayBack, @@ -21,7 +17,6 @@ import { walletAddBPayCompleted, walletAddBPayFailure } from "../../store/actions"; -import { onboardingBPayAddedAccountSelector } from "../../store/reducers/addedBPay"; /** * Define the workflow that allows the user to add BPay accounts to the wallet. @@ -42,9 +37,9 @@ function* bPayWorkUnit() { } /** - * Chain the add BPay to wallet with "activate bpd on the new BPay accounts" + * add Bpay to wallet saga */ -export function* addBPayToWalletAndActivateBpd() { +export function* addBPayToWalletSaga() { const initialScreenName: ReturnType< typeof NavigationService.getCurrentRouteName > = yield* call(NavigationService.getCurrentRouteName); @@ -67,14 +62,5 @@ export function* addBPayToWalletAndActivateBpd() { if (res === "completed") { // refresh wallets list yield* put(fetchWalletsRequest()); - // read the new added BPay - const bPayAdded: ReturnType = - yield* select(onboardingBPayAddedAccountSelector); - - yield* call( - activateBpdOnNewPaymentMethods, - bPayAdded, - navigateToActivateBpdOnNewBPay - ); } } diff --git a/ts/features/wallet/onboarding/bancomatPay/screens/ActivateBpdOnNewBPayScreen.tsx b/ts/features/wallet/onboarding/bancomatPay/screens/ActivateBpdOnNewBPayScreen.tsx deleted file mode 100644 index 601aeb51307..00000000000 --- a/ts/features/wallet/onboarding/bancomatPay/screens/ActivateBpdOnNewBPayScreen.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import * as React from "react"; -import { connect } from "react-redux"; -import I18n from "../../../../../i18n"; -import { GlobalState } from "../../../../../store/reducers/types"; -import { emptyContextualHelp } from "../../../../../utils/emptyContextualHelp"; -import ActivateBpdOnNewPaymentMethodScreen from "../../common/screens/bpd/ActivateBpdOnNewPaymentMethodScreen"; -import { onboardingBPayAddedAccountSelector } from "../store/reducers/addedBPay"; - -type Props = ReturnType; - -/** - * The user can activate the cashback on the new added BPay account - * @param props - * @constructor - */ -const ActivateBpdOnNewBPayScreen = (props: Props) => ( - -); - -const mapStateToProps = (state: GlobalState) => ({ - newBPay: onboardingBPayAddedAccountSelector(state) -}); -export default connect(mapStateToProps)(ActivateBpdOnNewBPayScreen); diff --git a/ts/features/wallet/onboarding/bancomatPay/screens/add-account/AddBPayScreen.tsx b/ts/features/wallet/onboarding/bancomatPay/screens/add-account/AddBPayScreen.tsx index d65956ca407..4afc7d0b5cf 100644 --- a/ts/features/wallet/onboarding/bancomatPay/screens/add-account/AddBPayScreen.tsx +++ b/ts/features/wallet/onboarding/bancomatPay/screens/add-account/AddBPayScreen.tsx @@ -16,7 +16,7 @@ import { isReady } from "../../../../../../common/model/RemoteValue"; import { - addBPayToWallet, + addBPayToWalletAction, walletAddBPayCancel, walletAddBPayCompleted } from "../../store/actions"; @@ -96,10 +96,10 @@ const AddBPayScreen = (props: Props): React.ReactElement | null => { }; const mapDispatchToProps = (dispatch: Dispatch) => ({ - addBPay: (bPay: BPay) => dispatch(addBPayToWallet.request(bPay)), + addBPay: (bPay: BPay) => dispatch(addBPayToWalletAction.request(bPay)), onCompleted: () => dispatch(walletAddBPayCompleted()), onCancel: () => dispatch(walletAddBPayCancel()), - onRetry: (bPay: BPay) => dispatch(addBPayToWallet.request(bPay)) + onRetry: (bPay: BPay) => dispatch(addBPayToWalletAction.request(bPay)) }); const mapStateToProps = (state: GlobalState) => { diff --git a/ts/features/wallet/onboarding/bancomatPay/store/actions/index.ts b/ts/features/wallet/onboarding/bancomatPay/store/actions/index.ts index 87f9d0cd00b..f6566a097f6 100644 --- a/ts/features/wallet/onboarding/bancomatPay/store/actions/index.ts +++ b/ts/features/wallet/onboarding/bancomatPay/store/actions/index.ts @@ -19,7 +19,7 @@ export const searchUserBPay = createAsyncAction( /** * The user add a specific BPay account to the wallet */ -export const addBPayToWallet = createAsyncAction( +export const addBPayToWalletAction = createAsyncAction( "WALLET_ONBOARDING_BPAY_ADD_REQUEST", "WALLET_ONBOARDING_BPAY_ADD_SUCCESS", "WALLET_ONBOARDING_BPAY_ADD_FAILURE" @@ -63,7 +63,7 @@ export const walletAddBPayBack = createStandardAction( export type BPayActions = | ActionType - | ActionType + | ActionType | ActionType | ActionType | ActionType diff --git a/ts/features/wallet/onboarding/bancomatPay/store/reducers/addedBPay.ts b/ts/features/wallet/onboarding/bancomatPay/store/reducers/addedBPay.ts index 8c08ff68374..b47f75c9b09 100644 --- a/ts/features/wallet/onboarding/bancomatPay/store/reducers/addedBPay.ts +++ b/ts/features/wallet/onboarding/bancomatPay/store/reducers/addedBPay.ts @@ -8,7 +8,7 @@ import { import { enhanceBPay } from "../../../../../../utils/paymentMethod"; import { getValueOrElse } from "../../../../../../common/model/RemoteValue"; import { abiSelector } from "../../../store/abi"; -import { addBPayToWallet, walletAddBPayStart } from "../actions"; +import { addBPayToWalletAction, walletAddBPayStart } from "../actions"; const addedBPayReducer = ( state: ReadonlyArray = [], @@ -16,7 +16,7 @@ const addedBPayReducer = ( ): ReadonlyArray => { switch (action.type) { // Register a new BPay account added in the current onboarding session - case getType(addBPayToWallet.success): + case getType(addBPayToWalletAction.success): return [...state, action.payload]; // Reset the state when starting a new BPay onboarding workflow case getType(walletAddBPayStart): diff --git a/ts/features/wallet/onboarding/bancomatPay/store/reducers/addingBPay.ts b/ts/features/wallet/onboarding/bancomatPay/store/reducers/addingBPay.ts index 1e71e059508..a55df64f42f 100644 --- a/ts/features/wallet/onboarding/bancomatPay/store/reducers/addingBPay.ts +++ b/ts/features/wallet/onboarding/bancomatPay/store/reducers/addingBPay.ts @@ -10,7 +10,7 @@ import { remoteUndefined, RemoteValue } from "../../../../../../common/model/RemoteValue"; -import { addBPayToWallet, walletAddBPayStart } from "../actions"; +import { addBPayToWalletAction, walletAddBPayStart } from "../actions"; import { NetworkError } from "../../../../../../utils/errors"; export type AddingBPayState = { @@ -27,17 +27,17 @@ const addingBPayReducer = ( action: Action ): AddingBPayState => { switch (action.type) { - case getType(addBPayToWallet.request): + case getType(addBPayToWalletAction.request): return { selectedBPay: action.payload, addingResult: remoteLoading }; - case getType(addBPayToWallet.success): + case getType(addBPayToWalletAction.success): return { ...state, addingResult: remoteReady(action.payload) }; - case getType(addBPayToWallet.failure): + case getType(addBPayToWalletAction.failure): return { ...state, addingResult: remoteError(action.payload) diff --git a/ts/features/wallet/onboarding/cobadge/navigation/action.ts b/ts/features/wallet/onboarding/cobadge/navigation/action.ts index b656a6aa6b6..6ffb0063c1d 100644 --- a/ts/features/wallet/onboarding/cobadge/navigation/action.ts +++ b/ts/features/wallet/onboarding/cobadge/navigation/action.ts @@ -48,17 +48,3 @@ export const navigateToOnboardingCoBadgeSearchAvailable = () => } }) ); - -/** - * @deprecated Do not use this method when you have access to a navigation prop or useNavigation since it will behave differently, - * and many helper methods specific to screens won't be available. - */ -export const navigateToActivateBpdOnNewCoBadge = () => - NavigationService.dispatchNavigationAction( - CommonActions.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: WALLET_ONBOARDING_COBADGE_ROUTES.MAIN, - params: { - screen: WALLET_ONBOARDING_COBADGE_ROUTES.ACTIVATE_BPD_NEW - } - }) - ); diff --git a/ts/features/wallet/onboarding/cobadge/navigation/routes.ts b/ts/features/wallet/onboarding/cobadge/navigation/routes.ts index b695faa6402..ff162ba4325 100644 --- a/ts/features/wallet/onboarding/cobadge/navigation/routes.ts +++ b/ts/features/wallet/onboarding/cobadge/navigation/routes.ts @@ -4,9 +4,7 @@ const WALLET_ONBOARDING_COBADGE_ROUTES = { CHOOSE_TYPE: "WALLET_ONBOARDING_COBADGE_CHOOSE_TYPE", START: "WALLET_ONBOARDING_COBADGE_START", - SEARCH_AVAILABLE: "WALLET_ONBOARDING_COBADGE_SEARCH_AVAILABLE", - - ACTIVATE_BPD_NEW: "WALLET_ONBOARDING_COBADGE_ACTIVATE_BPD_NEW" + SEARCH_AVAILABLE: "WALLET_ONBOARDING_COBADGE_SEARCH_AVAILABLE" } as const; export default WALLET_ONBOARDING_COBADGE_ROUTES; diff --git a/ts/features/wallet/onboarding/cobadge/saga/orchestration/addCoBadgeToWallet.ts b/ts/features/wallet/onboarding/cobadge/saga/orchestration/addCoBadgeToWallet.ts index 4aab3d35b68..dfb48e4e668 100644 --- a/ts/features/wallet/onboarding/cobadge/saga/orchestration/addCoBadgeToWallet.ts +++ b/ts/features/wallet/onboarding/cobadge/saga/orchestration/addCoBadgeToWallet.ts @@ -1,4 +1,4 @@ -import { call, put, select } from "typed-redux-saga/macro"; +import { call, put } from "typed-redux-saga/macro"; import NavigationService from "../../../../../../navigation/NavigationService"; import ROUTES from "../../../../../../navigation/routes"; import { @@ -9,11 +9,7 @@ import { import { navigateToWalletHome } from "../../../../../../store/actions/navigation"; import { fetchWalletsRequest } from "../../../../../../store/actions/wallet/wallets"; import { SagaCallReturnType } from "../../../../../../types/utils"; -import { activateBpdOnNewPaymentMethods } from "../../../../../bonus/bpd/saga/orchestration/activateBpdOnNewAddedPaymentMethods"; -import { - navigateToActivateBpdOnNewCoBadge, - navigateToOnboardingCoBadgeSearchStartScreen -} from "../../navigation/action"; +import { navigateToOnboardingCoBadgeSearchStartScreen } from "../../navigation/action"; import WALLET_ONBOARDING_COBADGE_ROUTES from "../../navigation/routes"; import { walletAddCoBadgeBack, @@ -21,7 +17,6 @@ import { walletAddCoBadgeCompleted, walletAddCoBadgeFailure } from "../../store/actions"; -import { onboardingCoBadgeAddedSelector } from "../../store/reducers/addedCoBadge"; /** * Define the workflow that allows the user to add a co-badge card to the wallet. @@ -50,9 +45,9 @@ const returnToWalletRoutes = new Set([ ]); /** - * Chain the add co-badge to wallet with "activate bpd on the new co-badge cards" + * add co-badge to wallet saga */ -export function* addCoBadgeToWalletAndActivateBpd() { +export function* addCoBadgeToWalletSaga() { const initialScreenName: ReturnType< typeof NavigationService.getCurrentRouteName > = yield* call(NavigationService.getCurrentRouteName); @@ -78,14 +73,5 @@ export function* addCoBadgeToWalletAndActivateBpd() { if (res === "completed") { // refresh wallets list yield* put(fetchWalletsRequest()); - // read the new added co-badge cards - const coBadgeAdded: ReturnType = - yield* select(onboardingCoBadgeAddedSelector); - - yield* call( - activateBpdOnNewPaymentMethods, - coBadgeAdded, - navigateToActivateBpdOnNewCoBadge - ); } } diff --git a/ts/features/wallet/onboarding/cobadge/screens/ActivateBpdOnNewCoBadgeScreen.tsx b/ts/features/wallet/onboarding/cobadge/screens/ActivateBpdOnNewCoBadgeScreen.tsx deleted file mode 100644 index 2623e1118f8..00000000000 --- a/ts/features/wallet/onboarding/cobadge/screens/ActivateBpdOnNewCoBadgeScreen.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import * as React from "react"; -import { connect } from "react-redux"; -import I18n from "../../../../../i18n"; -import { GlobalState } from "../../../../../store/reducers/types"; -import { emptyContextualHelp } from "../../../../../utils/emptyContextualHelp"; -import ActivateBpdOnNewPaymentMethodScreen from "../../common/screens/bpd/ActivateBpdOnNewPaymentMethodScreen"; -import { onboardingCoBadgeAddedSelector } from "../store/reducers/addedCoBadge"; - -type Props = ReturnType; - -/** - * The user can activate the cashback on the new added coBadge card - * @param props - * @constructor - */ -const ActivateBpdOnNewCoBadgeScreen = (props: Props) => ( - -); - -const mapStateToProps = (state: GlobalState) => ({ - newCoBadge: onboardingCoBadgeAddedSelector(state) -}); -export default connect(mapStateToProps)(ActivateBpdOnNewCoBadgeScreen); diff --git a/ts/features/wallet/onboarding/common/screens/bpd/ActivateBpdOnNewPaymentMethodScreen.tsx b/ts/features/wallet/onboarding/common/screens/bpd/ActivateBpdOnNewPaymentMethodScreen.tsx deleted file mode 100644 index 5df3f19afd3..00000000000 --- a/ts/features/wallet/onboarding/common/screens/bpd/ActivateBpdOnNewPaymentMethodScreen.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import * as React from "react"; -import { View, SafeAreaView, ScrollView } from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { VSpacer } from "@pagopa/io-app-design-system"; -import { Body } from "../../../../../../components/core/typography/Body"; -import { H1 } from "../../../../../../components/core/typography/H1"; -import { IOStyles } from "../../../../../../components/core/variables/IOStyles"; -import BaseScreenComponent from "../../../../../../components/screens/BaseScreenComponent"; -import FooterWithButtons from "../../../../../../components/ui/FooterWithButtons"; -import I18n from "../../../../../../i18n"; -import { navigateBack } from "../../../../../../store/actions/navigation"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { PaymentMethod } from "../../../../../../types/pagopa"; -import { PaymentMethodRawList } from "../../../../../bonus/bpd/components/paymentMethodActivationToggle/list/PaymentMethodRawList"; -import { areAnyPaymentMethodsActiveSelector } from "../../../../../bonus/bpd/store/reducers/details/paymentMethods"; - -type OwnProps = { - paymentMethods: ReadonlyArray; - title: string; -}; - -export type Props = ReturnType & - ReturnType & - OwnProps & - Pick, "contextualHelp">; - -const loadLocales = () => ({ - title: I18n.t("bonus.bpd.activateOnNewMethods.title"), - body1: I18n.t("bonus.bpd.activateOnNewMethods.body1"), - body2: I18n.t("bonus.bpd.activateOnNewMethods.body2"), - skip: I18n.t("bonus.bpd.activateOnNewMethods.skip"), - continueStr: I18n.t("global.buttons.continue") -}); - -/** - * return a two button footer - * left button is enabled when no payment methods are BPD active - * right button button is enabled when at least one payment method is BPD active - * @param props - */ -const getFooter = (props: Props) => { - const { continueStr, skip } = loadLocales(); - const notNowButtonProps = { - primary: false, - bordered: true, - disabled: props.areAnyPaymentMethodsActive, - onPress: props.skip, - title: skip - }; - const continueButtonProps = { - block: true, - primary: true, - disabled: !props.areAnyPaymentMethodsActive, - onPress: props.skip, - title: continueStr - }; - return ( - - ); -}; - -const ActivateBpdOnNewPaymentMethodScreen: React.FunctionComponent< - Props -> = props => { - const { title, body1, body2 } = loadLocales(); - - return ( - - - - - -

{title}

- - {body1} - - - - {body2} -
-
- {getFooter(props)} -
-
- ); -}; - -const mapDispatchToProps = (_: Dispatch) => ({ - skip: () => navigateBack() -}); - -const mapStateToProps = (state: GlobalState, props: OwnProps) => ({ - areAnyPaymentMethodsActive: areAnyPaymentMethodsActiveSelector( - props.paymentMethods - )(state) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(ActivateBpdOnNewPaymentMethodScreen); diff --git a/ts/features/wallet/onboarding/common/screens/bpd/SuggestBpdActivationScreen.tsx b/ts/features/wallet/onboarding/common/screens/bpd/SuggestBpdActivationScreen.tsx deleted file mode 100644 index b4ed53131c5..00000000000 --- a/ts/features/wallet/onboarding/common/screens/bpd/SuggestBpdActivationScreen.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import * as React from "react"; -import { View, SafeAreaView, ScrollView } from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { VSpacer } from "@pagopa/io-app-design-system"; -import { Body } from "../../../../../../components/core/typography/Body"; -import { H1 } from "../../../../../../components/core/typography/H1"; -import { IOStyles } from "../../../../../../components/core/variables/IOStyles"; -import BaseScreenComponent from "../../../../../../components/screens/BaseScreenComponent"; -import I18n from "../../../../../../i18n"; -import { navigateBack } from "../../../../../../store/actions/navigation"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { emptyContextualHelp } from "../../../../../../utils/emptyContextualHelp"; -import { FooterTwoButtons } from "../../../../../../components/markdown/FooterTwoButtons"; -import { bpdOnboardingStart } from "../../../../../bonus/bpd/store/actions/onboarding"; - -export type Props = ReturnType & - ReturnType; - -const loadLocales = () => ({ - headerTitle: I18n.t("bonus.bpd.title"), - title: I18n.t("bonus.bpd.suggestActivation.title"), - body: I18n.t("bonus.bpd.suggestActivation.body"), - skip: I18n.t("bonus.bpd.suggestActivation.skip"), - confirm: I18n.t("bonus.bpd.suggestActivation.confirm") -}); - -/** - * this screen prompts the user to activate bpd after inserting a new bancomat. - * @constructor - */ -const SuggestBpdActivationScreen: React.FunctionComponent = props => { - const { headerTitle, title, body, skip, confirm } = loadLocales(); - - return ( - - - - - -

{title}

- - {body} -
-
- -
-
- ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - skip: () => navigateBack(), - activateBpd: () => dispatch(bpdOnboardingStart()) -}); - -const mapStateToProps = (_: GlobalState) => ({}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(SuggestBpdActivationScreen); diff --git a/ts/navigation/params/WalletParamsList.ts b/ts/navigation/params/WalletParamsList.ts index 1aeea2ef5b0..fec5a2ce8a4 100644 --- a/ts/navigation/params/WalletParamsList.ts +++ b/ts/navigation/params/WalletParamsList.ts @@ -60,7 +60,4 @@ export type WalletParamsList = { [WALLET_ONBOARDING_COBADGE_ROUTES.MAIN]: NavigatorScreenParams; [PAYPAL_ROUTES.ONBOARDING .MAIN]: NavigatorScreenParams; - - [WALLET_ONBOARDING_BPAY_ROUTES.ACTIVATE_BPD_NEW]: undefined; - [WALLET_ONBOARDING_COBADGE_ROUTES.ACTIVATE_BPD_NEW]: undefined; }; diff --git a/ts/sagas/profile.ts b/ts/sagas/profile.ts index 17d89b38b20..26d88cfa615 100644 --- a/ts/sagas/profile.ts +++ b/ts/sagas/profile.ts @@ -16,8 +16,6 @@ import { UpdateProfile412ErrorTypesEnum } from "../../definitions/backend/Update import { UserDataProcessingChoiceEnum } from "../../definitions/backend/UserDataProcessingChoice"; import { BackendClient } from "../api/backend"; import { tosVersion } from "../config"; -import { bpdLoadActivationStatus } from "../features/bonus/bpd/store/actions/details"; -import { bpdEnabledSelector } from "../features/bonus/bpd/store/reducers/details/activation"; import { cgnDetails } from "../features/bonus/cgn/store/actions/details"; import { cgnDetailSelector } from "../features/bonus/cgn/store/reducers/details"; import { withRefreshApiCall } from "../features/fastLogin/saga/utils"; @@ -326,21 +324,6 @@ function* startEmailValidationProcessSaga( } function* handleLoadBonusBeforeRemoveAccount() { - const bpdActive: ReturnType = yield* select( - bpdEnabledSelector - ); - - // check if there are some bpd - if (pot.isNone(bpdActive)) { - // Load the bpd data and wait for a response - yield* put(bpdLoadActivationStatus.request()); - - yield* take([ - bpdLoadActivationStatus.success, - bpdLoadActivationStatus.failure - ]); - } - const cgnActive: ReturnType = yield* select( cgnDetailSelector ); diff --git a/ts/sagas/wallet.ts b/ts/sagas/wallet.ts index c117adb37a5..491dbdec41b 100644 --- a/ts/sagas/wallet.ts +++ b/ts/sagas/wallet.ts @@ -44,9 +44,9 @@ import { handleAddpayToWallet, handleSearchUserBPay } from "../features/wallet/onboarding/bancomatPay/saga/networking"; -import { addBPayToWalletAndActivateBpd } from "../features/wallet/onboarding/bancomatPay/saga/orchestration/addBPayToWallet"; +import { addBPayToWalletSaga } from "../features/wallet/onboarding/bancomatPay/saga/orchestration/addBPayToWallet"; import { - addBPayToWallet, + addBPayToWalletAction, searchUserBPay, walletAddBPayStart } from "../features/wallet/onboarding/bancomatPay/store/actions"; @@ -55,7 +55,7 @@ import { handleLoadCoBadgeConfiguration, handleSearchUserCoBadge } from "../features/wallet/onboarding/cobadge/saga/networking"; -import { addCoBadgeToWalletAndActivateBpd } from "../features/wallet/onboarding/cobadge/saga/orchestration/addCoBadgeToWallet"; +import { addCoBadgeToWalletSaga } from "../features/wallet/onboarding/cobadge/saga/orchestration/addCoBadgeToWallet"; import { addCoBadgeToWallet, loadCoBadgeAbiConfiguration, @@ -771,7 +771,7 @@ export function* watchWalletSaga( ); // watch for add BPay to Wallet workflow - yield* takeLatest(walletAddBPayStart, addBPayToWalletAndActivateBpd); + yield* takeLatest(walletAddBPayStart, addBPayToWalletSaga); // watch for BancomatPay search request yield* takeLatest( @@ -782,12 +782,33 @@ export function* watchWalletSaga( ); // watch for add BancomatPay to the user's wallet yield* takeLatest( - addBPayToWallet.request, + addBPayToWalletAction.request, handleAddpayToWallet, paymentManagerClient.addBPayToWallet, pmSessionManager ); + // watch for CoBadge search request + yield* takeLatest( + searchUserCoBadge.request, + handleSearchUserCoBadge, + paymentManagerClient.getCobadgePans, + paymentManagerClient.searchCobadgePans, + pmSessionManager + ); + // watch for add CoBadge to the user's wallet + yield* takeLatest( + addCoBadgeToWallet.request, + handleAddCoBadgeToWallet, + paymentManagerClient.addCobadgeToWallet, + pmSessionManager + ); + // watch for CoBadge configuration request + yield* takeLatest( + loadCoBadgeAbiConfiguration.request, + handleLoadCoBadgeConfiguration, + contentClient.getCobadgeServices + ); // watch for CoBadge search request yield* takeLatest( searchUserCoBadge.request, @@ -811,7 +832,7 @@ export function* watchWalletSaga( ); // watch for add co-badge to Wallet workflow - yield* takeLatest(walletAddCoBadgeStart, addCoBadgeToWalletAndActivateBpd); + yield* takeLatest(walletAddCoBadgeStart, addCoBadgeToWalletSaga); yield* fork( watchPaypalOnboardingSaga, diff --git a/ts/screens/profile/RemoveAccountDetailsScreen.tsx b/ts/screens/profile/RemoveAccountDetailsScreen.tsx index 5a27983e2c4..091d97de597 100644 --- a/ts/screens/profile/RemoveAccountDetailsScreen.tsx +++ b/ts/screens/profile/RemoveAccountDetailsScreen.tsx @@ -17,7 +17,6 @@ import BaseScreenComponent from "../../components/screens/BaseScreenComponent"; import FooterWithButtons from "../../components/ui/FooterWithButtons"; import { shufflePinPadOnPayment } from "../../config"; import { LoadingErrorComponent } from "../../components/LoadingErrorComponent"; -import { bpdEnabledSelector } from "../../features/bonus/bpd/store/reducers/details/activation"; import { isCgnEnrolledSelector } from "../../features/bonus/cgn/store/reducers/details"; import I18n from "../../i18n"; import NavigationService from "../../navigation/NavigationService"; @@ -71,8 +70,7 @@ const RemoveAccountDetails: React.FunctionComponent = (props: Props) => { const [otherMotivation, setOtherMotivation] = React.useState(""); const handleContinuePress = () => { - const hasActiveBonus = - pot.getOrElse(props.bpdActiveBonus, false) || props.cgnActiveBonus; + const hasActiveBonus = props.cgnActiveBonus; if (hasActiveBonus) { Alert.alert( @@ -226,7 +224,6 @@ const mapDispatchToProps = (dispatch: Dispatch) => { }; const mapStateToProps = (state: GlobalState) => { - const bpdActiveBonus = bpdEnabledSelector(state); const cgnActiveBonus = isCgnEnrolledSelector(state); const userDataProcessing = userDataProcessingSelector(state); const isLoading = @@ -234,7 +231,6 @@ const mapStateToProps = (state: GlobalState) => { pot.isUpdating(userDataProcessing.DELETE); const isError = pot.isError(userDataProcessing.DELETE); return { - bpdActiveBonus, cgnActiveBonus, userDataProcessing, isLoading, diff --git a/ts/screens/wallet/payment/__tests__/PickPaymentMethodScreen.test.tsx b/ts/screens/wallet/payment/__tests__/PickPaymentMethodScreen.test.tsx index 39c087eb703..a4e0ccc569a 100644 --- a/ts/screens/wallet/payment/__tests__/PickPaymentMethodScreen.test.tsx +++ b/ts/screens/wallet/payment/__tests__/PickPaymentMethodScreen.test.tsx @@ -35,10 +35,7 @@ const aCreditCard = { brand: "VISA", type: undefined }, - enableableFunctions: [ - EnableableFunctionsEnum.pagoPA, - EnableableFunctionsEnum.BPD - ], + enableableFunctions: [EnableableFunctionsEnum.pagoPA], caption: "", icon: "", pagoPA: true, diff --git a/ts/screens/wallet/payment/__tests__/TransactionErrorScreen.test.tsx b/ts/screens/wallet/payment/__tests__/TransactionErrorScreen.test.tsx index 79ebc8a70ad..d69839896ce 100644 --- a/ts/screens/wallet/payment/__tests__/TransactionErrorScreen.test.tsx +++ b/ts/screens/wallet/payment/__tests__/TransactionErrorScreen.test.tsx @@ -17,6 +17,10 @@ import { GlobalState } from "../../../../store/reducers/types"; import { PayloadForAction } from "../../../../types/utils"; import { renderScreenWithNavigationStoreContext } from "../../../../utils/testWrapper"; import TransactionErrorScreen from "../TransactionErrorScreen"; +// this is required for TSC, since apparently +// it does not update the typedef unless it is actually +// imported in a test (or a global.d) and not in the jest global config +import "@testing-library/jest-native/extend-expect"; const rptId = { organizationFiscalCode: "00000000005", diff --git a/ts/store/reducers/backoffErrorConfig.ts b/ts/store/reducers/backoffErrorConfig.ts index 00b63a1e565..a7232176516 100644 --- a/ts/store/reducers/backoffErrorConfig.ts +++ b/ts/store/reducers/backoffErrorConfig.ts @@ -1,10 +1,6 @@ import { getType } from "typesafe-actions"; import _ from "lodash"; import { PayloadAC } from "typesafe-actions/dist/type-helpers"; -import { - bpdTransactionsLoadPage, - bpdTransactionsLoadRequiredData -} from "../../features/bonus/bpd/store/actions/transactions"; import { fetchTransactionsFailure, fetchTransactionsSuccess @@ -15,8 +11,6 @@ import { fetchWalletsFailure, fetchWalletsSuccess } from "../actions/wallet/wallets"; -import { bpdLoadActivationStatus } from "../../features/bonus/bpd/store/actions/details"; -import { bpdPeriodsAmountLoad } from "../../features/bonus/bpd/store/actions/periods"; import { euCovidCertificateGet } from "../../features/euCovidCert/store/actions"; import { svPossibleVoucherStateGet, @@ -36,13 +30,6 @@ const monitoredActions: ReadonlyArray< [addWalletCreditCardFailure, addWalletCreditCardSuccess], [fetchTransactionsFailure, fetchTransactionsSuccess], [fetchWalletsFailure, fetchWalletsSuccess], - [bpdLoadActivationStatus.failure, bpdLoadActivationStatus.success], - [bpdPeriodsAmountLoad.failure, bpdPeriodsAmountLoad.success], - [bpdTransactionsLoadPage.failure, bpdTransactionsLoadPage.success], - [ - bpdTransactionsLoadRequiredData.failure, - bpdTransactionsLoadRequiredData.success - ], [euCovidCertificateGet.failure, euCovidCertificateGet.success], [svPossibleVoucherStateGet.failure, svPossibleVoucherStateGet.success], [svVoucherListGet.failure, svVoucherListGet.success], diff --git a/ts/store/reducers/wallet/__mocks__/wallets.ts b/ts/store/reducers/wallet/__mocks__/wallets.ts index 2f432e39395..80b19984978 100644 --- a/ts/store/reducers/wallet/__mocks__/wallets.ts +++ b/ts/store/reducers/wallet/__mocks__/wallets.ts @@ -11,8 +11,7 @@ export const walletsV2_1 = { createDate: "2021-08-28", enableableFunctions: [ EnableableFunctionsEnum.FA, - EnableableFunctionsEnum.pagoPA, - EnableableFunctionsEnum.BPD + EnableableFunctionsEnum.pagoPA ], favourite: false, idWallet: 23190, @@ -39,8 +38,7 @@ export const walletsV2_1 = { createDate: "2021-07-22", enableableFunctions: [ EnableableFunctionsEnum.FA, - EnableableFunctionsEnum.pagoPA, - EnableableFunctionsEnum.BPD + EnableableFunctionsEnum.pagoPA ], favourite: false, idWallet: 29371, @@ -67,8 +65,7 @@ export const walletsV2_1 = { createDate: "2020-12-28", enableableFunctions: [ EnableableFunctionsEnum.FA, - EnableableFunctionsEnum.pagoPA, - EnableableFunctionsEnum.BPD + EnableableFunctionsEnum.pagoPA ], favourite: false, idWallet: 23216, @@ -100,8 +97,7 @@ export const walletsV2_2 = { createDate: "2021-08-28", enableableFunctions: [ EnableableFunctionsEnum.FA, - EnableableFunctionsEnum.pagoPA, - EnableableFunctionsEnum.BPD + EnableableFunctionsEnum.pagoPA ], favourite: false, idWallet: 23190, @@ -128,8 +124,7 @@ export const walletsV2_2 = { createDate: "2020-12-28", enableableFunctions: [ EnableableFunctionsEnum.FA, - EnableableFunctionsEnum.pagoPA, - EnableableFunctionsEnum.BPD + EnableableFunctionsEnum.pagoPA ], favourite: false, idWallet: 23216, @@ -162,8 +157,7 @@ export const walletsV2_3 = { createDate: "2021-10-22", enableableFunctions: [ EnableableFunctionsEnum.FA, - EnableableFunctionsEnum.pagoPA, - EnableableFunctionsEnum.BPD + EnableableFunctionsEnum.pagoPA ], favourite: false, idWallet: 20341, @@ -190,8 +184,7 @@ export const walletsV2_3 = { createDate: "2021-04-15", enableableFunctions: [ EnableableFunctionsEnum.FA, - EnableableFunctionsEnum.pagoPA, - EnableableFunctionsEnum.BPD + EnableableFunctionsEnum.pagoPA ], favourite: false, idWallet: 21750, @@ -218,8 +211,7 @@ export const walletsV2_3 = { createDate: "2021-07-08", enableableFunctions: [ EnableableFunctionsEnum.FA, - EnableableFunctionsEnum.pagoPA, - EnableableFunctionsEnum.BPD + EnableableFunctionsEnum.pagoPA ], favourite: false, idWallet: 25572, @@ -243,8 +235,7 @@ export const rawBPay: RawBPayPaymentMethod = { createDate: "2021-07-08", enableableFunctions: [ EnableableFunctionsEnum.FA, - EnableableFunctionsEnum.pagoPA, - EnableableFunctionsEnum.BPD + EnableableFunctionsEnum.pagoPA ], favourite: false, idWallet: 1, @@ -267,8 +258,7 @@ export const mockCreditCardPaymentMethod: PaymentMethod = { createDate: "2021-07-08", enableableFunctions: [ EnableableFunctionsEnum.FA, - EnableableFunctionsEnum.pagoPA, - EnableableFunctionsEnum.BPD + EnableableFunctionsEnum.pagoPA ], favourite: false, idWallet: 25572, diff --git a/ts/store/reducers/wallet/__tests__/wallets.test.ts b/ts/store/reducers/wallet/__tests__/wallets.test.ts index fce0853eeb5..0c74d145dd1 100644 --- a/ts/store/reducers/wallet/__tests__/wallets.test.ts +++ b/ts/store/reducers/wallet/__tests__/wallets.test.ts @@ -410,23 +410,13 @@ describe("walletV2 reducer - deleteAllByFunction", () => { { ...aPaymentMethod, idWallet: 3, - enableableFunctions: [EnableableFunctionsEnum.BPD] + enableableFunctions: [] } ]; const maybeWalletsV2 = PatchedWalletV2ListResponse.decode({ data: wallet }); - const maybeWalletsExceptBPDV2 = PatchedWalletV2ListResponse.decode({ - data: wallet.filter(w => - w.enableableFunctions.includes(EnableableFunctionsEnum.BPD) - ) - }); const convertedWallets = ( E.getOrElseW(() => [])(maybeWalletsV2) as PatchedWalletV2ListResponse ).data!.map(convertWalletV2toWalletV1); - const convertedWalletsExceptBPD = ( - E.getOrElseW(() => [])( - maybeWalletsExceptBPDV2 - ) as PatchedWalletV2ListResponse - ).data!.map(convertWalletV2toWalletV1); const globalState: GlobalState = appReducer( undefined, @@ -442,7 +432,7 @@ describe("walletV2 reducer - deleteAllByFunction", () => { const updatedState: GlobalState = appReducer( globalState, deleteAllPaymentMethodsByFunction.success({ - wallets: convertedWalletsExceptBPD, + wallets: convertedWallets, deletedMethodsCount: 1 }) ); @@ -450,7 +440,7 @@ describe("walletV2 reducer - deleteAllByFunction", () => { expect(pot.isSome(walletUpdated)).toBeTruthy(); if (pot.isSome(walletUpdated)) { expect(Object.keys(walletUpdated.value).length).toEqual( - convertedWalletsExceptBPD.length + convertedWallets.length ); } }); diff --git a/ts/store/reducers/wallet/wallets.ts b/ts/store/reducers/wallet/wallets.ts index dcbc7c0d19d..81c04ec7a3a 100644 --- a/ts/store/reducers/wallet/wallets.ts +++ b/ts/store/reducers/wallet/wallets.ts @@ -8,39 +8,36 @@ import { createSelector } from "reselect"; import { getType, isOfType } from "typesafe-actions"; import { WalletTypeEnum } from "../../../../definitions/pagopa/WalletV2"; import { + RemoteValue, getValueOrElse, remoteError, remoteLoading, remoteReady, - remoteUndefined, - RemoteValue + remoteUndefined } from "../../../common/model/RemoteValue"; import { abiSelector } from "../../../features/wallet/onboarding/store/abi"; import { - BancomatPaymentMethod, BPayPaymentMethod, + BancomatPaymentMethod, CreditCardPaymentMethod, - isBancomat, - isBPay, - isCreditCard, - isPayPal, - isRawCreditCard, - PaymentMethod, PayPalPaymentMethod, + PaymentMethod, RawCreditCardPaymentMethod, RawPaymentMethod, - Wallet + Wallet, + isBPay, + isBancomat, + isCreditCard, + isPayPal, + isRawCreditCard } from "../../../types/pagopa"; -import { EnableableFunctionsEnum } from "../../../../definitions/pagopa/EnableableFunctions"; import { TypeEnum } from "../../../../definitions/pagopa/walletv2/CardInfo"; -import { optInPaymentMethodsStart } from "../../../features/bonus/bpd/store/actions/optInPaymentMethods"; import { PotFromActions } from "../../../types/utils"; import { getErrorFromNetworkError } from "../../../utils/errors"; import { isDefined } from "../../../utils/guards"; import { enhancePaymentMethod } from "../../../utils/paymentMethod"; import { hasPaymentFeature } from "../../../utils/paymentMethodCapabilities"; -import { hasFunctionEnabled } from "../../../utils/walletv2"; import { sessionExpired, sessionInvalid } from "../../actions/authentication"; import { clearCache } from "../../actions/profile"; import { Action } from "../../actions/types"; @@ -210,20 +207,6 @@ export const withPaymentFeatureSelector = createSelector( ) ); -// return those payment methods that have BPD as enabled function -export const getBPDMethodsSelector = createSelector( - paymentMethodsSelector, - ( - potPm: ReturnType - ): ReadonlyArray => - pot.getOrElse( - pot.map(potPm, pms => - pms.filter(pm => hasFunctionEnabled(pm, EnableableFunctionsEnum.BPD)) - ), - [] - ) -); - /** * return true if the payment method is visible in the wallet (the onboardingChannel * is IO or WISP) or if the onboardingChannel is undefined. @@ -241,12 +224,6 @@ export const isVisibleInWallet = (pm: PaymentMethod): boolean => ); // return those payment methods that have BPD as enabled function and are visible in Wallet -export const getBPDMethodsVisibleInWalletSelector = createSelector( - getBPDMethodsSelector, - ( - pms: ReturnType - ): ReadonlyArray => pms.filter(isVisibleInWallet) -); export const rawCreditCardListSelector = createSelector( [paymentMethodsSelector], @@ -439,12 +416,6 @@ const reducer = ( // // delete all payments method by function // - case getType(optInPaymentMethodsStart): - // Reset the state when the opt-in payment methods workunit starts - return { - ...state, - deleteAllByFunction: WALLETS_INITIAL_STATE.deleteAllByFunction - }; case getType(deleteAllPaymentMethodsByFunction.request): return { ...state, deleteAllByFunction: remoteLoading }; case getType(deleteAllPaymentMethodsByFunction.success): diff --git a/ts/utils/__tests__/paymentMethodCapabilities.test.ts b/ts/utils/__tests__/paymentMethodCapabilities.test.ts index c9c68eeb8af..330c9ca3c24 100644 --- a/ts/utils/__tests__/paymentMethodCapabilities.test.ts +++ b/ts/utils/__tests__/paymentMethodCapabilities.test.ts @@ -76,7 +76,7 @@ const testCases: ReadonlyArray< { pm: { ...paymentMethod, - enableableFunctions: [EnableableFunctionsEnum.BPD], + enableableFunctions: [], pagoPA: true }, expected: { diff --git a/ts/utils/testFaker.ts b/ts/utils/testFaker.ts index 877e19a369d..2d09bc7cd29 100644 --- a/ts/utils/testFaker.ts +++ b/ts/utils/testFaker.ts @@ -9,7 +9,6 @@ import { ImportoEuroCents } from "../../definitions/backend/ImportoEuroCents"; import { PaymentRequestsGetResponse } from "../../definitions/backend/PaymentRequestsGetResponse"; import { SpidLevelEnum } from "../../definitions/backend/SpidLevel"; import { SpidIdp } from "../../definitions/content/SpidIdp"; -import { EnableableFunctionsEnum } from "../../definitions/pagopa/EnableableFunctions"; import { TypeEnum } from "../../definitions/pagopa/Wallet"; import { WalletTypeEnum } from "../../definitions/pagopa/WalletV2"; import { @@ -202,7 +201,7 @@ export const myWalletNoCreditCard: { [key: string]: any } = { export const bancomat = { walletType: WalletTypeEnum.Bancomat, createDate: "2021-04-05", - enableableFunctions: [EnableableFunctionsEnum.BPD], + enableableFunctions: [], favourite: false, idWallet: 24415, info: {