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 1/7] 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: { From 6b690c72e87a6ce0c4512902f7b9fe4e48955c8c Mon Sep 17 00:00:00 2001 From: Martino Cesari Tomba <60693085+forrest57@users.noreply.github.com> Date: Mon, 29 Jan 2024 15:39:01 +0100 Subject: [PATCH 2/7] fix: [IOBP-506] I18n fix in transaction details (#5450) ## Short description fixed I18n bug in `WALLET_TRANSACTION_DETAILS` ## List of changes proposed in this pull request - added missing dot to I18n - removed dot from component, in order to organize all text in a locale ## How to test in the app, open a transaction's details and check that both with a correctly loaded PSP and with an error loading a PSP the copy is correct and displays a single dot at the end of the sentence. --- locales/de/index.yml | 2 +- locales/en/index.yml | 2 +- locales/it/index.yml | 2 +- .../transaction/components/WalletTransactionHeadingSection.tsx | 1 - 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/locales/de/index.yml b/locales/de/index.yml index 9f5160e3c7d..7002712991e 100644 --- a/locales/de/index.yml +++ b/locales/de/index.yml @@ -2459,7 +2459,7 @@ transaction: title: "Details zur Transaktion" totalAmount: "Insgesamt" totalFee: "Der Gesamtbetrag umfasst " - totalFeePsp: "Provision, berechnet von {{pspName}}" + totalFeePsp: "Provision, berechnet von {{pspName}}." totalFeeNoPsp: "Provision, die vom Transaktionsdienstleister (PSP) erhoben wird." info: title: "Informationen zur Transaktion" diff --git a/locales/en/index.yml b/locales/en/index.yml index e0e59a0642b..fd61136f9ff 100644 --- a/locales/en/index.yml +++ b/locales/en/index.yml @@ -3859,7 +3859,7 @@ transaction: title: Dettaglio operazione totalAmount: Totale totalFee: Il totale comprende - totalFeePsp: di commissione, applicata da {{pspName}} + totalFeePsp: "di commissione, applicata da {{pspName}}." totalFeeNoPsp: "di commissione, applicata dal gestore della transazione (PSP)." info: title: Informazioni sulla transazione diff --git a/locales/it/index.yml b/locales/it/index.yml index f5a8d5b56b3..fb226ff9174 100644 --- a/locales/it/index.yml +++ b/locales/it/index.yml @@ -3859,7 +3859,7 @@ transaction: title: Dettaglio operazione totalAmount: Totale totalFee: Il totale comprende - totalFeePsp: di commissione, applicata da {{pspName}} + totalFeePsp: "di commissione, applicata da {{pspName}}." totalFeeNoPsp: "di commissione, applicata dal gestore della transazione (PSP)." info: title: Informazioni sulla transazione diff --git a/ts/features/walletV3/transaction/components/WalletTransactionHeadingSection.tsx b/ts/features/walletV3/transaction/components/WalletTransactionHeadingSection.tsx index 59fddd912c3..a9cf9459284 100644 --- a/ts/features/walletV3/transaction/components/WalletTransactionHeadingSection.tsx +++ b/ts/features/walletV3/transaction/components/WalletTransactionHeadingSection.tsx @@ -68,7 +68,6 @@ export const WalletTransactionHeadingSection = ({ pspName: psp.businessName }) : I18n.t("transaction.details.totalFeeNoPsp")} - . ); } From 5f5f644b32faa6737af480aa119fa94f22f760b8 Mon Sep 17 00:00:00 2001 From: Alessandro Dell'Oste Date: Mon, 29 Jan 2024 15:56:36 +0100 Subject: [PATCH 3/7] feat: [IOCOM-868] Update SEND message header and content to the new design system (#5431) ## Short description This PR updates the header and content of a SEND message. _NOTE: Other PRs will follow to update the rest of the message._
Details

| message details | | - | | |

## List of changes proposed in this pull request - added `MessageDetailHeader` - updated `MessageDetailsContent` - added tests ## How to test Using the `io-dev-api-server`, generate a SEND message. Enable the design system and navigate to the message detail. Check that the content is displayed correctly. --------- Co-authored-by: Andrea --- .../MessageDetail/MessageDetailHeader.tsx | 48 + .../__tests__/MessageDetailHeader.test.tsx | 35 + .../MessageDetailHeader.test.tsx.snap | 191 +++ ts/features/pn/__mocks__/message.ts | 46 + .../pn/components/LegacyMessageDetails.tsx | 193 +++ .../LegacyMessageDetailsContent.tsx | 31 + ts/features/pn/components/MessageDetails.tsx | 206 +-- .../pn/components/MessageDetailsContent.tsx | 44 +- .../__test__/LegacyMessageDetails.test.tsx | 188 +++ .../__test__/MessageDetails.test.tsx | 176 +-- .../__test__/MessageDetailsContent.test.tsx | 15 + .../MessageDetails.test.tsx.snap | 449 ++++++ .../MessageDetailsContent.test.tsx.snap | 51 + ts/features/pn/navigation/navigator.tsx | 60 +- .../pn/screens/LegacyMessageDetailsScreen.tsx | 132 ++ .../pn/screens/MessageDetailsScreen.tsx | 102 +- .../__test__/MessageDetailsScreen.test.tsx | 83 + .../MessageDetailsScreen.test.tsx.snap | 1372 +++++++++++++++++ ts/features/pn/store/types/transformers.ts | 1 + ts/features/pn/store/types/types.ts | 8 +- 20 files changed, 2990 insertions(+), 441 deletions(-) create mode 100644 ts/features/messages/components/MessageDetail/MessageDetailHeader.tsx create mode 100644 ts/features/messages/components/MessageDetail/__tests__/MessageDetailHeader.test.tsx create mode 100644 ts/features/messages/components/MessageDetail/__tests__/__snapshots__/MessageDetailHeader.test.tsx.snap create mode 100644 ts/features/pn/__mocks__/message.ts create mode 100644 ts/features/pn/components/LegacyMessageDetails.tsx create mode 100644 ts/features/pn/components/LegacyMessageDetailsContent.tsx create mode 100644 ts/features/pn/components/__test__/LegacyMessageDetails.test.tsx create mode 100644 ts/features/pn/components/__test__/MessageDetailsContent.test.tsx create mode 100644 ts/features/pn/components/__test__/__snapshots__/MessageDetails.test.tsx.snap create mode 100644 ts/features/pn/components/__test__/__snapshots__/MessageDetailsContent.test.tsx.snap create mode 100644 ts/features/pn/screens/LegacyMessageDetailsScreen.tsx create mode 100644 ts/features/pn/screens/__test__/MessageDetailsScreen.test.tsx create mode 100644 ts/features/pn/screens/__test__/__snapshots__/MessageDetailsScreen.test.tsx.snap diff --git a/ts/features/messages/components/MessageDetail/MessageDetailHeader.tsx b/ts/features/messages/components/MessageDetail/MessageDetailHeader.tsx new file mode 100644 index 00000000000..849cf89df7c --- /dev/null +++ b/ts/features/messages/components/MessageDetail/MessageDetailHeader.tsx @@ -0,0 +1,48 @@ +import { Divider, H3, LabelSmall, VSpacer } from "@pagopa/io-app-design-system"; +import React, { PropsWithChildren } from "react"; +import { StyleSheet, View } from "react-native"; +import { localeDateFormat } from "../../../../utils/locale"; +import I18n from "../../../../i18n"; +import { ServicePublic } from "../../../../../definitions/backend/ServicePublic"; + +export type MessageDetailHeaderProps = PropsWithChildren<{ + createdAt: Date; + subject: string; + sender?: string; + service?: ServicePublic; +}>; + +const styles = StyleSheet.create({ + header: { + paddingHorizontal: 24 + } +}); + +const MessageHeaderContent = ({ + subject, + createdAt +}: MessageDetailHeaderProps) => ( + <> +

{subject}

+ + + {localeDateFormat( + createdAt, + I18n.t("global.dateFormats.fullFormatShortMonthLiteralWithTime") + )} + + +); + +export const MessageDetailHeader = ({ + children, + ...rest +}: MessageDetailHeaderProps) => ( + + {children} + + + + {/* TODO: Add MessageHeaderService */} + +); diff --git a/ts/features/messages/components/MessageDetail/__tests__/MessageDetailHeader.test.tsx b/ts/features/messages/components/MessageDetail/__tests__/MessageDetailHeader.test.tsx new file mode 100644 index 00000000000..5539b0d3fdc --- /dev/null +++ b/ts/features/messages/components/MessageDetail/__tests__/MessageDetailHeader.test.tsx @@ -0,0 +1,35 @@ +import React, { ComponentProps } from "react"; +import { render } from "@testing-library/react-native"; +import { MessageDetailHeader } from "../MessageDetailHeader"; +import { ServicePublic } from "../../../../../../definitions/backend/ServicePublic"; + +const service = { + service_id: "serviceId", + service_name: "health", + organization_name: "Organization foo", + department_name: "Department one", + organization_fiscal_code: "OFSAAAAAA" +} as ServicePublic; + +const defaultProps: ComponentProps = { + subject: "Subject", + createdAt: new Date("2021-10-18T16:00:30.541Z") +}; + +describe("MessageDetailHeader component", () => { + it("should match the snapshot with default props", () => { + const component = render(); + expect(component.toJSON()).toMatchSnapshot(); + }); + + it("should match the snapshot with all props", () => { + const component = render( + + ); + expect(component.toJSON()).toMatchSnapshot(); + }); +}); diff --git a/ts/features/messages/components/MessageDetail/__tests__/__snapshots__/MessageDetailHeader.test.tsx.snap b/ts/features/messages/components/MessageDetail/__tests__/__snapshots__/MessageDetailHeader.test.tsx.snap new file mode 100644 index 00000000000..d1597d22480 --- /dev/null +++ b/ts/features/messages/components/MessageDetail/__tests__/__snapshots__/MessageDetailHeader.test.tsx.snap @@ -0,0 +1,191 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MessageDetailHeader component should match the snapshot with all props 1`] = ` + + + Subject + + + + 18 Oct 2021, 16:00 + + + + +`; + +exports[`MessageDetailHeader component should match the snapshot with default props 1`] = ` + + + Subject + + + + 18 Oct 2021, 16:00 + + + + +`; diff --git a/ts/features/pn/__mocks__/message.ts b/ts/features/pn/__mocks__/message.ts new file mode 100644 index 00000000000..6bfc8eeaccb --- /dev/null +++ b/ts/features/pn/__mocks__/message.ts @@ -0,0 +1,46 @@ +import { ThirdPartyMessageWithContent } from "../../../../definitions/backend/ThirdPartyMessageWithContent"; +import { message_1 } from "../../messages/__mocks__/message"; +import { ATTACHMENT_CATEGORY } from "../../messages/types/attachmentCategory"; + +export const thirdPartyMessage: ThirdPartyMessageWithContent = { + ...message_1, + created_at: new Date("2020-01-01T00:00:00.000Z"), + third_party_message: { + details: { + abstract: "######## abstract ########", + attachments: [ + { + messageId: message_1.id, + id: "1", + displayName: "A First Attachment", + contentType: "application/pdf", + category: ATTACHMENT_CATEGORY.DOCUMENT, + resourceUrl: { href: "/resource/attachment1.pdf" } + }, + { + messageId: message_1.id, + id: "2", + displayName: "A Second Attachment", + contentType: "application/pdf", + category: ATTACHMENT_CATEGORY.DOCUMENT, + resourceUrl: { href: "/resource/attachment2.pdf" } + } + ], + iun: "731143-7-0317-8200-0", + subject: "######## subject ########", + recipients: [ + { + recipientType: "-", + taxId: "AAABBB00A00A000A", + denomination: "AaAaAa BbBbBb", + payment: { + noticeCode: "026773337463073118", + creditorTaxId: "00000000009" + } + } + ], + notificationStatusHistory: [], + senderDenomination: "Sender denomination" + } + } +}; diff --git a/ts/features/pn/components/LegacyMessageDetails.tsx b/ts/features/pn/components/LegacyMessageDetails.tsx new file mode 100644 index 00000000000..78d59087e5e --- /dev/null +++ b/ts/features/pn/components/LegacyMessageDetails.tsx @@ -0,0 +1,193 @@ +import React, { useCallback, createRef, useRef } from "react"; +import { pipe } from "fp-ts/lib/function"; +import * as O from "fp-ts/lib/Option"; +import * as RA from "fp-ts/lib/ReadonlyArray"; +import * as SEP from "fp-ts/lib/Separated"; +import { View } from "react-native"; +import { ScrollView } from "react-native-gesture-handler"; +import { + IOVisualCostants, + ListItemInfoCopy, + VSpacer +} from "@pagopa/io-app-design-system"; +import { ServicePublic } from "../../../../definitions/backend/ServicePublic"; +import { H5 } from "../../../components/core/typography/H5"; +import I18n from "../../../i18n"; +import { useIOSelector } from "../../../store/hooks"; +import { pnFrontendUrlSelector } from "../../../store/reducers/backendStatus"; +import { UIAttachment, UIMessageId } from "../../messages/types"; +import { clipboardSetStringWithFeedback } from "../../../utils/clipboard"; +import { LegacyMessageAttachments } from "../../messages/components/LegacyMessageAttachments"; +import NavigationService from "../../../navigation/NavigationService"; +import PN_ROUTES from "../navigation/routes"; +import { PNMessage } from "../store/types/types"; +import { NotificationPaymentInfo } from "../../../../definitions/pn/NotificationPaymentInfo"; +import { trackPNAttachmentOpening } from "../analytics"; +import { DSFullWidthComponent } from "../../design-system/components/DSFullWidthComponent"; +import StatusContent from "../../../components/SectionStatus/StatusContent"; +import { + getStatusTextColor, + statusColorMap, + statusIconMap +} from "../../../components/SectionStatus"; +import { LevelEnum } from "../../../../definitions/content/SectionStatus"; +import { ATTACHMENT_CATEGORY } from "../../messages/types/attachmentCategory"; +import { maxVisiblePaymentCountGenerator } from "../utils"; +import { LegacyMessageDetailsContent } from "./LegacyMessageDetailsContent"; +import { MessageDetailsHeader } from "./MessageDetailsHeader"; +import { MessageDetailsSection } from "./MessageDetailsSection"; +import { MessageTimeline } from "./MessageTimeline"; +import { MessageTimelineCTA } from "./MessageTimelineCTA"; +import { MessageF24 } from "./MessageF24"; +import { MessagePayments } from "./MessagePayments"; +import { MessageFooter } from "./MessageFooter"; +import { MessagePaymentBottomSheet } from "./MessagePaymentBottomSheet"; + +type Props = Readonly<{ + messageId: UIMessageId; + message: PNMessage; + service: ServicePublic | undefined; + payments: ReadonlyArray | undefined; +}>; + +export const LegacyMessageDetails = ({ + message, + messageId, + service, + payments +}: Props) => { + const viewRef = createRef(); + const presentPaymentsBottomSheetRef = useRef<() => void>(); + const frontendUrl = useIOSelector(pnFrontendUrlSelector); + + const partitionedAttachments = pipe( + message.attachments, + O.fromNullable, + O.getOrElse>(() => []), + RA.partition(attachment => attachment.category === ATTACHMENT_CATEGORY.F24) + ); + + const f24List = SEP.right(partitionedAttachments); + const attachmentList = SEP.left(partitionedAttachments); + + const isCancelled = message.isCancelled ?? false; + const completedPaymentNoticeCodes = isCancelled + ? message.completedPayments + : undefined; + + const openAttachment = useCallback( + (attachment: UIAttachment) => { + trackPNAttachmentOpening(attachment.category); + NavigationService.navigate(PN_ROUTES.MESSAGE_ATTACHMENT, { + messageId, + attachmentId: attachment.id, + category: attachment.category + }); + }, + [messageId] + ); + + const maxVisiblePaymentCount = maxVisiblePaymentCountGenerator(); + const scrollViewRef = React.createRef(); + + return ( + <> + + {service && } + + + {isCancelled && ( + <> + + + + {I18n.t("features.pn.details.cancelledMessage.body")} + + + + )} + + {RA.isNonEmpty(attachmentList) && ( + + + + )} + + + {!isCancelled && RA.isNonEmpty(f24List) ? ( + <> + + + + ) : null} + + + clipboardSetStringWithFeedback(message.iun)} + accessibilityLabel={I18n.t("features.pn.details.infoSection.iun")} + label={I18n.t("features.pn.details.infoSection.iun")} + /> +
+ {I18n.t("features.pn.details.timeline.title")} +
+ + { + scrollViewRef.current?.scrollToEnd({ animated: true }); + }} + /> + {frontendUrl.length > 0 && } +
+
+ + {payments && !isCancelled && ( + + )} + + + + ); +}; diff --git a/ts/features/pn/components/LegacyMessageDetailsContent.tsx b/ts/features/pn/components/LegacyMessageDetailsContent.tsx new file mode 100644 index 00000000000..59cb0f34fbe --- /dev/null +++ b/ts/features/pn/components/LegacyMessageDetailsContent.tsx @@ -0,0 +1,31 @@ +import React from "react"; +import { View, ViewProps, StyleSheet } from "react-native"; +import { Body } from "../../../components/core/typography/Body"; +import { H1 } from "../../../components/core/typography/H1"; +import { H2 } from "../../../components/core/typography/H2"; +import { PNMessage } from "../store/types/types"; +import customVariables from "../../../theme/variables"; +import { isStringNullyOrEmpty } from "../../../utils/strings"; + +const styles = StyleSheet.create({ + subject: { + marginTop: customVariables.spacerExtrasmallHeight + }, + abstract: { + marginTop: customVariables.spacerExtrasmallHeight + } +}); + +type Props = Readonly<{ message: PNMessage }> & ViewProps; + +export const LegacyMessageDetailsContent = (props: Props) => ( + + {!isStringNullyOrEmpty(props.message.senderDenomination) && ( +

{props.message.senderDenomination}

+ )} +

{props.message.subject}

+ {!isStringNullyOrEmpty(props.message.abstract) && ( + {props.message.abstract} + )} +
+); diff --git a/ts/features/pn/components/MessageDetails.tsx b/ts/features/pn/components/MessageDetails.tsx index a2370ecabd8..d6f25513ce4 100644 --- a/ts/features/pn/components/MessageDetails.tsx +++ b/ts/features/pn/components/MessageDetails.tsx @@ -1,193 +1,27 @@ -import React, { useCallback, createRef, useRef } from "react"; -import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import * as RA from "fp-ts/lib/ReadonlyArray"; -import * as SEP from "fp-ts/lib/Separated"; -import { View } from "react-native"; -import { ScrollView } from "react-native-gesture-handler"; -import { - IOVisualCostants, - ListItemInfoCopy, - VSpacer -} from "@pagopa/io-app-design-system"; +import React from "react"; +import { ScrollView } from "react-native"; import { ServicePublic } from "../../../../definitions/backend/ServicePublic"; -import { H5 } from "../../../components/core/typography/H5"; -import I18n from "../../../i18n"; -import { useIOSelector } from "../../../store/hooks"; -import { pnFrontendUrlSelector } from "../../../store/reducers/backendStatus"; -import { UIAttachment, UIMessageId } from "../../messages/types"; -import { clipboardSetStringWithFeedback } from "../../../utils/clipboard"; -import { LegacyMessageAttachments } from "../../messages/components/LegacyMessageAttachments"; -import NavigationService from "../../../navigation/NavigationService"; -import PN_ROUTES from "../navigation/routes"; +import { UIMessageId } from "../../messages/types"; import { PNMessage } from "../store/types/types"; import { NotificationPaymentInfo } from "../../../../definitions/pn/NotificationPaymentInfo"; -import { trackPNAttachmentOpening } from "../analytics"; -import { DSFullWidthComponent } from "../../design-system/components/DSFullWidthComponent"; -import StatusContent from "../../../components/SectionStatus/StatusContent"; -import { - getStatusTextColor, - statusColorMap, - statusIconMap -} from "../../../components/SectionStatus"; -import { LevelEnum } from "../../../../definitions/content/SectionStatus"; -import { ATTACHMENT_CATEGORY } from "../../messages/types/attachmentCategory"; -import { maxVisiblePaymentCountGenerator } from "../utils"; +import { MessageDetailHeader } from "../../messages/components/MessageDetail/MessageDetailHeader"; import { MessageDetailsContent } from "./MessageDetailsContent"; -import { MessageDetailsHeader } from "./MessageDetailsHeader"; -import { MessageDetailsSection } from "./MessageDetailsSection"; -import { MessageTimeline } from "./MessageTimeline"; -import { MessageTimelineCTA } from "./MessageTimelineCTA"; -import { MessageF24 } from "./MessageF24"; -import { MessagePayments } from "./MessagePayments"; -import { MessageFooter } from "./MessageFooter"; -import { MessagePaymentBottomSheet } from "./MessagePaymentBottomSheet"; -type Props = Readonly<{ - messageId: UIMessageId; +type MessageDetailsProps = { message: PNMessage; - service: ServicePublic | undefined; - payments: ReadonlyArray | undefined; -}>; - -export const MessageDetails = ({ - message, - messageId, - service, - payments -}: Props) => { - const viewRef = createRef(); - const presentPaymentsBottomSheetRef = useRef<() => void>(); - const frontendUrl = useIOSelector(pnFrontendUrlSelector); - - const partitionedAttachments = pipe( - message.attachments, - O.fromNullable, - O.getOrElse>(() => []), - RA.partition(attachment => attachment.category === ATTACHMENT_CATEGORY.F24) - ); - - const f24List = SEP.right(partitionedAttachments); - const attachmentList = SEP.left(partitionedAttachments); - - const isCancelled = message.isCancelled ?? false; - const completedPaymentNoticeCodes = isCancelled - ? message.completedPayments - : undefined; - - const openAttachment = useCallback( - (attachment: UIAttachment) => { - trackPNAttachmentOpening(attachment.category); - NavigationService.navigate(PN_ROUTES.MESSAGE_ATTACHMENT, { - messageId, - attachmentId: attachment.id, - category: attachment.category - }); - }, - [messageId] - ); - - const maxVisiblePaymentCount = maxVisiblePaymentCountGenerator(); - const scrollViewRef = React.createRef(); - - return ( - <> - - {service && } - - - {isCancelled && ( - <> - - - - {I18n.t("features.pn.details.cancelledMessage.body")} - - - - )} - - {RA.isNonEmpty(attachmentList) && ( - - - - )} - - - {!isCancelled && RA.isNonEmpty(f24List) ? ( - <> - - - - ) : null} - - - clipboardSetStringWithFeedback(message.iun)} - accessibilityLabel={I18n.t("features.pn.details.infoSection.iun")} - label={I18n.t("features.pn.details.infoSection.iun")} - /> -
- {I18n.t("features.pn.details.timeline.title")} -
- - { - scrollViewRef.current?.scrollToEnd({ animated: true }); - }} - /> - {frontendUrl.length > 0 && } -
-
- - {payments && !isCancelled && ( - - )} - - - - ); + messageId: UIMessageId; + service?: ServicePublic; + payments?: ReadonlyArray; }; + +export const MessageDetails = ({ message, service }: MessageDetailsProps) => ( + + + + +); diff --git a/ts/features/pn/components/MessageDetailsContent.tsx b/ts/features/pn/components/MessageDetailsContent.tsx index 21b33cfef0c..96cbdbbe3ad 100644 --- a/ts/features/pn/components/MessageDetailsContent.tsx +++ b/ts/features/pn/components/MessageDetailsContent.tsx @@ -1,31 +1,21 @@ import React from "react"; -import { View, ViewProps, StyleSheet } from "react-native"; -import { Body } from "../../../components/core/typography/Body"; -import { H1 } from "../../../components/core/typography/H1"; -import { H2 } from "../../../components/core/typography/H2"; -import { PNMessage } from "../store/types/types"; -import customVariables from "../../../theme/variables"; -import { isStringNullyOrEmpty } from "../../../utils/strings"; +import { Body, ContentWrapper, VSpacer } from "@pagopa/io-app-design-system"; -const styles = StyleSheet.create({ - subject: { - marginTop: customVariables.spacerExtrasmallHeight - }, - abstract: { - marginTop: customVariables.spacerExtrasmallHeight - } -}); +type MessageDetailsContentProps = { + abstract?: string; +}; -type Props = Readonly<{ message: PNMessage }> & ViewProps; +export const MessageDetailsContent = ({ + abstract +}: MessageDetailsContentProps) => { + if (abstract === undefined) { + return null; + } -export const MessageDetailsContent = (props: Props) => ( - - {!isStringNullyOrEmpty(props.message.senderDenomination) && ( -

{props.message.senderDenomination}

- )} -

{props.message.subject}

- {!isStringNullyOrEmpty(props.message.abstract) && ( - {props.message.abstract} - )} -
-); + return ( + + + {abstract} + + ); +}; diff --git a/ts/features/pn/components/__test__/LegacyMessageDetails.test.tsx b/ts/features/pn/components/__test__/LegacyMessageDetails.test.tsx new file mode 100644 index 00000000000..a7ec40d4d30 --- /dev/null +++ b/ts/features/pn/components/__test__/LegacyMessageDetails.test.tsx @@ -0,0 +1,188 @@ +import React from "react"; +import * as pot from "@pagopa/ts-commons/lib/pot"; +import { act, fireEvent } from "@testing-library/react-native"; +import { createStore } from "redux"; +import { applicationChangeState } from "../../../../store/actions/application"; +import { appReducer } from "../../../../store/reducers"; +import { LegacyMessageDetails } from "../LegacyMessageDetails"; +import { GlobalState } from "../../../../store/reducers/types"; +import { renderScreenWithNavigationStoreContext } from "../../../../utils/testWrapper"; +import PN_ROUTES from "../../navigation/routes"; +import { UIAttachment, UIMessageId } from "../../../messages/types"; +import { PNMessage } from "../../store/types/types"; +import { Download } from "../../../messages/store/reducers/downloads"; +import { NotificationRecipient } from "../../../../../definitions/pn/NotificationRecipient"; +import { ATTACHMENT_CATEGORY } from "../../../messages/types/attachmentCategory"; + +const mockedOnAttachmentSelect = jest.fn(); + +jest.mock("../../../messages/hooks/useAttachmentDownload", () => ({ + useAttachmentDownload: ( + _attachment: UIAttachment, + _openPreview: (attachment: UIAttachment) => void + ) => ({ + onAttachmentSelect: mockedOnAttachmentSelect, + downloadPot: { kind: "PotNone" } as pot.Pot + }) +})); + +describe("LegacyMessageDetails component", () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it("should not show the cancelled banner when the PN message is not cancelled", () => { + const { component } = renderComponent( + generateComponentProperties(generatePnMessage()) + ); + expect(component.queryByTestId("PnCancelledMessageBanner")).toBeFalsy(); + }); + + it("every attachment item should have a full opaque style when the PN message is not cancelled", () => { + const { component } = renderComponent( + generateComponentProperties(generatePnMessage()) + ); + const messageAttachmentComponents = component.queryAllByTestId( + "MessageAttachmentTouchable" + ); + expect(messageAttachmentComponents.length).toBe(2); + + messageAttachmentComponents.forEach(messageAttachmentComponent => { + const opacity = messageAttachmentComponent?.props.style.opacity; + expect(opacity).toBe(1.0); + }); + }); + + it("should show the cancelled banner when the PN message is cancelled", () => { + const { component } = renderComponent( + generateComponentProperties({ + ...generatePnMessage(), + isCancelled: true + }) + ); + expect(component.queryByTestId("PnCancelledMessageBanner")).toBeDefined(); + }); + + it("every attachment item should have a semi-transparent style when the PN message is cancelled", () => { + const { component } = renderComponent( + generateComponentProperties({ + ...generatePnMessage(), + isCancelled: true + }) + ); + const messageAttachmentComponents = component.queryAllByTestId( + "MessageAttachmentTouchable" + ); + expect(messageAttachmentComponents.length).toBe(2); + + messageAttachmentComponents.forEach(messageAttachmentComponent => { + const opacity = messageAttachmentComponent?.props.style.opacity; + expect(opacity).toBe(0.5); + }); + }); + + it("every attachment item should handle input when the PN message not is cancelled", async () => { + const { component } = renderComponent( + generateComponentProperties(generatePnMessage()) + ); + const messageAttachmentComponents = component.queryAllByTestId( + "MessageAttachmentTouchable" + ); + expect(messageAttachmentComponents.length).toBe(2); + await act(() => { + messageAttachmentComponents.forEach(messageAttachmentComponent => + fireEvent.press(messageAttachmentComponent) + ); + }); + expect(mockedOnAttachmentSelect).toHaveBeenCalledTimes( + messageAttachmentComponents.length + ); + }); + + it("every attachment item should not handle input when the PN message is cancelled", async () => { + const { component } = renderComponent( + generateComponentProperties({ ...generatePnMessage(), isCancelled: true }) + ); + const messageAttachmentComponents = component.queryAllByTestId( + "MessageAttachmentTouchable" + ); + expect(messageAttachmentComponents.length).toBe(2); + // eslint-disable-next-line sonarjs/no-identical-functions + await act(() => { + messageAttachmentComponents.forEach(messageAttachmentComponent => + fireEvent.press(messageAttachmentComponent) + ); + }); + expect(mockedOnAttachmentSelect).toHaveBeenCalledTimes(0); + }); + + it("should NOT render the F24 section if there are no multiple F24", () => { + const { component } = renderComponent( + generateComponentProperties(generatePnMessage()) + ); + expect(component.queryByTestId("pn-message-f24-section")).toBeFalsy(); + }); +}); + +const generateTestMessageId = () => "00000000000000000000000004" as UIMessageId; +const generateTestFiscalCode = () => "AAABBB00A00A000A"; +const generatePnMessage = (): PNMessage => ({ + created_at: new Date(), + iun: "731143-7-0317-8200-0", + subject: "This is the message subject", + senderDenomination: "Sender denomination", + abstract: "Message abstract", + notificationStatusHistory: [], + recipients: [ + { + recipientType: "-", + taxId: generateTestFiscalCode(), + denomination: "AaAaAa BbBbBb", + payment: { + noticeCode: "026773337463073118", + creditorTaxId: "00000000009" + } + } + ] as Array, + attachments: [ + { + messageId: generateTestMessageId(), + id: "1", + displayName: "A First Attachment", + contentType: "application/pdf", + category: ATTACHMENT_CATEGORY.DOCUMENT, + resourceUrl: { href: "/resource/attachment1.pdf" } + }, + { + messageId: generateTestMessageId(), + id: "2", + displayName: "A Second Attachment", + contentType: "application/pdf", + category: ATTACHMENT_CATEGORY.DOCUMENT, + resourceUrl: { href: "/resource/attachment2.pdf" } + } + ] as Array +}); +const generateComponentProperties = (pnMessage: PNMessage) => ({ + payments: undefined, + messageId: generateTestMessageId(), + message: pnMessage, + service: undefined +}); + +const renderComponent = ( + props: React.ComponentProps +) => { + const globalState = appReducer(undefined, applicationChangeState("active")); + const store = createStore(appReducer, globalState as any); + + return { + component: renderScreenWithNavigationStoreContext( + () => , + PN_ROUTES.MESSAGE_DETAILS, + {}, + store + ), + store + }; +}; diff --git a/ts/features/pn/components/__test__/MessageDetails.test.tsx b/ts/features/pn/components/__test__/MessageDetails.test.tsx index 11a7c0e7b65..b6c8687b84c 100644 --- a/ts/features/pn/components/__test__/MessageDetails.test.tsx +++ b/ts/features/pn/components/__test__/MessageDetails.test.tsx @@ -1,171 +1,38 @@ import React from "react"; -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { act, fireEvent } from "@testing-library/react-native"; -import { createStore } from "redux"; +import configureMockStore from "redux-mock-store"; +import { pipe } from "fp-ts/lib/function"; +import * as O from "fp-ts/lib/Option"; import { applicationChangeState } from "../../../../store/actions/application"; import { appReducer } from "../../../../store/reducers"; import { MessageDetails } from "../MessageDetails"; import { GlobalState } from "../../../../store/reducers/types"; import { renderScreenWithNavigationStoreContext } from "../../../../utils/testWrapper"; -import PN_ROUTES from "../../navigation/routes"; -import { UIAttachment, UIMessageId } from "../../../messages/types"; import { PNMessage } from "../../store/types/types"; -import { Download } from "../../../messages/store/reducers/downloads"; -import { NotificationRecipient } from "../../../../../definitions/pn/NotificationRecipient"; -import { ATTACHMENT_CATEGORY } from "../../../messages/types/attachmentCategory"; +import { thirdPartyMessage } from "../../__mocks__/message"; +import { toPNMessage } from "../../store/types/transformers"; +import { UIMessageId } from "../../../messages/types"; -const mockedOnAttachmentSelect = jest.fn(); - -jest.mock("../../../messages/hooks/useAttachmentDownload", () => ({ - useAttachmentDownload: ( - _attachment: UIAttachment, - _openPreview: (attachment: UIAttachment) => void - ) => ({ - onAttachmentSelect: mockedOnAttachmentSelect, - downloadPot: { kind: "PotNone" } as pot.Pot - }) -})); +const pnMessage = pipe(thirdPartyMessage, toPNMessage, O.toUndefined); describe("MessageDetails component", () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - it("should not show the cancelled banner when the PN message is not cancelled", () => { - const { component } = renderComponent( - generateComponentProperties(generatePnMessage()) - ); - expect(component.queryByTestId("PnCancelledMessageBanner")).toBeFalsy(); - }); - - it("every attachment item should have a full opaque style when the PN message is not cancelled", () => { - const { component } = renderComponent( - generateComponentProperties(generatePnMessage()) - ); - const messageAttachmentComponents = component.queryAllByTestId( - "MessageAttachmentTouchable" - ); - expect(messageAttachmentComponents.length).toBe(2); - - messageAttachmentComponents.forEach(messageAttachmentComponent => { - const opacity = messageAttachmentComponent?.props.style.opacity; - expect(opacity).toBe(1.0); - }); - }); - - it("should show the cancelled banner when the PN message is cancelled", () => { + it("should match the snapshot", () => { const { component } = renderComponent( - generateComponentProperties({ - ...generatePnMessage(), - isCancelled: true - }) + generateComponentProperties( + thirdPartyMessage.id as UIMessageId, + pnMessage! + ) ); - expect(component.queryByTestId("PnCancelledMessageBanner")).toBeDefined(); - }); - - it("every attachment item should have a semi-transparent style when the PN message is cancelled", () => { - const { component } = renderComponent( - generateComponentProperties({ - ...generatePnMessage(), - isCancelled: true - }) - ); - const messageAttachmentComponents = component.queryAllByTestId( - "MessageAttachmentTouchable" - ); - expect(messageAttachmentComponents.length).toBe(2); - - messageAttachmentComponents.forEach(messageAttachmentComponent => { - const opacity = messageAttachmentComponent?.props.style.opacity; - expect(opacity).toBe(0.5); - }); - }); - - it("every attachment item should handle input when the PN message not is cancelled", async () => { - const { component } = renderComponent( - generateComponentProperties(generatePnMessage()) - ); - const messageAttachmentComponents = component.queryAllByTestId( - "MessageAttachmentTouchable" - ); - expect(messageAttachmentComponents.length).toBe(2); - await act(() => { - messageAttachmentComponents.forEach(messageAttachmentComponent => - fireEvent.press(messageAttachmentComponent) - ); - }); - expect(mockedOnAttachmentSelect).toHaveBeenCalledTimes( - messageAttachmentComponents.length - ); - }); - - it("every attachment item should not handle input when the PN message is cancelled", async () => { - const { component } = renderComponent( - generateComponentProperties({ ...generatePnMessage(), isCancelled: true }) - ); - const messageAttachmentComponents = component.queryAllByTestId( - "MessageAttachmentTouchable" - ); - expect(messageAttachmentComponents.length).toBe(2); - // eslint-disable-next-line sonarjs/no-identical-functions - await act(() => { - messageAttachmentComponents.forEach(messageAttachmentComponent => - fireEvent.press(messageAttachmentComponent) - ); - }); - expect(mockedOnAttachmentSelect).toHaveBeenCalledTimes(0); - }); - - it("should NOT render the F24 section if there are no multiple F24", () => { - const { component } = renderComponent( - generateComponentProperties(generatePnMessage()) - ); - expect(component.queryByTestId("pn-message-f24-section")).toBeFalsy(); + expect(component).toMatchSnapshot(); }); }); -const generateTestMessageId = () => "00000000000000000000000004" as UIMessageId; -const generateTestFiscalCode = () => "AAABBB00A00A000A"; -const generatePnMessage = (): PNMessage => ({ - iun: "731143-7-0317-8200-0", - subject: "This is the message subject", - senderDenomination: "Sender denomination", - abstract: "Message abstract", - notificationStatusHistory: [], - recipients: [ - { - recipientType: "-", - taxId: generateTestFiscalCode(), - denomination: "AaAaAa BbBbBb", - payment: { - noticeCode: "026773337463073118", - creditorTaxId: "00000000009" - } - } - ] as Array, - attachments: [ - { - messageId: generateTestMessageId(), - id: "1", - displayName: "A First Attachment", - contentType: "application/pdf", - category: ATTACHMENT_CATEGORY.DOCUMENT, - resourceUrl: { href: "/resource/attachment1.pdf" } - }, - { - messageId: generateTestMessageId(), - id: "2", - displayName: "A Second Attachment", - contentType: "application/pdf", - category: ATTACHMENT_CATEGORY.DOCUMENT, - resourceUrl: { href: "/resource/attachment2.pdf" } - } - ] as Array -}); -const generateComponentProperties = (pnMessage: PNMessage) => ({ +const generateComponentProperties = ( + messageId: UIMessageId, + message: PNMessage +) => ({ + messageId, + message, payments: undefined, - messageId: generateTestMessageId(), - message: pnMessage, service: undefined }); @@ -173,12 +40,13 @@ const renderComponent = ( props: React.ComponentProps ) => { const globalState = appReducer(undefined, applicationChangeState("active")); - const store = createStore(appReducer, globalState as any); + const mockStore = configureMockStore(); + const store: ReturnType = mockStore(globalState); return { component: renderScreenWithNavigationStoreContext( () => , - PN_ROUTES.MESSAGE_DETAILS, + "DUMMY_ROUTE", {}, store ), diff --git a/ts/features/pn/components/__test__/MessageDetailsContent.test.tsx b/ts/features/pn/components/__test__/MessageDetailsContent.test.tsx new file mode 100644 index 00000000000..31e5a7c3603 --- /dev/null +++ b/ts/features/pn/components/__test__/MessageDetailsContent.test.tsx @@ -0,0 +1,15 @@ +import React from "react"; +import { render } from "@testing-library/react-native"; +import { MessageDetailsContent } from "../MessageDetailsContent"; + +describe("MessageDetailsContent component", () => { + it("should match the snapshot when abstract is defined", () => { + const component = render(); + expect(component.toJSON()).toMatchSnapshot(); + }); + + it("should match the snapshot when abstract is not defined", () => { + const component = render(); + expect(component.toJSON()).toMatchSnapshot(); + }); +}); diff --git a/ts/features/pn/components/__test__/__snapshots__/MessageDetails.test.tsx.snap b/ts/features/pn/components/__test__/__snapshots__/MessageDetails.test.tsx.snap new file mode 100644 index 00000000000..45785dc4e5f --- /dev/null +++ b/ts/features/pn/components/__test__/__snapshots__/MessageDetails.test.tsx.snap @@ -0,0 +1,449 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MessageDetails component should match the snapshot 1`] = ` + + + + + + + + + + + + + DUMMY_ROUTE + + + + + + + + + + + + + + + + + + + + + ######## subject ######## + + + + 01 Jan 2020, 00:00 + + + + + + + + ######## abstract ######## + + + + + + + + + + + + + + +`; diff --git a/ts/features/pn/components/__test__/__snapshots__/MessageDetailsContent.test.tsx.snap b/ts/features/pn/components/__test__/__snapshots__/MessageDetailsContent.test.tsx.snap new file mode 100644 index 00000000000..19f593ad32a --- /dev/null +++ b/ts/features/pn/components/__test__/__snapshots__/MessageDetailsContent.test.tsx.snap @@ -0,0 +1,51 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MessageDetailsContent component should match the snapshot when abstract is defined 1`] = ` + + + + abstract + + +`; + +exports[`MessageDetailsContent component should match the snapshot when abstract is not defined 1`] = `null`; diff --git a/ts/features/pn/navigation/navigator.tsx b/ts/features/pn/navigation/navigator.tsx index 5b6a533ca60..d837d801971 100644 --- a/ts/features/pn/navigation/navigator.tsx +++ b/ts/features/pn/navigation/navigator.tsx @@ -2,30 +2,50 @@ import * as React from "react"; import { createStackNavigator } from "@react-navigation/stack"; import { isGestureEnabled } from "../../../utils/navigation"; import { MessageDetailsScreen } from "../screens/MessageDetailsScreen"; +import { LegacyMessageDetailsScreen } from "../screens/LegacyMessageDetailsScreen"; import { AttachmentPreviewScreen } from "../screens/AttachmentPreviewScreen"; import { PaidPaymentScreen } from "../screens/PaidPaymentScreen"; +import { useIOSelector } from "../../../store/hooks"; +import { isDesignSystemEnabledSelector } from "../../../store/reducers/persistedPreferences"; import { PnParamsList } from "./params"; import PN_ROUTES from "./routes"; const Stack = createStackNavigator(); -export const PnStackNavigator = () => ( - - - - - -); +export const PnStackNavigator = () => { + const isDesignSystemEnabled = useIOSelector(isDesignSystemEnabledSelector); + + return ( + + + + + + ); +}; diff --git a/ts/features/pn/screens/LegacyMessageDetailsScreen.tsx b/ts/features/pn/screens/LegacyMessageDetailsScreen.tsx new file mode 100644 index 00000000000..7d2ad7d08a2 --- /dev/null +++ b/ts/features/pn/screens/LegacyMessageDetailsScreen.tsx @@ -0,0 +1,132 @@ +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 React from "react"; +import { SafeAreaView } from "react-native"; +import { useFocusEffect, useNavigation } from "@react-navigation/native"; +import { useStore } from "react-redux"; +import { IOStyles } from "../../../components/core/variables/IOStyles"; +import BaseScreenComponent from "../../../components/screens/BaseScreenComponent"; +import I18n from "../../../i18n"; +import { IOStackNavigationRouteProps } from "../../../navigation/params/AppParamsList"; +import { useIODispatch, useIOSelector } from "../../../store/hooks"; +import { serviceByIdSelector } from "../../../store/reducers/entities/services/servicesById"; +import { emptyContextualHelp } from "../../../utils/emptyContextualHelp"; +import { useOnFirstRender } from "../../../utils/hooks/useOnFirstRender"; +import { LegacyMessageDetails } from "../components/LegacyMessageDetails"; +import { PnParamsList } from "../navigation/params"; +import { pnMessageFromIdSelector } from "../store/reducers"; +import { cancelPreviousAttachmentDownload } from "../../messages/store/actions"; +import { profileFiscalCodeSelector } from "../../../store/reducers/profile"; +import { + containsF24FromPNMessagePot, + isCancelledFromPNMessagePot, + paymentsFromPNMessagePot +} from "../utils"; +import { trackPNUxSuccess } from "../analytics"; +import { isStrictSome } from "../../../utils/pot"; +import { + cancelPaymentStatusTracking, + cancelQueuedPaymentUpdates, + clearSelectedPayment, + startPaymentStatusTracking, + updatePaymentForMessage +} from "../store/actions"; +import { GlobalState } from "../../../store/reducers/types"; +import { selectedPaymentIdSelector } from "../store/reducers/payments"; +import { InfoScreenComponent } from "../../../components/infoScreen/InfoScreenComponent"; +import { renderInfoRasterImage } from "../../../components/infoScreen/imageRendering"; +import genericErrorIcon from "../../../../img/wallet/errors/generic-error-icon.png"; + +export const LegacyMessageDetailsScreen = ( + props: IOStackNavigationRouteProps +): React.ReactElement => { + const { messageId, serviceId, firstTimeOpening } = props.route.params; + + const dispatch = useIODispatch(); + const navigation = useNavigation(); + + const service = pot.toUndefined( + useIOSelector(state => serviceByIdSelector(state, serviceId)) + ); + + const currentFiscalCode = useIOSelector(profileFiscalCodeSelector); + const messagePot = useIOSelector(state => + pnMessageFromIdSelector(state, messageId) + ); + const payments = paymentsFromPNMessagePot(currentFiscalCode, messagePot); + + const customGoBack = React.useCallback(() => { + dispatch(cancelPreviousAttachmentDownload()); + dispatch(cancelQueuedPaymentUpdates()); + dispatch(cancelPaymentStatusTracking()); + navigation.goBack(); + }, [dispatch, navigation]); + + useOnFirstRender(() => { + dispatch(startPaymentStatusTracking(messageId)); + + if (isStrictSome(messagePot)) { + const paymentCount = payments?.length ?? 0; + const isCancelled = isCancelledFromPNMessagePot(messagePot); + const containsF24 = containsF24FromPNMessagePot(messagePot); + + trackPNUxSuccess( + paymentCount, + firstTimeOpening, + isCancelled, + containsF24 + ); + } + }); + + const store = useStore(); + useFocusEffect( + React.useCallback(() => { + const globalState = store.getState() as GlobalState; + const selectedPaymentId = selectedPaymentIdSelector(globalState); + if (selectedPaymentId) { + dispatch(clearSelectedPayment()); + dispatch( + updatePaymentForMessage.request({ + messageId, + paymentId: selectedPaymentId + }) + ); + } + }, [dispatch, messageId, store]) + ); + + return ( + + + {pipe( + messagePot, + pot.toOption, + O.flatten, + O.fold( + () => ( + + ), + message => ( + + ) + ) + )} + + + ); +}; diff --git a/ts/features/pn/screens/MessageDetailsScreen.tsx b/ts/features/pn/screens/MessageDetailsScreen.tsx index bd84320d4a5..cc4ff8e8dbe 100644 --- a/ts/features/pn/screens/MessageDetailsScreen.tsx +++ b/ts/features/pn/screens/MessageDetailsScreen.tsx @@ -1,19 +1,19 @@ +import React, { useCallback } from "react"; +import { + RouteProp, + useFocusEffect, + useNavigation, + useRoute +} from "@react-navigation/native"; +import { useStore } from "react-redux"; 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 React from "react"; -import { SafeAreaView } from "react-native"; -import { useFocusEffect, useNavigation } from "@react-navigation/native"; -import { useStore } from "react-redux"; -import { IOStyles } from "../../../components/core/variables/IOStyles"; -import BaseScreenComponent from "../../../components/screens/BaseScreenComponent"; import { ServiceId } from "../../../../definitions/backend/ServiceId"; import I18n from "../../../i18n"; -import { IOStackNavigationRouteProps } from "../../../navigation/params/AppParamsList"; import { useIODispatch, useIOSelector } from "../../../store/hooks"; import { UIMessageId } from "../../messages/types"; import { serviceByIdSelector } from "../../../store/reducers/entities/services/servicesById"; -import { emptyContextualHelp } from "../../../utils/emptyContextualHelp"; import { useOnFirstRender } from "../../../utils/hooks/useOnFirstRender"; import { MessageDetails } from "../components/MessageDetails"; import { PnParamsList } from "../navigation/params"; @@ -36,40 +36,48 @@ import { } from "../store/actions"; import { GlobalState } from "../../../store/reducers/types"; import { selectedPaymentIdSelector } from "../store/reducers/payments"; -import { InfoScreenComponent } from "../../../components/infoScreen/InfoScreenComponent"; -import { renderInfoRasterImage } from "../../../components/infoScreen/imageRendering"; -import genericErrorIcon from "../../../../img/wallet/errors/generic-error-icon.png"; +import { useHeaderSecondLevel } from "../../../hooks/useHeaderSecondLevel"; +import { OperationResultScreenContent } from "../../../components/screens/OperationResultScreenContent"; -export type MessageDetailsScreenNavigationParams = Readonly<{ +export type MessageDetailsScreenNavigationParams = { messageId: UIMessageId; serviceId: ServiceId; firstTimeOpening: boolean; -}>; +}; -export const MessageDetailsScreen = ( - props: IOStackNavigationRouteProps -): React.ReactElement => { - const { messageId, serviceId, firstTimeOpening } = props.route.params; +type MessageDetailsRouteProps = RouteProp< + PnParamsList, + "PN_ROUTES_MESSAGE_DETAILS" +>; +export const MessageDetailsScreen = () => { const dispatch = useIODispatch(); const navigation = useNavigation(); + const route = useRoute(); + + const { messageId, serviceId, firstTimeOpening } = route.params; const service = pot.toUndefined( useIOSelector(state => serviceByIdSelector(state, serviceId)) ); - const currentFiscalCode = useIOSelector(profileFiscalCodeSelector); const messagePot = useIOSelector(state => pnMessageFromIdSelector(state, messageId) ); const payments = paymentsFromPNMessagePot(currentFiscalCode, messagePot); - const customGoBack = React.useCallback(() => { + const goBack = useCallback(() => { dispatch(cancelPreviousAttachmentDownload()); dispatch(cancelQueuedPaymentUpdates()); dispatch(cancelPaymentStatusTracking()); navigation.goBack(); - }, [dispatch, navigation]); + }, []); + + useHeaderSecondLevel({ + title: "", + goBack, + supportRequest: true + }); useOnFirstRender(() => { dispatch(startPaymentStatusTracking(messageId)); @@ -90,7 +98,7 @@ export const MessageDetailsScreen = ( const store = useStore(); useFocusEffect( - React.useCallback(() => { + useCallback(() => { const globalState = store.getState() as GlobalState; const selectedPaymentId = selectedPaymentIdSelector(globalState); if (selectedPaymentId) { @@ -106,35 +114,29 @@ export const MessageDetailsScreen = ( ); return ( - - - {pipe( - messagePot, - pot.toOption, - O.flatten, - O.fold( - () => ( - - ), - message => ( - - ) + <> + {pipe( + messagePot, + pot.toOption, + O.flatten, + O.fold( + () => ( + + ), + message => ( + ) - )} - - + ) + )} + ); }; diff --git a/ts/features/pn/screens/__test__/MessageDetailsScreen.test.tsx b/ts/features/pn/screens/__test__/MessageDetailsScreen.test.tsx new file mode 100644 index 00000000000..0aa4268dca0 --- /dev/null +++ b/ts/features/pn/screens/__test__/MessageDetailsScreen.test.tsx @@ -0,0 +1,83 @@ +import configureMockStore from "redux-mock-store"; +import { Action, Store } from "redux"; +import PN_ROUTES from "../../navigation/routes"; +import { GlobalState } from "../../../../store/reducers/types"; +import { appReducer } from "../../../../store/reducers"; +import { MessageDetailsScreen } from "../MessageDetailsScreen"; +import { renderScreenWithNavigationStoreContext } from "../../../../utils/testWrapper"; +import { reproduceSequence } from "../../../../utils/tests"; +import { + loadMessageById, + loadMessageDetails, + loadThirdPartyMessage +} from "../../../messages/store/actions"; +import { + toUIMessage, + toUIMessageDetails +} from "../../../messages/store/reducers/transformers"; +import { message_1 } from "../../../messages/__mocks__/message"; +import { loadServiceDetail } from "../../../../store/actions/services"; +import { service_1 } from "../../../messages/__mocks__/messages"; +import { UIMessageId } from "../../../messages/types"; +import { applicationChangeState } from "../../../../store/actions/application"; +import { thirdPartyMessage } from "../../__mocks__/message"; + +describe("MessageDetailsScreen", () => { + it("should match the snapshot when there is an error", () => { + const sequenceOfActions: ReadonlyArray = [ + applicationChangeState("active") + ]; + + const state: GlobalState = reproduceSequence( + {} as GlobalState, + appReducer, + sequenceOfActions + ); + const mockStore = configureMockStore(); + const store: Store = mockStore(state); + + const { component } = renderComponent(store); + expect(component).toMatchSnapshot(); + }); + + it("should match the snapshot when everything went fine", () => { + const sequenceOfActions: ReadonlyArray = [ + applicationChangeState("active"), + loadMessageById.success(toUIMessage(message_1)), + loadServiceDetail.success(service_1), + loadMessageDetails.success(toUIMessageDetails(message_1)), + loadThirdPartyMessage.success({ + id: message_1.id as UIMessageId, + content: thirdPartyMessage + }) + ]; + + const state: GlobalState = reproduceSequence( + {} as GlobalState, + appReducer, + sequenceOfActions + ); + const mockStore = configureMockStore(); + const store: Store = mockStore(state); + + const { component } = renderComponent(store); + expect(component).toMatchSnapshot(); + }); +}); + +const renderComponent = (store: Store) => { + const { id, sender_service_id } = message_1; + + return { + component: renderScreenWithNavigationStoreContext( + MessageDetailsScreen, + PN_ROUTES.MESSAGE_DETAILS, + { + firstTimeOpening: false, + messageId: id, + serviceId: sender_service_id + }, + store + ) + }; +}; diff --git a/ts/features/pn/screens/__test__/__snapshots__/MessageDetailsScreen.test.tsx.snap b/ts/features/pn/screens/__test__/__snapshots__/MessageDetailsScreen.test.tsx.snap new file mode 100644 index 00000000000..48edcca56d3 --- /dev/null +++ b/ts/features/pn/screens/__test__/__snapshots__/MessageDetailsScreen.test.tsx.snap @@ -0,0 +1,1372 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MessageDetailsScreen should match the snapshot when everything went fine 1`] = ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ######## subject ######## + + + + 01 Jan 2020, 00:00 + + + + + + + + ######## abstract ######## + + + + + + + + + + + + + + +`; + +exports[`MessageDetailsScreen should match the snapshot when there is an error 1`] = ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Qualcosa è andato storto + + + + Non è stato possibile recuperare i dettagli del tuo messaggio. Riprova per favore + + + + + + + + + + + + + + +`; diff --git a/ts/features/pn/store/types/transformers.ts b/ts/features/pn/store/types/transformers.ts index e89ffc030b4..3b0c5269074 100644 --- a/ts/features/pn/store/types/transformers.ts +++ b/ts/features/pn/store/types/transformers.ts @@ -15,6 +15,7 @@ export const toPNMessage = ( O.chainNullableK(message => message.details), O.map(details => ({ ...details, + created_at: messageFromApi.created_at, attachments: pipe( attachmentsFromThirdPartyMessage(messageFromApi), O.toUndefined diff --git a/ts/features/pn/store/types/types.ts b/ts/features/pn/store/types/types.ts index 525cce459d3..192e7d9652a 100644 --- a/ts/features/pn/store/types/types.ts +++ b/ts/features/pn/store/types/types.ts @@ -1,7 +1,7 @@ import { IOReceivedNotification } from "../../../../../definitions/pn/IOReceivedNotification"; import { UIAttachment } from "../../../messages/types"; -export type PNMessage = IOReceivedNotification & - Readonly<{ - attachments?: ReadonlyArray; - }>; +export type PNMessage = IOReceivedNotification & { + created_at: Date; + attachments?: ReadonlyArray; +}; From e517229f28bb25e38443862485197be73442fe03 Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 29 Jan 2024 16:20:12 +0100 Subject: [PATCH 4/7] chore: E2E tests failure reporter (#5452) ## Short description This PR adds a Github action to report E2E tests failure to Slack. ## List of changes proposed in this pull request - Added proper worflow on E2E test to notify a failure ## How to test Refer to the working message in the Slack channel. --- .github/actions/notify-e2e/action.yml | 20 + .github/workflows/test-e2e.yml | 6 + scripts/e2e_message/Pipfile | 15 + scripts/e2e_message/Pipfile.lock | 541 ++++++++++++++++++++++++++ scripts/e2e_message/e2e_notifier.py | 56 +++ 5 files changed, 638 insertions(+) create mode 100644 .github/actions/notify-e2e/action.yml create mode 100644 scripts/e2e_message/Pipfile create mode 100644 scripts/e2e_message/Pipfile.lock create mode 100644 scripts/e2e_message/e2e_notifier.py diff --git a/.github/actions/notify-e2e/action.yml b/.github/actions/notify-e2e/action.yml new file mode 100644 index 00000000000..57ef0f5dd3a --- /dev/null +++ b/.github/actions/notify-e2e/action.yml @@ -0,0 +1,20 @@ +name: Actions to notify E2E tests failure through Slack message +description: 'This action collects the steps to setup python env and run job' +runs: + using: "composite" + steps: + - name: setup python + uses: actions/setup-python@57ded4d7d5e986d7296eab16560982c6dd7c923b #v4.6.0 + with: + python-version: '3.8' + - name: install pipenv + run: pip install pipenv + shell: bash + - name: report E2E tests failure + run: | + cd scripts/e2e_message + pipenv install + pipenv run python3 e2e_notifier.py + env: + BUILD_ID: ${{ github.run_id }} + shell: bash \ No newline at end of file diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index f1cac87fd51..c49ae1f34ad 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -9,6 +9,7 @@ jobs: run-e2e-test-ios: needs: run-static-checks runs-on: macos-latest + environment: dev concurrency: group: ${{ github.workflow }}-e2e-tests-${{ github.head_ref || github.run_id }} cancel-in-progress: true @@ -60,6 +61,11 @@ jobs: path: './_io-dev-api-server_' - id: run-e2e-tests run: bash ./.github/scripts/run-e2e-tests.sh + - id: notify-test-failure + if: failure() + uses: ./.github/actions/notify-e2e + env: + IO_APP_SLACK_HELPER_BOT_TOKEN: ${{ secrets.IO_APP_SLACK_HELPER_BOT_TOKEN }} - id: upload-artifacts uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v.3.1.2 if: always() diff --git a/scripts/e2e_message/Pipfile b/scripts/e2e_message/Pipfile new file mode 100644 index 00000000000..4a715d5d192 --- /dev/null +++ b/scripts/e2e_message/Pipfile @@ -0,0 +1,15 @@ +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] + +[packages] +urllib3 = ">=1.26.6" +requests = "*" +slackclient = "*" +urlextract = "*" + +[requires] +python_version = "3.8" diff --git a/scripts/e2e_message/Pipfile.lock b/scripts/e2e_message/Pipfile.lock new file mode 100644 index 00000000000..607da490b79 --- /dev/null +++ b/scripts/e2e_message/Pipfile.lock @@ -0,0 +1,541 @@ +{ + "_meta": { + "hash": { + "sha256": "e9c77c3a436707c321108075603a5b1973e55e49e7d40c694ed421c6d501e172" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.8" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "aiohttp": { + "hashes": [ + "sha256:05857848da443c8c12110d99285d499b4e84d59918a21132e45c3f0804876994", + "sha256:05a183f1978802588711aed0dea31e697d760ce9055292db9dc1604daa9a8ded", + "sha256:09f23292d29135025e19e8ff4f0a68df078fe4ee013bca0105b2e803989de92d", + "sha256:11ca808f9a6b63485059f5f6e164ef7ec826483c1212a44f268b3653c91237d8", + "sha256:1736d87dad8ef46a8ec9cddd349fa9f7bd3a064c47dd6469c0d6763d3d49a4fc", + "sha256:1df43596b826022b14998f0460926ce261544fedefe0d2f653e1b20f49e96454", + "sha256:23170247ef89ffa842a02bbfdc425028574d9e010611659abeb24d890bc53bb8", + "sha256:2779f5e7c70f7b421915fd47db332c81de365678180a9f3ab404088f87ba5ff9", + "sha256:28185e36a78d247c55e9fbea2332d16aefa14c5276a582ce7a896231c6b1c208", + "sha256:2cbc14a13fb6b42d344e4f27746a4b03a2cb0c1c3c5b932b0d6ad8881aa390e3", + "sha256:2d71abc15ff7047412ef26bf812dfc8d0d1020d664617f4913df2df469f26b76", + "sha256:2d820162c8c2bdbe97d328cd4f417c955ca370027dce593345e437b2e9ffdc4d", + "sha256:317719d7f824eba55857fe0729363af58e27c066c731bc62cd97bc9c3d9c7ea4", + "sha256:35a68cd63ca6aaef5707888f17a70c36efe62b099a4e853d33dc2e9872125be8", + "sha256:3607375053df58ed6f23903aa10cf3112b1240e8c799d243bbad0f7be0666986", + "sha256:366bc870d7ac61726f32a489fbe3d1d8876e87506870be66b01aeb84389e967e", + "sha256:3abf0551874fecf95f93b58f25ef4fc9a250669a2257753f38f8f592db85ddea", + "sha256:3d7f6235c7475658acfc1769d968e07ab585c79f6ca438ddfecaa9a08006aee2", + "sha256:3dd8119752dd30dd7bca7d4bc2a92a59be6a003e4e5c2cf7e248b89751b8f4b7", + "sha256:42fe4fd9f0dfcc7be4248c162d8056f1d51a04c60e53366b0098d1267c4c9da8", + "sha256:45820ddbb276113ead8d4907a7802adb77548087ff5465d5c554f9aa3928ae7d", + "sha256:4790e44f46a4aa07b64504089def5744d3b6780468c4ec3a1a36eb7f2cae9814", + "sha256:4afa8f71dba3a5a2e1e1282a51cba7341ae76585345c43d8f0e624882b622218", + "sha256:4b777c9286b6c6a94f50ddb3a6e730deec327e9e2256cb08b5530db0f7d40fd8", + "sha256:4ee1b4152bc3190cc40ddd6a14715e3004944263ea208229ab4c297712aa3075", + "sha256:51a4cd44788ea0b5e6bb8fa704597af3a30be75503a7ed1098bc5b8ffdf6c982", + "sha256:536b01513d67d10baf6f71c72decdf492fb7433c5f2f133e9a9087379d4b6f31", + "sha256:571760ad7736b34d05597a1fd38cbc7d47f7b65deb722cb8e86fd827404d1f6b", + "sha256:5a2eb5311a37fe105aa35f62f75a078537e1a9e4e1d78c86ec9893a3c97d7a30", + "sha256:5ab16c254e2312efeb799bc3c06897f65a133b38b69682bf75d1f1ee1a9c43a9", + "sha256:65b0a70a25456d329a5e1426702dde67be0fb7a4ead718005ba2ca582d023a94", + "sha256:673343fbc0c1ac44d0d2640addc56e97a052504beacd7ade0dc5e76d3a4c16e8", + "sha256:6777a390e41e78e7c45dab43a4a0196c55c3b8c30eebe017b152939372a83253", + "sha256:6896b8416be9ada4d22cd359d7cb98955576ce863eadad5596b7cdfbf3e17c6c", + "sha256:694df243f394629bcae2d8ed94c589a181e8ba8604159e6e45e7b22e58291113", + "sha256:70e851f596c00f40a2f00a46126c95c2e04e146015af05a9da3e4867cfc55911", + "sha256:7276fe0017664414fdc3618fca411630405f1aaf0cc3be69def650eb50441787", + "sha256:76a86a9989ebf82ee61e06e2bab408aec4ea367dc6da35145c3352b60a112d11", + "sha256:7a94bde005a8f926d0fa38b88092a03dea4b4875a61fbcd9ac6f4351df1b57cd", + "sha256:7ae5f99a32c53731c93ac3075abd3e1e5cfbe72fc3eaac4c27c9dd64ba3b19fe", + "sha256:7e8a3b79b6d186a9c99761fd4a5e8dd575a48d96021f220ac5b5fa856e5dd029", + "sha256:816f4db40555026e4cdda604a1088577c1fb957d02f3f1292e0221353403f192", + "sha256:8303531e2c17b1a494ffaeba48f2da655fe932c4e9a2626c8718403c83e5dd2b", + "sha256:8488519aa05e636c5997719fe543c8daf19f538f4fa044f3ce94bee608817cff", + "sha256:87c8b0a6487e8109427ccf638580865b54e2e3db4a6e0e11c02639231b41fc0f", + "sha256:8c9e5f4d7208cda1a2bb600e29069eecf857e6980d0ccc922ccf9d1372c16f4b", + "sha256:94697c7293199c2a2551e3e3e18438b4cba293e79c6bc2319f5fd652fccb7456", + "sha256:9623cfd9e85b76b83ef88519d98326d4731f8d71869867e47a0b979ffec61c73", + "sha256:98d21092bf2637c5fa724a428a69e8f5955f2182bff61f8036827cf6ce1157bf", + "sha256:99ae01fb13a618b9942376df77a1f50c20a281390dad3c56a6ec2942e266220d", + "sha256:9c196b30f1b1aa3363a69dd69079ae9bec96c2965c4707eaa6914ba099fb7d4f", + "sha256:a00ce44c21612d185c5275c5cba4bab8d7c1590f248638b667ed8a782fa8cd6f", + "sha256:a1b66dbb8a7d5f50e9e2ea3804b01e766308331d0cac76eb30c563ac89c95985", + "sha256:a1d7edf74a36de0e5ca50787e83a77cf352f5504eb0ffa3f07000a911ba353fb", + "sha256:a1e3b3c107ccb0e537f309f719994a55621acd2c8fdf6d5ce5152aed788fb940", + "sha256:a486ddf57ab98b6d19ad36458b9f09e6022de0381674fe00228ca7b741aacb2f", + "sha256:ac9669990e2016d644ba8ae4758688534aabde8dbbc81f9af129c3f5f01ca9cd", + "sha256:b1a2ea8252cacc7fd51df5a56d7a2bb1986ed39be9397b51a08015727dfb69bd", + "sha256:c5b7bf8fe4d39886adc34311a233a2e01bc10eb4e842220235ed1de57541a896", + "sha256:c67a51ea415192c2e53e4e048c78bab82d21955b4281d297f517707dc836bf3d", + "sha256:ca4fddf84ac7d8a7d0866664936f93318ff01ee33e32381a115b19fb5a4d1202", + "sha256:d5b9345ab92ebe6003ae11d8092ce822a0242146e6fa270889b9ba965457ca40", + "sha256:d97c3e286d0ac9af6223bc132dc4bad6540b37c8d6c0a15fe1e70fb34f9ec411", + "sha256:db04d1de548f7a62d1dd7e7cdf7c22893ee168e22701895067a28a8ed51b3735", + "sha256:dcf71c55ec853826cd70eadb2b6ac62ec577416442ca1e0a97ad875a1b3a0305", + "sha256:de3cc86f4ea8b4c34a6e43a7306c40c1275e52bfa9748d869c6b7d54aa6dad80", + "sha256:deac0a32aec29608eb25d730f4bc5a261a65b6c48ded1ed861d2a1852577c932", + "sha256:e18d92c3e9e22553a73e33784fcb0ed484c9874e9a3e96c16a8d6a1e74a0217b", + "sha256:eb6dfd52063186ac97b4caa25764cdbcdb4b10d97f5c5f66b0fa95052e744eb7", + "sha256:f09960b5bb1017d16c0f9e9f7fc42160a5a49fa1e87a175fd4a2b1a1833ea0af", + "sha256:f1e4f254e9c35d8965d377e065c4a8a55d396fe87c8e7e8429bcfdeeb229bfb3", + "sha256:f32c86dc967ab8c719fd229ce71917caad13cc1e8356ee997bf02c5b368799bf", + "sha256:f50b4663c3e0262c3a361faf440761fbef60ccdde5fe8545689a4b3a3c149fb4", + "sha256:f8e05f5163528962ce1d1806fce763ab893b1c5b7ace0a3538cd81a90622f844", + "sha256:f929f4c9b9a00f3e6cc0587abb95ab9c05681f8b14e0fe1daecfa83ea90f8318", + "sha256:f9e09a1c83521d770d170b3801eea19b89f41ccaa61d53026ed111cb6f088887" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==3.9.0" + }, + "aiosignal": { + "hashes": [ + "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc", + "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17" + ], + "markers": "python_version >= '3.7'", + "version": "==1.3.1" + }, + "appdirs": { + "hashes": [ + "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", + "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" + ], + "version": "==1.4.4" + }, + "async-timeout": { + "hashes": [ + "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f", + "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028" + ], + "markers": "python_version < '3.11'", + "version": "==4.0.3" + }, + "attrs": { + "hashes": [ + "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04", + "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015" + ], + "markers": "python_version >= '3.7'", + "version": "==23.1.0" + }, + "certifi": { + "hashes": [ + "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082", + "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9" + ], + "markers": "python_version >= '3.6'", + "version": "==2023.7.22" + }, + "charset-normalizer": { + "hashes": [ + "sha256:02673e456dc5ab13659f85196c534dc596d4ef260e4d86e856c3b2773ce09843", + "sha256:02af06682e3590ab952599fbadac535ede5d60d78848e555aa58d0c0abbde786", + "sha256:03680bb39035fbcffe828eae9c3f8afc0428c91d38e7d61aa992ef7a59fb120e", + "sha256:0570d21da019941634a531444364f2482e8db0b3425fcd5ac0c36565a64142c8", + "sha256:09c77f964f351a7369cc343911e0df63e762e42bac24cd7d18525961c81754f4", + "sha256:0d3d5b7db9ed8a2b11a774db2bbea7ba1884430a205dbd54a32d61d7c2a190fa", + "sha256:1063da2c85b95f2d1a430f1c33b55c9c17ffaf5e612e10aeaad641c55a9e2b9d", + "sha256:12ebea541c44fdc88ccb794a13fe861cc5e35d64ed689513a5c03d05b53b7c82", + "sha256:153e7b6e724761741e0974fc4dcd406d35ba70b92bfe3fedcb497226c93b9da7", + "sha256:15b26ddf78d57f1d143bdf32e820fd8935d36abe8a25eb9ec0b5a71c82eb3895", + "sha256:1872d01ac8c618a8da634e232f24793883d6e456a66593135aeafe3784b0848d", + "sha256:187d18082694a29005ba2944c882344b6748d5be69e3a89bf3cc9d878e548d5a", + "sha256:1b2919306936ac6efb3aed1fbf81039f7087ddadb3160882a57ee2ff74fd2382", + "sha256:232ac332403e37e4a03d209a3f92ed9071f7d3dbda70e2a5e9cff1c4ba9f0678", + "sha256:23e8565ab7ff33218530bc817922fae827420f143479b753104ab801145b1d5b", + "sha256:24817cb02cbef7cd499f7c9a2735286b4782bd47a5b3516a0e84c50eab44b98e", + "sha256:249c6470a2b60935bafd1d1d13cd613f8cd8388d53461c67397ee6a0f5dce741", + "sha256:24a91a981f185721542a0b7c92e9054b7ab4fea0508a795846bc5b0abf8118d4", + "sha256:2502dd2a736c879c0f0d3e2161e74d9907231e25d35794584b1ca5284e43f596", + "sha256:250c9eb0f4600361dd80d46112213dff2286231d92d3e52af1e5a6083d10cad9", + "sha256:278c296c6f96fa686d74eb449ea1697f3c03dc28b75f873b65b5201806346a69", + "sha256:2935ffc78db9645cb2086c2f8f4cfd23d9b73cc0dc80334bc30aac6f03f68f8c", + "sha256:2f4a0033ce9a76e391542c182f0d48d084855b5fcba5010f707c8e8c34663d77", + "sha256:30a85aed0b864ac88309b7d94be09f6046c834ef60762a8833b660139cfbad13", + "sha256:380c4bde80bce25c6e4f77b19386f5ec9db230df9f2f2ac1e5ad7af2caa70459", + "sha256:3ae38d325b512f63f8da31f826e6cb6c367336f95e418137286ba362925c877e", + "sha256:3b447982ad46348c02cb90d230b75ac34e9886273df3a93eec0539308a6296d7", + "sha256:3debd1150027933210c2fc321527c2299118aa929c2f5a0a80ab6953e3bd1908", + "sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a", + "sha256:468d2a840567b13a590e67dd276c570f8de00ed767ecc611994c301d0f8c014f", + "sha256:4cc152c5dd831641e995764f9f0b6589519f6f5123258ccaca8c6d34572fefa8", + "sha256:542da1178c1c6af8873e143910e2269add130a299c9106eef2594e15dae5e482", + "sha256:557b21a44ceac6c6b9773bc65aa1b4cc3e248a5ad2f5b914b91579a32e22204d", + "sha256:5707a746c6083a3a74b46b3a631d78d129edab06195a92a8ece755aac25a3f3d", + "sha256:588245972aca710b5b68802c8cad9edaa98589b1b42ad2b53accd6910dad3545", + "sha256:5adf257bd58c1b8632046bbe43ee38c04e1038e9d37de9c57a94d6bd6ce5da34", + "sha256:619d1c96099be5823db34fe89e2582b336b5b074a7f47f819d6b3a57ff7bdb86", + "sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6", + "sha256:67b8cc9574bb518ec76dc8e705d4c39ae78bb96237cb533edac149352c1f39fe", + "sha256:6a685067d05e46641d5d1623d7c7fdf15a357546cbb2f71b0ebde91b175ffc3e", + "sha256:70f1d09c0d7748b73290b29219e854b3207aea922f839437870d8cc2168e31cc", + "sha256:750b446b2ffce1739e8578576092179160f6d26bd5e23eb1789c4d64d5af7dc7", + "sha256:7966951325782121e67c81299a031f4c115615e68046f79b85856b86ebffc4cd", + "sha256:7b8b8bf1189b3ba9b8de5c8db4d541b406611a71a955bbbd7385bbc45fcb786c", + "sha256:7f5d10bae5d78e4551b7be7a9b29643a95aded9d0f602aa2ba584f0388e7a557", + "sha256:805dfea4ca10411a5296bcc75638017215a93ffb584c9e344731eef0dcfb026a", + "sha256:81bf654678e575403736b85ba3a7867e31c2c30a69bc57fe88e3ace52fb17b89", + "sha256:82eb849f085624f6a607538ee7b83a6d8126df6d2f7d3b319cb837b289123078", + "sha256:85a32721ddde63c9df9ebb0d2045b9691d9750cb139c161c80e500d210f5e26e", + "sha256:86d1f65ac145e2c9ed71d8ffb1905e9bba3a91ae29ba55b4c46ae6fc31d7c0d4", + "sha256:86f63face3a527284f7bb8a9d4f78988e3c06823f7bea2bd6f0e0e9298ca0403", + "sha256:8eaf82f0eccd1505cf39a45a6bd0a8cf1c70dcfc30dba338207a969d91b965c0", + "sha256:93aa7eef6ee71c629b51ef873991d6911b906d7312c6e8e99790c0f33c576f89", + "sha256:96c2b49eb6a72c0e4991d62406e365d87067ca14c1a729a870d22354e6f68115", + "sha256:9cf3126b85822c4e53aa28c7ec9869b924d6fcfb76e77a45c44b83d91afd74f9", + "sha256:9fe359b2e3a7729010060fbca442ca225280c16e923b37db0e955ac2a2b72a05", + "sha256:a0ac5e7015a5920cfce654c06618ec40c33e12801711da6b4258af59a8eff00a", + "sha256:a3f93dab657839dfa61025056606600a11d0b696d79386f974e459a3fbc568ec", + "sha256:a4b71f4d1765639372a3b32d2638197f5cd5221b19531f9245fcc9ee62d38f56", + "sha256:aae32c93e0f64469f74ccc730a7cb21c7610af3a775157e50bbd38f816536b38", + "sha256:aaf7b34c5bc56b38c931a54f7952f1ff0ae77a2e82496583b247f7c969eb1479", + "sha256:abecce40dfebbfa6abf8e324e1860092eeca6f7375c8c4e655a8afb61af58f2c", + "sha256:abf0d9f45ea5fb95051c8bfe43cb40cda383772f7e5023a83cc481ca2604d74e", + "sha256:ac71b2977fb90c35d41c9453116e283fac47bb9096ad917b8819ca8b943abecd", + "sha256:ada214c6fa40f8d800e575de6b91a40d0548139e5dc457d2ebb61470abf50186", + "sha256:b09719a17a2301178fac4470d54b1680b18a5048b481cb8890e1ef820cb80455", + "sha256:b1121de0e9d6e6ca08289583d7491e7fcb18a439305b34a30b20d8215922d43c", + "sha256:b3b2316b25644b23b54a6f6401074cebcecd1244c0b8e80111c9a3f1c8e83d65", + "sha256:b3d9b48ee6e3967b7901c052b670c7dda6deb812c309439adaffdec55c6d7b78", + "sha256:b5bcf60a228acae568e9911f410f9d9e0d43197d030ae5799e20dca8df588287", + "sha256:b8f3307af845803fb0b060ab76cf6dd3a13adc15b6b451f54281d25911eb92df", + "sha256:c2af80fb58f0f24b3f3adcb9148e6203fa67dd3f61c4af146ecad033024dde43", + "sha256:c350354efb159b8767a6244c166f66e67506e06c8924ed74669b2c70bc8735b1", + "sha256:c5a74c359b2d47d26cdbbc7845e9662d6b08a1e915eb015d044729e92e7050b7", + "sha256:c71f16da1ed8949774ef79f4a0260d28b83b3a50c6576f8f4f0288d109777989", + "sha256:d47ecf253780c90ee181d4d871cd655a789da937454045b17b5798da9393901a", + "sha256:d7eff0f27edc5afa9e405f7165f85a6d782d308f3b6b9d96016c010597958e63", + "sha256:d97d85fa63f315a8bdaba2af9a6a686e0eceab77b3089af45133252618e70884", + "sha256:db756e48f9c5c607b5e33dd36b1d5872d0422e960145b08ab0ec7fd420e9d649", + "sha256:dc45229747b67ffc441b3de2f3ae5e62877a282ea828a5bdb67883c4ee4a8810", + "sha256:e0fc42822278451bc13a2e8626cf2218ba570f27856b536e00cfa53099724828", + "sha256:e39c7eb31e3f5b1f88caff88bcff1b7f8334975b46f6ac6e9fc725d829bc35d4", + "sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2", + "sha256:e5c1502d4ace69a179305abb3f0bb6141cbe4714bc9b31d427329a95acfc8bdd", + "sha256:edfe077ab09442d4ef3c52cb1f9dab89bff02f4524afc0acf2d46be17dc479f5", + "sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe", + "sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293", + "sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e", + "sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e", + "sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.3.0" + }, + "filelock": { + "hashes": [ + "sha256:08c21d87ded6e2b9da6728c3dff51baf1dcecf973b768ef35bcbc3447edb9ad4", + "sha256:2e6f249f1f3654291606e046b09f1fd5eac39b360664c27f5aad072012f8bcbd" + ], + "markers": "python_version >= '3.8'", + "version": "==3.12.4" + }, + "frozenlist": { + "hashes": [ + "sha256:007df07a6e3eb3e33e9a1fe6a9db7af152bbd8a185f9aaa6ece10a3529e3e1c6", + "sha256:008eb8b31b3ea6896da16c38c1b136cb9fec9e249e77f6211d479db79a4eaf01", + "sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251", + "sha256:0c7c1b47859ee2cac3846fde1c1dc0f15da6cec5a0e5c72d101e0f83dcb67ff9", + "sha256:0e5c8764c7829343d919cc2dfc587a8db01c4f70a4ebbc49abde5d4b158b007b", + "sha256:10ff5faaa22786315ef57097a279b833ecab1a0bfb07d604c9cbb1c4cdc2ed87", + "sha256:17ae5cd0f333f94f2e03aaf140bb762c64783935cc764ff9c82dff626089bebf", + "sha256:19488c57c12d4e8095a922f328df3f179c820c212940a498623ed39160bc3c2f", + "sha256:1a0848b52815006ea6596c395f87449f693dc419061cc21e970f139d466dc0a0", + "sha256:1e78fb68cf9c1a6aa4a9a12e960a5c9dfbdb89b3695197aa7064705662515de2", + "sha256:261b9f5d17cac914531331ff1b1d452125bf5daa05faf73b71d935485b0c510b", + "sha256:2b8bcf994563466db019fab287ff390fffbfdb4f905fc77bc1c1d604b1c689cc", + "sha256:38461d02d66de17455072c9ba981d35f1d2a73024bee7790ac2f9e361ef1cd0c", + "sha256:490132667476f6781b4c9458298b0c1cddf237488abd228b0b3650e5ecba7467", + "sha256:491e014f5c43656da08958808588cc6c016847b4360e327a62cb308c791bd2d9", + "sha256:515e1abc578dd3b275d6a5114030b1330ba044ffba03f94091842852f806f1c1", + "sha256:556de4430ce324c836789fa4560ca62d1591d2538b8ceb0b4f68fb7b2384a27a", + "sha256:5833593c25ac59ede40ed4de6d67eb42928cca97f26feea219f21d0ed0959b79", + "sha256:6221d84d463fb110bdd7619b69cb43878a11d51cbb9394ae3105d082d5199167", + "sha256:6918d49b1f90821e93069682c06ffde41829c346c66b721e65a5c62b4bab0300", + "sha256:6c38721585f285203e4b4132a352eb3daa19121a035f3182e08e437cface44bf", + "sha256:71932b597f9895f011f47f17d6428252fc728ba2ae6024e13c3398a087c2cdea", + "sha256:7211ef110a9194b6042449431e08c4d80c0481e5891e58d429df5899690511c2", + "sha256:764226ceef3125e53ea2cb275000e309c0aa5464d43bd72abd661e27fffc26ab", + "sha256:7645a8e814a3ee34a89c4a372011dcd817964ce8cb273c8ed6119d706e9613e3", + "sha256:76d4711f6f6d08551a7e9ef28c722f4a50dd0fc204c56b4bcd95c6cc05ce6fbb", + "sha256:7f4f399d28478d1f604c2ff9119907af9726aed73680e5ed1ca634d377abb087", + "sha256:88f7bc0fcca81f985f78dd0fa68d2c75abf8272b1f5c323ea4a01a4d7a614efc", + "sha256:8d0edd6b1c7fb94922bf569c9b092ee187a83f03fb1a63076e7774b60f9481a8", + "sha256:901289d524fdd571be1c7be054f48b1f88ce8dddcbdf1ec698b27d4b8b9e5d62", + "sha256:93ea75c050c5bb3d98016b4ba2497851eadf0ac154d88a67d7a6816206f6fa7f", + "sha256:981b9ab5a0a3178ff413bca62526bb784249421c24ad7381e39d67981be2c326", + "sha256:9ac08e601308e41eb533f232dbf6b7e4cea762f9f84f6357136eed926c15d12c", + "sha256:a02eb8ab2b8f200179b5f62b59757685ae9987996ae549ccf30f983f40602431", + "sha256:a0c6da9aee33ff0b1a451e867da0c1f47408112b3391dd43133838339e410963", + "sha256:a6c8097e01886188e5be3e6b14e94ab365f384736aa1fca6a0b9e35bd4a30bc7", + "sha256:aa384489fefeb62321b238e64c07ef48398fe80f9e1e6afeff22e140e0850eef", + "sha256:ad2a9eb6d9839ae241701d0918f54c51365a51407fd80f6b8289e2dfca977cc3", + "sha256:b206646d176a007466358aa21d85cd8600a415c67c9bd15403336c331a10d956", + "sha256:b826d97e4276750beca7c8f0f1a4938892697a6bcd8ec8217b3312dad6982781", + "sha256:b89ac9768b82205936771f8d2eb3ce88503b1556324c9f903e7156669f521472", + "sha256:bd7bd3b3830247580de99c99ea2a01416dfc3c34471ca1298bccabf86d0ff4dc", + "sha256:bdf1847068c362f16b353163391210269e4f0569a3c166bc6a9f74ccbfc7e839", + "sha256:c11b0746f5d946fecf750428a95f3e9ebe792c1ee3b1e96eeba145dc631a9672", + "sha256:c5374b80521d3d3f2ec5572e05adc94601985cc526fb276d0c8574a6d749f1b3", + "sha256:ca265542ca427bf97aed183c1676e2a9c66942e822b14dc6e5f42e038f92a503", + "sha256:ce31ae3e19f3c902de379cf1323d90c649425b86de7bbdf82871b8a2a0615f3d", + "sha256:ceb6ec0a10c65540421e20ebd29083c50e6d1143278746a4ef6bcf6153171eb8", + "sha256:d081f13b095d74b67d550de04df1c756831f3b83dc9881c38985834387487f1b", + "sha256:d5655a942f5f5d2c9ed93d72148226d75369b4f6952680211972a33e59b1dfdc", + "sha256:d5a32087d720c608f42caed0ef36d2b3ea61a9d09ee59a5142d6070da9041b8f", + "sha256:d6484756b12f40003c6128bfcc3fa9f0d49a687e171186c2d85ec82e3758c559", + "sha256:dd65632acaf0d47608190a71bfe46b209719bf2beb59507db08ccdbe712f969b", + "sha256:de343e75f40e972bae1ef6090267f8260c1446a1695e77096db6cfa25e759a95", + "sha256:e29cda763f752553fa14c68fb2195150bfab22b352572cb36c43c47bedba70eb", + "sha256:e41f3de4df3e80de75845d3e743b3f1c4c8613c3997a912dbf0229fc61a8b963", + "sha256:e66d2a64d44d50d2543405fb183a21f76b3b5fd16f130f5c99187c3fb4e64919", + "sha256:e74b0506fa5aa5598ac6a975a12aa8928cbb58e1f5ac8360792ef15de1aa848f", + "sha256:f0ed05f5079c708fe74bf9027e95125334b6978bf07fd5ab923e9e55e5fbb9d3", + "sha256:f61e2dc5ad442c52b4887f1fdc112f97caeff4d9e6ebe78879364ac59f1663e1", + "sha256:fec520865f42e5c7f050c2a79038897b1c7d1595e907a9e08e3353293ffc948e" + ], + "markers": "python_version >= '3.8'", + "version": "==1.4.0" + }, + "idna": { + "hashes": [ + "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", + "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f" + ], + "markers": "python_version >= '3.5'", + "version": "==3.6" + }, + "multidict": { + "hashes": [ + "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9", + "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8", + "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03", + "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710", + "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161", + "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664", + "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569", + "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067", + "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313", + "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706", + "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2", + "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636", + "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49", + "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93", + "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603", + "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0", + "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60", + "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4", + "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e", + "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1", + "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60", + "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951", + "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc", + "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe", + "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95", + "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d", + "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8", + "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed", + "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2", + "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775", + "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87", + "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c", + "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2", + "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98", + "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3", + "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe", + "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78", + "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660", + "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176", + "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e", + "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988", + "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c", + "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c", + "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0", + "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449", + "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f", + "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde", + "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5", + "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d", + "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac", + "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a", + "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9", + "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca", + "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11", + "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35", + "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063", + "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b", + "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982", + "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258", + "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1", + "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52", + "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480", + "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7", + "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461", + "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d", + "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc", + "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779", + "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a", + "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547", + "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0", + "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171", + "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf", + "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d", + "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba" + ], + "markers": "python_version >= '3.7'", + "version": "==6.0.4" + }, + "requests": { + "hashes": [ + "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", + "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==2.31.0" + }, + "slackclient": { + "hashes": [ + "sha256:07ec8fa76f6aa64852210ae235ff9e637ba78124e06c0b07a7eeea4abb955965", + "sha256:2d68d668c02f4038299897e5c4723ab85dd40a3548354924b24f333a435856f8" + ], + "index": "pypi", + "markers": "python_full_version >= '3.6.0'", + "version": "==2.9.3" + }, + "uritools": { + "hashes": [ + "sha256:04df2b787d0eb76200e8319382a03562fbfe4741fd66c15506b08d3b8211d573", + "sha256:607b15eae1e7b69a120f463a7d98f91a56671e1ab92aae13f8e1f25c017fe60e" + ], + "markers": "python_version >= '3.7'", + "version": "==4.0.2" + }, + "urlextract": { + "hashes": [ + "sha256:483f4dadbc749be7fd3a3305ec6c89d5682de1be739e0ef299148a1e4c62ea94", + "sha256:cb13ae8acc053899c0bf1c134fef99864f276562b67f878fb10a54608ec4612e" + ], + "index": "pypi", + "version": "==1.3.0" + }, + "urllib3": { + "hashes": [ + "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07", + "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0" + ], + "index": "pypi", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==1.26.18" + }, + "yarl": { + "hashes": [ + "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51", + "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce", + "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559", + "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0", + "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81", + "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc", + "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4", + "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c", + "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130", + "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136", + "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e", + "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec", + "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7", + "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1", + "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455", + "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099", + "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129", + "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10", + "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142", + "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98", + "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa", + "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7", + "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525", + "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c", + "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9", + "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c", + "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8", + "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b", + "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf", + "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23", + "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd", + "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27", + "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f", + "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece", + "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434", + "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec", + "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff", + "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78", + "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d", + "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863", + "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53", + "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31", + "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15", + "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5", + "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b", + "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57", + "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3", + "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1", + "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f", + "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad", + "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c", + "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7", + "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2", + "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b", + "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2", + "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b", + "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9", + "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be", + "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e", + "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984", + "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4", + "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074", + "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2", + "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392", + "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91", + "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541", + "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf", + "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572", + "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66", + "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575", + "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14", + "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5", + "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1", + "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e", + "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551", + "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17", + "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead", + "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0", + "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe", + "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234", + "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0", + "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7", + "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34", + "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42", + "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385", + "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78", + "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be", + "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958", + "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749", + "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec" + ], + "markers": "python_version >= '3.7'", + "version": "==1.9.4" + } + }, + "develop": {} +} diff --git a/scripts/e2e_message/e2e_notifier.py b/scripts/e2e_message/e2e_notifier.py new file mode 100644 index 00000000000..3a6d8ee776a --- /dev/null +++ b/scripts/e2e_message/e2e_notifier.py @@ -0,0 +1,56 @@ +import os +import ssl +from os.path import join + +import certifi +import urllib3 +from slack import WebClient +from slack.errors import SlackApiError + +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + +SLACK_TOKEN = os.environ.get("IO_APP_SLACK_HELPER_BOT_TOKEN", None) +tagged_people = [""] +SLACK_CHANNEL = "#io_dev_app_feed" +BUILD_ID = os.environ.get("BUILD_ID", None) +BASE_ACTION_URI = "https://github.com/pagopa/io-app/actions/runs/" + +def send_slack_message(): + """ + Sends the report of the check to slack to notify the status of the E2E tests of the app + :return: + """ + try: + # avoid ssl certificate warning + ssl_context = ssl.create_default_context(cafile=certifi.where()) + rtm_client = WebClient( + token=SLACK_TOKEN, ssl=ssl_context + ) + tags = " ".join(tagged_people) + message = "[E2E Tests] :warning: %s e2e tests have failed (<%s%s|here>)" % ( + tags, BASE_ACTION_URI, BUILD_ID) + message_blocks = [] + message_blocks.append({ + "type": "section", + "text": { + "type": "mrkdwn", + "text": message + } + }) + rtm_client.chat_postMessage( + channel=SLACK_CHANNEL, + blocks=message_blocks + ) + + + except SlackApiError as e: + # You will get a SlackApiError if "ok" is False + assert e.response["ok"] is False + # str like 'invalid_auth', 'channel_not_found' + assert e.response["error"] + print(f"Got an error: {e.response['error']}") + +if SLACK_TOKEN: + send_slack_message() +else: + print("no SLACK token provided") \ No newline at end of file From 644157f50889d8594ee18b02c8b4776e51639ed5 Mon Sep 17 00:00:00 2001 From: Federico Mastrini Date: Mon, 29 Jan 2024 20:45:56 +0100 Subject: [PATCH 5/7] chore: [IOBP-523] Add missing `language` parameter in `calculateFees` request (#5453) --- .../handleWalletPaymentCalculateFees.test.ts | 7 +++++++ .../networking/handleWalletPaymentCalculateFees.ts | 12 +++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/ts/features/walletV3/payment/saga/networking/__tests__/handleWalletPaymentCalculateFees.test.ts b/ts/features/walletV3/payment/saga/networking/__tests__/handleWalletPaymentCalculateFees.test.ts index 57d99b9c539..9488931478e 100644 --- a/ts/features/walletV3/payment/saga/networking/__tests__/handleWalletPaymentCalculateFees.test.ts +++ b/ts/features/walletV3/payment/saga/networking/__tests__/handleWalletPaymentCalculateFees.test.ts @@ -10,6 +10,7 @@ import { walletPaymentCalculateFees } from "../../../store/actions/networking"; import { handleWalletPaymentCalculateFees } from "../handleWalletPaymentCalculateFees"; import { CalculateFeeRequest } from "../../../../../../../definitions/pagopa/ecommerce/CalculateFeeRequest"; import { selectWalletPaymentSessionToken } from "../../../store/selectors"; +import { preferredLanguageSelector } from "../../../../../../store/reducers/persistedPreferences"; describe("Test handleWalletPaymentCalculateFees saga", () => { const calculateFeesPayload: CalculateFeeRequest & { @@ -41,6 +42,8 @@ describe("Test handleWalletPaymentCalculateFees saga", () => { walletPaymentCalculateFees.request(calculateFeesPayload) ) .next() + .select(preferredLanguageSelector) + .next("IT") .select(selectWalletPaymentSessionToken) .next(T_SESSION_TOKEN) .call( @@ -66,6 +69,8 @@ describe("Test handleWalletPaymentCalculateFees saga", () => { walletPaymentCalculateFees.request(calculateFeesPayload) ) .next() + .select(preferredLanguageSelector) + .next("IT") .select(selectWalletPaymentSessionToken) .next(T_SESSION_TOKEN) .call( @@ -94,6 +99,8 @@ describe("Test handleWalletPaymentCalculateFees saga", () => { walletPaymentCalculateFees.request(calculateFeesPayload) ) .next() + .select(preferredLanguageSelector) + .next("IT") .select(selectWalletPaymentSessionToken) .next(T_SESSION_TOKEN) .call( diff --git a/ts/features/walletV3/payment/saga/networking/handleWalletPaymentCalculateFees.ts b/ts/features/walletV3/payment/saga/networking/handleWalletPaymentCalculateFees.ts index 46cc2fc6753..dc6450a9d6d 100644 --- a/ts/features/walletV3/payment/saga/networking/handleWalletPaymentCalculateFees.ts +++ b/ts/features/walletV3/payment/saga/networking/handleWalletPaymentCalculateFees.ts @@ -1,8 +1,11 @@ import * as E from "fp-ts/lib/Either"; import * as O from "fp-ts/lib/Option"; +import { pipe } from "fp-ts/lib/function"; +import { toUpper } from "lodash"; import { call, put, select } from "typed-redux-saga/macro"; import { ActionType } from "typesafe-actions"; import { CalculateFeeResponse } from "../../../../../../definitions/pagopa/ecommerce/CalculateFeeResponse"; +import { preferredLanguageSelector } from "../../../../../store/reducers/persistedPreferences"; import { SagaCallReturnType } from "../../../../../types/utils"; import { getGenericError, getNetworkError } from "../../../../../utils/errors"; import { readablePrivacyReport } from "../../../../../utils/reporters"; @@ -19,6 +22,13 @@ export function* handleWalletPaymentCalculateFees( action: ActionType<(typeof walletPaymentCalculateFees)["request"]> ) { try { + const preferredLanguageOption = yield* select(preferredLanguageSelector); + const language = pipe( + preferredLanguageOption, + O.map(toUpper), + O.getOrElse(() => "IT") + ); + const sessionToken = yield* getOrFetchWalletSessionToken(); if (sessionToken === undefined) { @@ -30,7 +40,7 @@ export function* handleWalletPaymentCalculateFees( return; } - const { paymentMethodId, ...body } = action.payload; + const { paymentMethodId, ...body } = { ...action.payload, language }; const calculateFeesRequest = calculateFees({ eCommerceSessionToken: sessionToken, id: paymentMethodId, From 52a3d1e761bb915c75f610f230bc288f5eacb073 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Jan 2024 10:16:35 +0100 Subject: [PATCH 6/7] chore(deps): bump aiohttp from 3.9.0 to 3.9.2 in /scripts/e2e_message (#5457) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.9.0 to 3.9.2.
Release notes

Sourced from aiohttp's releases.

3.9.2

Bug fixes

  • Fixed server-side websocket connection leak.

    Related issues and pull requests on GitHub: #7978.

  • Fixed web.FileResponse doing blocking I/O in the event loop.

    Related issues and pull requests on GitHub: #8012.

  • Fixed double compress when compression enabled and compressed file exists in server file responses.

    Related issues and pull requests on GitHub: #8014.

  • Added runtime type check for ClientSession timeout parameter.

    Related issues and pull requests on GitHub: #8021.

  • Fixed an unhandled exception in the Python HTTP parser on header lines starting with a colon -- by :user:pajod.

    Invalid request lines with anything but a dot between the HTTP major and minor version are now rejected. Invalid header field names containing question mark or slash are now rejected. Such requests are incompatible with :rfc:9110#section-5.6.2 and are not known to be of any legitimate use.

    Related issues and pull requests on GitHub: #8074.

  • Improved validation of paths for static resources requests to the server -- by :user:bdraco.

... (truncated)

Changelog

Sourced from aiohttp's changelog.

3.9.2 (2024-01-28)

Bug fixes

  • Fixed server-side websocket connection leak.

    Related issues and pull requests on GitHub: :issue:7978.

  • Fixed web.FileResponse doing blocking I/O in the event loop.

    Related issues and pull requests on GitHub: :issue:8012.

  • Fixed double compress when compression enabled and compressed file exists in server file responses.

    Related issues and pull requests on GitHub: :issue:8014.

  • Added runtime type check for ClientSession timeout parameter.

    Related issues and pull requests on GitHub: :issue:8021.

  • Fixed an unhandled exception in the Python HTTP parser on header lines starting with a colon -- by :user:pajod.

    Invalid request lines with anything but a dot between the HTTP major and minor version are now rejected. Invalid header field names containing question mark or slash are now rejected. Such requests are incompatible with :rfc:9110#section-5.6.2 and are not known to be of any legitimate use.

    Related issues and pull requests on GitHub: :issue:8074.

... (truncated)

Commits
  • 24a6d64 Release v3.9.2 (#8082)
  • 9118a58 [PR #8079/1c335944 backport][3.9] Validate static paths (#8080)
  • 435ad46 [PR #3955/8960063e backport][3.9] Replace all tmpdir fixtures with tmp_path (...
  • d33bc21 Improve validation in HTTP parser (#8074) (#8078)
  • 0d945d1 [PR #7916/822fbc74 backport][3.9] Add more information to contributing page (...
  • 3ec4fa1 [PR #8069/69bbe874 backport][3.9] 📝 Only show changelog draft for non-release...
  • 419d715 [PR #8066/cba34699 backport][3.9] 💅📝 Restructure the changelog for clarity (#...
  • a54dab3 [PR #8049/a379e634 backport][3.9] Set cause for ClientPayloadError (#8050)
  • 437ac47 [PR #7995/43a5bc50 backport][3.9] Fix examples of fallback_charset_resolver...
  • 034e5e3 [PR #8042/4b91b530 backport][3.9] Tightening the runtime type check for ssl (...
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=aiohttp&package-manager=pip&previous-version=3.9.0&new-version=3.9.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/pagopa/io-app/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- scripts/e2e_message/Pipfile.lock | 300 ++++++++++++++++--------------- 1 file changed, 158 insertions(+), 142 deletions(-) diff --git a/scripts/e2e_message/Pipfile.lock b/scripts/e2e_message/Pipfile.lock index 607da490b79..d93310bd49d 100644 --- a/scripts/e2e_message/Pipfile.lock +++ b/scripts/e2e_message/Pipfile.lock @@ -18,86 +18,86 @@ "default": { "aiohttp": { "hashes": [ - "sha256:05857848da443c8c12110d99285d499b4e84d59918a21132e45c3f0804876994", - "sha256:05a183f1978802588711aed0dea31e697d760ce9055292db9dc1604daa9a8ded", - "sha256:09f23292d29135025e19e8ff4f0a68df078fe4ee013bca0105b2e803989de92d", - "sha256:11ca808f9a6b63485059f5f6e164ef7ec826483c1212a44f268b3653c91237d8", - "sha256:1736d87dad8ef46a8ec9cddd349fa9f7bd3a064c47dd6469c0d6763d3d49a4fc", - "sha256:1df43596b826022b14998f0460926ce261544fedefe0d2f653e1b20f49e96454", - "sha256:23170247ef89ffa842a02bbfdc425028574d9e010611659abeb24d890bc53bb8", - "sha256:2779f5e7c70f7b421915fd47db332c81de365678180a9f3ab404088f87ba5ff9", - "sha256:28185e36a78d247c55e9fbea2332d16aefa14c5276a582ce7a896231c6b1c208", - "sha256:2cbc14a13fb6b42d344e4f27746a4b03a2cb0c1c3c5b932b0d6ad8881aa390e3", - "sha256:2d71abc15ff7047412ef26bf812dfc8d0d1020d664617f4913df2df469f26b76", - "sha256:2d820162c8c2bdbe97d328cd4f417c955ca370027dce593345e437b2e9ffdc4d", - "sha256:317719d7f824eba55857fe0729363af58e27c066c731bc62cd97bc9c3d9c7ea4", - "sha256:35a68cd63ca6aaef5707888f17a70c36efe62b099a4e853d33dc2e9872125be8", - "sha256:3607375053df58ed6f23903aa10cf3112b1240e8c799d243bbad0f7be0666986", - "sha256:366bc870d7ac61726f32a489fbe3d1d8876e87506870be66b01aeb84389e967e", - "sha256:3abf0551874fecf95f93b58f25ef4fc9a250669a2257753f38f8f592db85ddea", - "sha256:3d7f6235c7475658acfc1769d968e07ab585c79f6ca438ddfecaa9a08006aee2", - "sha256:3dd8119752dd30dd7bca7d4bc2a92a59be6a003e4e5c2cf7e248b89751b8f4b7", - "sha256:42fe4fd9f0dfcc7be4248c162d8056f1d51a04c60e53366b0098d1267c4c9da8", - "sha256:45820ddbb276113ead8d4907a7802adb77548087ff5465d5c554f9aa3928ae7d", - "sha256:4790e44f46a4aa07b64504089def5744d3b6780468c4ec3a1a36eb7f2cae9814", - "sha256:4afa8f71dba3a5a2e1e1282a51cba7341ae76585345c43d8f0e624882b622218", - "sha256:4b777c9286b6c6a94f50ddb3a6e730deec327e9e2256cb08b5530db0f7d40fd8", - "sha256:4ee1b4152bc3190cc40ddd6a14715e3004944263ea208229ab4c297712aa3075", - "sha256:51a4cd44788ea0b5e6bb8fa704597af3a30be75503a7ed1098bc5b8ffdf6c982", - "sha256:536b01513d67d10baf6f71c72decdf492fb7433c5f2f133e9a9087379d4b6f31", - "sha256:571760ad7736b34d05597a1fd38cbc7d47f7b65deb722cb8e86fd827404d1f6b", - "sha256:5a2eb5311a37fe105aa35f62f75a078537e1a9e4e1d78c86ec9893a3c97d7a30", - "sha256:5ab16c254e2312efeb799bc3c06897f65a133b38b69682bf75d1f1ee1a9c43a9", - "sha256:65b0a70a25456d329a5e1426702dde67be0fb7a4ead718005ba2ca582d023a94", - "sha256:673343fbc0c1ac44d0d2640addc56e97a052504beacd7ade0dc5e76d3a4c16e8", - "sha256:6777a390e41e78e7c45dab43a4a0196c55c3b8c30eebe017b152939372a83253", - "sha256:6896b8416be9ada4d22cd359d7cb98955576ce863eadad5596b7cdfbf3e17c6c", - "sha256:694df243f394629bcae2d8ed94c589a181e8ba8604159e6e45e7b22e58291113", - "sha256:70e851f596c00f40a2f00a46126c95c2e04e146015af05a9da3e4867cfc55911", - "sha256:7276fe0017664414fdc3618fca411630405f1aaf0cc3be69def650eb50441787", - "sha256:76a86a9989ebf82ee61e06e2bab408aec4ea367dc6da35145c3352b60a112d11", - "sha256:7a94bde005a8f926d0fa38b88092a03dea4b4875a61fbcd9ac6f4351df1b57cd", - "sha256:7ae5f99a32c53731c93ac3075abd3e1e5cfbe72fc3eaac4c27c9dd64ba3b19fe", - "sha256:7e8a3b79b6d186a9c99761fd4a5e8dd575a48d96021f220ac5b5fa856e5dd029", - "sha256:816f4db40555026e4cdda604a1088577c1fb957d02f3f1292e0221353403f192", - "sha256:8303531e2c17b1a494ffaeba48f2da655fe932c4e9a2626c8718403c83e5dd2b", - "sha256:8488519aa05e636c5997719fe543c8daf19f538f4fa044f3ce94bee608817cff", - "sha256:87c8b0a6487e8109427ccf638580865b54e2e3db4a6e0e11c02639231b41fc0f", - "sha256:8c9e5f4d7208cda1a2bb600e29069eecf857e6980d0ccc922ccf9d1372c16f4b", - "sha256:94697c7293199c2a2551e3e3e18438b4cba293e79c6bc2319f5fd652fccb7456", - "sha256:9623cfd9e85b76b83ef88519d98326d4731f8d71869867e47a0b979ffec61c73", - "sha256:98d21092bf2637c5fa724a428a69e8f5955f2182bff61f8036827cf6ce1157bf", - "sha256:99ae01fb13a618b9942376df77a1f50c20a281390dad3c56a6ec2942e266220d", - "sha256:9c196b30f1b1aa3363a69dd69079ae9bec96c2965c4707eaa6914ba099fb7d4f", - "sha256:a00ce44c21612d185c5275c5cba4bab8d7c1590f248638b667ed8a782fa8cd6f", - "sha256:a1b66dbb8a7d5f50e9e2ea3804b01e766308331d0cac76eb30c563ac89c95985", - "sha256:a1d7edf74a36de0e5ca50787e83a77cf352f5504eb0ffa3f07000a911ba353fb", - "sha256:a1e3b3c107ccb0e537f309f719994a55621acd2c8fdf6d5ce5152aed788fb940", - "sha256:a486ddf57ab98b6d19ad36458b9f09e6022de0381674fe00228ca7b741aacb2f", - "sha256:ac9669990e2016d644ba8ae4758688534aabde8dbbc81f9af129c3f5f01ca9cd", - "sha256:b1a2ea8252cacc7fd51df5a56d7a2bb1986ed39be9397b51a08015727dfb69bd", - "sha256:c5b7bf8fe4d39886adc34311a233a2e01bc10eb4e842220235ed1de57541a896", - "sha256:c67a51ea415192c2e53e4e048c78bab82d21955b4281d297f517707dc836bf3d", - "sha256:ca4fddf84ac7d8a7d0866664936f93318ff01ee33e32381a115b19fb5a4d1202", - "sha256:d5b9345ab92ebe6003ae11d8092ce822a0242146e6fa270889b9ba965457ca40", - "sha256:d97c3e286d0ac9af6223bc132dc4bad6540b37c8d6c0a15fe1e70fb34f9ec411", - "sha256:db04d1de548f7a62d1dd7e7cdf7c22893ee168e22701895067a28a8ed51b3735", - "sha256:dcf71c55ec853826cd70eadb2b6ac62ec577416442ca1e0a97ad875a1b3a0305", - "sha256:de3cc86f4ea8b4c34a6e43a7306c40c1275e52bfa9748d869c6b7d54aa6dad80", - "sha256:deac0a32aec29608eb25d730f4bc5a261a65b6c48ded1ed861d2a1852577c932", - "sha256:e18d92c3e9e22553a73e33784fcb0ed484c9874e9a3e96c16a8d6a1e74a0217b", - "sha256:eb6dfd52063186ac97b4caa25764cdbcdb4b10d97f5c5f66b0fa95052e744eb7", - "sha256:f09960b5bb1017d16c0f9e9f7fc42160a5a49fa1e87a175fd4a2b1a1833ea0af", - "sha256:f1e4f254e9c35d8965d377e065c4a8a55d396fe87c8e7e8429bcfdeeb229bfb3", - "sha256:f32c86dc967ab8c719fd229ce71917caad13cc1e8356ee997bf02c5b368799bf", - "sha256:f50b4663c3e0262c3a361faf440761fbef60ccdde5fe8545689a4b3a3c149fb4", - "sha256:f8e05f5163528962ce1d1806fce763ab893b1c5b7ace0a3538cd81a90622f844", - "sha256:f929f4c9b9a00f3e6cc0587abb95ab9c05681f8b14e0fe1daecfa83ea90f8318", - "sha256:f9e09a1c83521d770d170b3801eea19b89f41ccaa61d53026ed111cb6f088887" + "sha256:00a9abcea793c81e7f8778ca195a1714a64f6d7436c4c0bb168ad2a212627000", + "sha256:04fd8ffd2be73d42bcf55fd78cde7958eeee6d4d8f73c3846b7cba491ecdb570", + "sha256:07205ae0015e05c78b3288c1517afa000823a678a41594b3fdc870878d645305", + "sha256:07be2be7071723c3509ab5c08108d3a74f2181d4964e869f2504aaab68f8d3e8", + "sha256:0b500c5ad9c07639d48615a770f49618130e61be36608fc9bc2d9bae31732b8f", + "sha256:0e4ee4df741670560b1bc393672035418bf9063718fee05e1796bf867e995fad", + "sha256:103daf41ff3b53ba6fa09ad410793e2e76c9d0269151812e5aba4b9dd674a7e8", + "sha256:114da29f39eccd71b93a0fcacff178749a5c3559009b4a4498c2c173a6d74dff", + "sha256:122468f6fee5fcbe67cb07014a08c195b3d4c41ff71e7b5160a7bcc41d585a5f", + "sha256:1543e7fb00214fb4ccead42e6a7d86f3bb7c34751ec7c605cca7388e525fd0b4", + "sha256:158564d0d1020e0d3fe919a81d97aadad35171e13e7b425b244ad4337fc6793a", + "sha256:16a967685907003765855999af11a79b24e70b34dc710f77a38d21cd9fc4f5fe", + "sha256:186e94570433a004e05f31f632726ae0f2c9dee4762a9ce915769ce9c0a23d89", + "sha256:193cc1ccd69d819562cc7f345c815a6fc51d223b2ef22f23c1a0f67a88de9a72", + "sha256:1956e3ac376b1711c1533266dec4efd485f821d84c13ce1217d53e42c9e65f08", + "sha256:1c45e4e815ac6af3b72ca2bde9b608d2571737bb1e2d42299fc1ffdf60f6f9a1", + "sha256:200dc0246f0cb5405c80d18ac905c8350179c063ea1587580e3335bfc243ba6a", + "sha256:2dec87a556f300d3211decf018bfd263424f0690fcca00de94a837949fbcea02", + "sha256:328918a6c2835861ff7afa8c6d2c70c35fdaf996205d5932351bdd952f33fa2f", + "sha256:3cc158466f6a980a6095ee55174d1de5730ad7dec251be655d9a6a9dd7ea1ff9", + "sha256:3d8d962b439a859b3ded9a1e111a4615357b01620a546bc601f25b0211f2da81", + "sha256:3e1a800f988ce7c4917f34096f81585a73dbf65b5c39618b37926b1238cf9bc4", + "sha256:3f17999ae3927d8a9a823a1283b201344a0627272f92d4f3e3a4efe276972fe8", + "sha256:4112d8ba61fbd0abd5d43a9cb312214565b446d926e282a6d7da3f5a5aa71d36", + "sha256:4bd9d5b989d57b41e4ff56ab250c5ddf259f32db17159cce630fd543376bd96b", + "sha256:4c189b64bd6d9a403a1a3f86a3ab3acbc3dc41a68f73a268a4f683f89a4dec1f", + "sha256:4deae2c165a5db1ed97df2868ef31ca3cc999988812e82386d22937d9d6fed52", + "sha256:5264d7327c9464786f74e4ec9342afbbb6ee70dfbb2ec9e3dfce7a54c8043aa3", + "sha256:5422cd9a4a00f24c7244e1b15aa9b87935c85fb6a00c8ac9b2527b38627a9211", + "sha256:54287bcb74d21715ac8382e9de146d9442b5f133d9babb7e5d9e453faadd005e", + "sha256:54ec82f45d57c9a65a1ead3953b51c704f9587440e6682f689da97f3e8defa35", + "sha256:5bb3d05569aa83011fcb346b5266e00b04180105fcacc63743fc2e4a1862a891", + "sha256:61c47ab8ef629793c086378b1df93d18438612d3ed60dca76c3422f4fbafa792", + "sha256:68bbee9e17d66f17bb0010aa15a22c6eb28583edcc8b3212e2b8e3f77f3ebe2a", + "sha256:6aaa6f99256dd1b5756a50891a20f0d252bd7bdb0854c5d440edab4495c9f973", + "sha256:6f4cdba12539215aaecf3c310ce9d067b0081a0795dd8a8805fdb67a65c0572a", + "sha256:6fa3ee92cd441d5c2d07ca88d7a9cef50f7ec975f0117cd0c62018022a184308", + "sha256:772fbe371788e61c58d6d3d904268e48a594ba866804d08c995ad71b144f94cb", + "sha256:7a75307ffe31329928a8d47eae0692192327c599113d41b278d4c12b54e1bd11", + "sha256:7a9825fdd64ecac5c670234d80bb52bdcaa4139d1f839165f548208b3779c6c6", + "sha256:7d579dcd5d82a86a46f725458418458fa43686f6a7b252f2966d359033ffc8ab", + "sha256:8008d0f451d66140a5aa1c17e3eedc9d56e14207568cd42072c9d6b92bf19b52", + "sha256:84e843b33d5460a5c501c05539809ff3aee07436296ff9fbc4d327e32aa3a326", + "sha256:8a7876f794523123bca6d44bfecd89c9fec9ec897a25f3dd202ee7fc5c6525b7", + "sha256:8ceb658afd12b27552597cf9a65d9807d58aef45adbb58616cdd5ad4c258c39e", + "sha256:972b63d589ff8f305463593050a31b5ce91638918da38139b9d8deaba9e0fed7", + "sha256:9a87aa0b13bbee025faa59fa58861303c2b064b9855d4c0e45ec70182bbeba1b", + "sha256:a1c3a4d0ab2f75f22ec80bca62385db2e8810ee12efa8c9e92efea45c1849133", + "sha256:a27d8c70ad87bcfce2e97488652075a9bdd5b70093f50b10ae051dfe5e6baf37", + "sha256:aa906b9bdfd4a7972dd0628dbbd6413d2062df5b431194486a78f0d2ae87bd55", + "sha256:abeb813a18eb387f0d835ef51f88568540ad0325807a77a6e501fed4610f864e", + "sha256:ae0a1e638cffc3ec4d4784b8b4fd1cf28968febc4bd2718ffa25b99b96a741bd", + "sha256:b0ad0a5e86ce73f5368a164c10ada10504bf91869c05ab75d982c6048217fbf7", + "sha256:b141753be581fab842a25cb319f79536d19c2a51995d7d8b29ee290169868eab", + "sha256:b65e861f4bebfb660f7f0f40fa3eb9f2ab9af10647d05dac824390e7af8f75b7", + "sha256:b9f1cb839b621f84a5b006848e336cf1496688059d2408e617af33e3470ba204", + "sha256:bc71f748e12284312f140eaa6599a520389273174b42c345d13c7e07792f4f57", + "sha256:bda42eb410be91b349fb4ee3a23a30ee301c391e503996a638d05659d76ea4c2", + "sha256:c07327b368745b1ce2393ae9e1aafed7073d9199e1dcba14e035cc646c7941bf", + "sha256:c4ad4241b52bb2eb7a4d2bde060d31c2b255b8c6597dd8deac2f039168d14fd7", + "sha256:c8534e7d69bb8e8d134fe2be9890d1b863518582f30c9874ed7ed12e48abe3c4", + "sha256:cc7d6502c23a0ec109687bf31909b3fb7b196faf198f8cff68c81b49eb316ea9", + "sha256:cc91d07280d7d169f3a0f9179d8babd0ee05c79d4d891447629ff0d7d8089ec2", + "sha256:cfee9287778399fdef6f8a11c9e425e1cb13cc9920fd3a3df8f122500978292b", + "sha256:d22a0931848b8c7a023c695fa2057c6aaac19085f257d48baa24455e67df97ec", + "sha256:d23fba734e3dd7b1d679b9473129cd52e4ec0e65a4512b488981a56420e708db", + "sha256:d43302a30ba1166325974858e6ef31727a23bdd12db40e725bec0f759abce505", + "sha256:d52d20832ac1560f4510d68e7ba8befbc801a2b77df12bd0cd2bcf3b049e52a4", + "sha256:da1346cd0ccb395f0ed16b113ebb626fa43b7b07fd7344fce33e7a4f04a8897a", + "sha256:e2cc0d04688b9f4a7854c56c18aa7af9e5b0a87a28f934e2e596ba7e14783192", + "sha256:ea510718a41b95c236c992b89fdfc3d04cc7ca60281f93aaada497c2b4e05c46", + "sha256:eaa9256de26ea0334ffa25f1913ae15a51e35c529a1ed9af8e6286dd44312554", + "sha256:edd4f1af2253f227ae311ab3d403d0c506c9b4410c7fc8d9573dec6d9740369f", + "sha256:ee2661a3f5b529f4fc8a8ffee9f736ae054adfb353a0d2f78218be90617194b3", + "sha256:f31df6a32217a34ae2f813b152a6f348154f948c83213b690e59d9e84020925c", + "sha256:fa6904088e6642609981f919ba775838ebf7df7fe64998b1a954fb411ffb4663" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==3.9.0" + "version": "==3.9.2" }, "aiosignal": { "hashes": [ @@ -124,11 +124,11 @@ }, "attrs": { "hashes": [ - "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04", - "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015" + "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30", + "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1" ], "markers": "python_version >= '3.7'", - "version": "==23.1.0" + "version": "==23.2.0" }, "certifi": { "hashes": [ @@ -244,70 +244,86 @@ }, "frozenlist": { "hashes": [ - "sha256:007df07a6e3eb3e33e9a1fe6a9db7af152bbd8a185f9aaa6ece10a3529e3e1c6", - "sha256:008eb8b31b3ea6896da16c38c1b136cb9fec9e249e77f6211d479db79a4eaf01", - "sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251", - "sha256:0c7c1b47859ee2cac3846fde1c1dc0f15da6cec5a0e5c72d101e0f83dcb67ff9", - "sha256:0e5c8764c7829343d919cc2dfc587a8db01c4f70a4ebbc49abde5d4b158b007b", - "sha256:10ff5faaa22786315ef57097a279b833ecab1a0bfb07d604c9cbb1c4cdc2ed87", - "sha256:17ae5cd0f333f94f2e03aaf140bb762c64783935cc764ff9c82dff626089bebf", - "sha256:19488c57c12d4e8095a922f328df3f179c820c212940a498623ed39160bc3c2f", - "sha256:1a0848b52815006ea6596c395f87449f693dc419061cc21e970f139d466dc0a0", - "sha256:1e78fb68cf9c1a6aa4a9a12e960a5c9dfbdb89b3695197aa7064705662515de2", - "sha256:261b9f5d17cac914531331ff1b1d452125bf5daa05faf73b71d935485b0c510b", - "sha256:2b8bcf994563466db019fab287ff390fffbfdb4f905fc77bc1c1d604b1c689cc", - "sha256:38461d02d66de17455072c9ba981d35f1d2a73024bee7790ac2f9e361ef1cd0c", - "sha256:490132667476f6781b4c9458298b0c1cddf237488abd228b0b3650e5ecba7467", - "sha256:491e014f5c43656da08958808588cc6c016847b4360e327a62cb308c791bd2d9", - "sha256:515e1abc578dd3b275d6a5114030b1330ba044ffba03f94091842852f806f1c1", - "sha256:556de4430ce324c836789fa4560ca62d1591d2538b8ceb0b4f68fb7b2384a27a", - "sha256:5833593c25ac59ede40ed4de6d67eb42928cca97f26feea219f21d0ed0959b79", - "sha256:6221d84d463fb110bdd7619b69cb43878a11d51cbb9394ae3105d082d5199167", - "sha256:6918d49b1f90821e93069682c06ffde41829c346c66b721e65a5c62b4bab0300", - "sha256:6c38721585f285203e4b4132a352eb3daa19121a035f3182e08e437cface44bf", - "sha256:71932b597f9895f011f47f17d6428252fc728ba2ae6024e13c3398a087c2cdea", - "sha256:7211ef110a9194b6042449431e08c4d80c0481e5891e58d429df5899690511c2", - "sha256:764226ceef3125e53ea2cb275000e309c0aa5464d43bd72abd661e27fffc26ab", - "sha256:7645a8e814a3ee34a89c4a372011dcd817964ce8cb273c8ed6119d706e9613e3", - "sha256:76d4711f6f6d08551a7e9ef28c722f4a50dd0fc204c56b4bcd95c6cc05ce6fbb", - "sha256:7f4f399d28478d1f604c2ff9119907af9726aed73680e5ed1ca634d377abb087", - "sha256:88f7bc0fcca81f985f78dd0fa68d2c75abf8272b1f5c323ea4a01a4d7a614efc", - "sha256:8d0edd6b1c7fb94922bf569c9b092ee187a83f03fb1a63076e7774b60f9481a8", - "sha256:901289d524fdd571be1c7be054f48b1f88ce8dddcbdf1ec698b27d4b8b9e5d62", - "sha256:93ea75c050c5bb3d98016b4ba2497851eadf0ac154d88a67d7a6816206f6fa7f", - "sha256:981b9ab5a0a3178ff413bca62526bb784249421c24ad7381e39d67981be2c326", - "sha256:9ac08e601308e41eb533f232dbf6b7e4cea762f9f84f6357136eed926c15d12c", - "sha256:a02eb8ab2b8f200179b5f62b59757685ae9987996ae549ccf30f983f40602431", - "sha256:a0c6da9aee33ff0b1a451e867da0c1f47408112b3391dd43133838339e410963", - "sha256:a6c8097e01886188e5be3e6b14e94ab365f384736aa1fca6a0b9e35bd4a30bc7", - "sha256:aa384489fefeb62321b238e64c07ef48398fe80f9e1e6afeff22e140e0850eef", - "sha256:ad2a9eb6d9839ae241701d0918f54c51365a51407fd80f6b8289e2dfca977cc3", - "sha256:b206646d176a007466358aa21d85cd8600a415c67c9bd15403336c331a10d956", - "sha256:b826d97e4276750beca7c8f0f1a4938892697a6bcd8ec8217b3312dad6982781", - "sha256:b89ac9768b82205936771f8d2eb3ce88503b1556324c9f903e7156669f521472", - "sha256:bd7bd3b3830247580de99c99ea2a01416dfc3c34471ca1298bccabf86d0ff4dc", - "sha256:bdf1847068c362f16b353163391210269e4f0569a3c166bc6a9f74ccbfc7e839", - "sha256:c11b0746f5d946fecf750428a95f3e9ebe792c1ee3b1e96eeba145dc631a9672", - "sha256:c5374b80521d3d3f2ec5572e05adc94601985cc526fb276d0c8574a6d749f1b3", - "sha256:ca265542ca427bf97aed183c1676e2a9c66942e822b14dc6e5f42e038f92a503", - "sha256:ce31ae3e19f3c902de379cf1323d90c649425b86de7bbdf82871b8a2a0615f3d", - "sha256:ceb6ec0a10c65540421e20ebd29083c50e6d1143278746a4ef6bcf6153171eb8", - "sha256:d081f13b095d74b67d550de04df1c756831f3b83dc9881c38985834387487f1b", - "sha256:d5655a942f5f5d2c9ed93d72148226d75369b4f6952680211972a33e59b1dfdc", - "sha256:d5a32087d720c608f42caed0ef36d2b3ea61a9d09ee59a5142d6070da9041b8f", - "sha256:d6484756b12f40003c6128bfcc3fa9f0d49a687e171186c2d85ec82e3758c559", - "sha256:dd65632acaf0d47608190a71bfe46b209719bf2beb59507db08ccdbe712f969b", - "sha256:de343e75f40e972bae1ef6090267f8260c1446a1695e77096db6cfa25e759a95", - "sha256:e29cda763f752553fa14c68fb2195150bfab22b352572cb36c43c47bedba70eb", - "sha256:e41f3de4df3e80de75845d3e743b3f1c4c8613c3997a912dbf0229fc61a8b963", - "sha256:e66d2a64d44d50d2543405fb183a21f76b3b5fd16f130f5c99187c3fb4e64919", - "sha256:e74b0506fa5aa5598ac6a975a12aa8928cbb58e1f5ac8360792ef15de1aa848f", - "sha256:f0ed05f5079c708fe74bf9027e95125334b6978bf07fd5ab923e9e55e5fbb9d3", - "sha256:f61e2dc5ad442c52b4887f1fdc112f97caeff4d9e6ebe78879364ac59f1663e1", - "sha256:fec520865f42e5c7f050c2a79038897b1c7d1595e907a9e08e3353293ffc948e" + "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7", + "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98", + "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad", + "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5", + "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae", + "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e", + "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a", + "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701", + "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d", + "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6", + "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6", + "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106", + "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75", + "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868", + "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a", + "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0", + "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1", + "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826", + "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec", + "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6", + "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950", + "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19", + "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0", + "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8", + "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a", + "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09", + "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86", + "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c", + "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5", + "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b", + "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b", + "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d", + "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0", + "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea", + "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776", + "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a", + "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897", + "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7", + "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09", + "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9", + "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe", + "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd", + "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742", + "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09", + "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0", + "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932", + "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1", + "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a", + "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49", + "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d", + "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7", + "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480", + "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89", + "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e", + "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b", + "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82", + "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb", + "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068", + "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8", + "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b", + "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb", + "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2", + "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11", + "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b", + "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc", + "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0", + "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497", + "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17", + "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0", + "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2", + "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439", + "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5", + "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac", + "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825", + "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887", + "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced", + "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74" ], "markers": "python_version >= '3.8'", - "version": "==1.4.0" + "version": "==1.4.1" }, "idna": { "hashes": [ From d51761c92a67ef1246b10e33005df68b0fa44032 Mon Sep 17 00:00:00 2001 From: Alessandro Dell'Oste Date: Tue, 30 Jan 2024 12:35:19 +0100 Subject: [PATCH 7/7] feat: [IOCOM-866] Display tags in the header of a SEND message (#5440) This PR depends on https://github.com/pagopa/io-app/pull/5431 ## Short description This PR updates the `MessageDetails` component in order to display the tags. _NOTE: Other PRs will follow to update the rest of the message._
Details

| MessageDetails | | - | | |

## List of changes proposed in this pull request - added the tags in the header of the SEND message ## How to test Using the `io-dev-api-server`, generate a SEND message. Enable the design system and navigate to the message details. Check that the content is displayed correctly. --------- Co-authored-by: Andrea --- locales/en/index.yml | 2 + locales/it/index.yml | 2 + ts/features/pn/__mocks__/message.ts | 35 ++- ts/features/pn/components/MessageDetails.tsx | 58 +++-- .../__test__/MessageDetails.test.tsx | 39 +++- .../MessageDetails.test.tsx.snap | 210 +++++++++++++++++- .../MessageDetailsScreen.test.tsx.snap | 208 +++++++++++++++++ 7 files changed, 519 insertions(+), 35 deletions(-) diff --git a/locales/en/index.yml b/locales/en/index.yml index fd61136f9ff..f53eb1064e6 100644 --- a/locales/en/index.yml +++ b/locales/en/index.yml @@ -3116,6 +3116,8 @@ features: activated: "Grazie, il servizio è attivo!" error: "Si è verificato un errore con la tua richiesta" details: + badge: + legalValue: "Legal value" title: Dettaglio del messaggio noticeCode: "Codice avviso" loadError: diff --git a/locales/it/index.yml b/locales/it/index.yml index fb226ff9174..fdcc44df3e6 100644 --- a/locales/it/index.yml +++ b/locales/it/index.yml @@ -3116,6 +3116,8 @@ features: activated: "Grazie, il servizio è attivo!" error: "Si è verificato un errore con la tua richiesta" details: + badge: + legalValue: "Valore legale" title: Dettaglio del messaggio noticeCode: "Codice avviso" loadError: diff --git a/ts/features/pn/__mocks__/message.ts b/ts/features/pn/__mocks__/message.ts index 6bfc8eeaccb..0bea75bc58e 100644 --- a/ts/features/pn/__mocks__/message.ts +++ b/ts/features/pn/__mocks__/message.ts @@ -1,4 +1,5 @@ import { ThirdPartyMessageWithContent } from "../../../../definitions/backend/ThirdPartyMessageWithContent"; +import { ThirdPartyAttachment } from "../../../../definitions/pn/ThirdPartyAttachment"; import { message_1 } from "../../messages/__mocks__/message"; import { ATTACHMENT_CATEGORY } from "../../messages/types/attachmentCategory"; @@ -6,26 +7,24 @@ export const thirdPartyMessage: ThirdPartyMessageWithContent = { ...message_1, created_at: new Date("2020-01-01T00:00:00.000Z"), third_party_message: { + attachments: [ + { + id: "1", + name: "A First Attachment", + content_type: "application/pdf", + category: ATTACHMENT_CATEGORY.DOCUMENT, + url: "/resource/attachment1.pdf" + }, + { + id: "2", + name: "A Second Attachment", + content_type: "application/pdf", + category: ATTACHMENT_CATEGORY.DOCUMENT, + url: "/resource/attachment2.pdf" + } + ] as Array, details: { abstract: "######## abstract ########", - attachments: [ - { - messageId: message_1.id, - id: "1", - displayName: "A First Attachment", - contentType: "application/pdf", - category: ATTACHMENT_CATEGORY.DOCUMENT, - resourceUrl: { href: "/resource/attachment1.pdf" } - }, - { - messageId: message_1.id, - id: "2", - displayName: "A Second Attachment", - contentType: "application/pdf", - category: ATTACHMENT_CATEGORY.DOCUMENT, - resourceUrl: { href: "/resource/attachment2.pdf" } - } - ], iun: "731143-7-0317-8200-0", subject: "######## subject ########", recipients: [ diff --git a/ts/features/pn/components/MessageDetails.tsx b/ts/features/pn/components/MessageDetails.tsx index d6f25513ce4..bb5ce071485 100644 --- a/ts/features/pn/components/MessageDetails.tsx +++ b/ts/features/pn/components/MessageDetails.tsx @@ -1,10 +1,17 @@ import React from "react"; -import { ScrollView } from "react-native"; +import { ScrollView, View } from "react-native"; +import { pipe } from "fp-ts/lib/function"; +import * as O from "fp-ts/lib/Option"; +import * as RA from "fp-ts/lib/ReadonlyArray"; +import * as SEP from "fp-ts/lib/Separated"; +import { HSpacer, IOStyles, Tag, VSpacer } from "@pagopa/io-app-design-system"; import { ServicePublic } from "../../../../definitions/backend/ServicePublic"; -import { UIMessageId } from "../../messages/types"; +import { UIAttachment, UIMessageId } from "../../messages/types"; import { PNMessage } from "../store/types/types"; import { NotificationPaymentInfo } from "../../../../definitions/pn/NotificationPaymentInfo"; import { MessageDetailHeader } from "../../messages/components/MessageDetail/MessageDetailHeader"; +import { ATTACHMENT_CATEGORY } from "../../messages/types/attachmentCategory"; +import I18n from "../../../i18n"; import { MessageDetailsContent } from "./MessageDetailsContent"; type MessageDetailsProps = { @@ -14,14 +21,39 @@ type MessageDetailsProps = { payments?: ReadonlyArray; }; -export const MessageDetails = ({ message, service }: MessageDetailsProps) => ( - - - - -); +export const MessageDetails = ({ message, service }: MessageDetailsProps) => { + const partitionedAttachments = pipe( + message.attachments, + O.fromNullable, + O.getOrElse>(() => []), + RA.partition(attachment => attachment.category === ATTACHMENT_CATEGORY.F24) + ); + + const attachmentList = SEP.left(partitionedAttachments); + + return ( + + + + + {attachmentList.length > 0 && ( + <> + + + + )} + + + + + + ); +}; diff --git a/ts/features/pn/components/__test__/MessageDetails.test.tsx b/ts/features/pn/components/__test__/MessageDetails.test.tsx index b6c8687b84c..a87f86291be 100644 --- a/ts/features/pn/components/__test__/MessageDetails.test.tsx +++ b/ts/features/pn/components/__test__/MessageDetails.test.tsx @@ -11,19 +11,52 @@ import { PNMessage } from "../../store/types/types"; import { thirdPartyMessage } from "../../__mocks__/message"; import { toPNMessage } from "../../store/types/transformers"; import { UIMessageId } from "../../../messages/types"; +import I18n from "../../../../i18n"; -const pnMessage = pipe(thirdPartyMessage, toPNMessage, O.toUndefined); +const pnMessage = pipe(thirdPartyMessage, toPNMessage, O.toUndefined)!; describe("MessageDetails component", () => { - it("should match the snapshot", () => { + it("should match the snapshot with default props", () => { const { component } = renderComponent( generateComponentProperties( thirdPartyMessage.id as UIMessageId, - pnMessage! + pnMessage ) ); expect(component).toMatchSnapshot(); }); + + it("should display the legalMessage tag", () => { + const { component } = renderComponent( + generateComponentProperties( + thirdPartyMessage.id as UIMessageId, + pnMessage + ) + ); + expect( + component.queryByText(I18n.t("features.pn.details.badge.legalValue")) + ).not.toBeNull(); + }); + + it("should display the attachment tag if there are attachments", () => { + const { component } = renderComponent( + generateComponentProperties( + thirdPartyMessage.id as UIMessageId, + pnMessage + ) + ); + expect(component.queryByTestId("attachment-tag")).not.toBeNull(); + }); + + it("should NOT display the attachment tag if there are no attachments", () => { + const { component } = renderComponent( + generateComponentProperties(thirdPartyMessage.id as UIMessageId, { + ...pnMessage, + attachments: [] + }) + ); + expect(component.queryByTestId("attachment-tag")).toBeNull(); + }); }); const generateComponentProperties = ( diff --git a/ts/features/pn/components/__test__/__snapshots__/MessageDetails.test.tsx.snap b/ts/features/pn/components/__test__/__snapshots__/MessageDetails.test.tsx.snap index 45785dc4e5f..ec8a030d60f 100644 --- a/ts/features/pn/components/__test__/__snapshots__/MessageDetails.test.tsx.snap +++ b/ts/features/pn/components/__test__/__snapshots__/MessageDetails.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`MessageDetails component should match the snapshot 1`] = ` +exports[`MessageDetails component should match the snapshot with default props 1`] = ` + + + + + + + + + + + + Legal value + + + + + + + + + + + + + + + + + + + + + + + + + + Legal value + + + + + + + + + + + + + +