diff --git a/img/features/cgn/cgn_card.svg b/img/features/cgn/cgn_card.svg index ddffd3a9987..6494a89f257 100644 --- a/img/features/cgn/cgn_card.svg +++ b/img/features/cgn/cgn_card.svg @@ -3,8 +3,8 @@ - - + + diff --git a/locales/en/index.yml b/locales/en/index.yml index 4350aec6253..68587ba1449 100644 --- a/locales/en/index.yml +++ b/locales/en/index.yml @@ -836,8 +836,8 @@ email: title: We have sent you an email subtitle: To confirm your address, follow the instructions we have sent to link: Isn't that right? - buttonlabelsent: Email sent! - countdowntext: Request new email in + buttonlabelsent: Email sent! + countdowntext: Request new email in buttonlabelsentagain: Send email again toast: Done! Check your mailbox. newvalidemail: @@ -2935,7 +2935,7 @@ features: bonus: Iniziative welfare cgn: Carta Giovani Nazionale itw: IT Wallet - payment: Metodo di pagamento + payment: Payment methods webView: error: missingParams: Not all information necessary to access this page are available diff --git a/locales/it/index.yml b/locales/it/index.yml index 9fd97bd041e..fd325003d27 100644 --- a/locales/it/index.yml +++ b/locales/it/index.yml @@ -804,16 +804,16 @@ email: label: Indirizzo email personale alert: title: "{{email}} è già in uso, inseriscine un’altra." - description1: Email già in uso + description1: Email già in uso description2: Inserisci un indirizzo diverso da quello attuale description3: Stai già usando questa email - invalidemail: Inserisci un indirizzo email valido + invalidemail: Inserisci un indirizzo email valido modaltitle: Questa email è già in uso modaldescription: Può succedere se condividi lo stesso indirizzo con un familiare modalbutton: Usa un’altra email edit: title: Modifica la tua email - subtitle: Ora stai usando + subtitle: Ora stai usando validated: L'email che avevi inserito e validato in precedenza è label: Nuovo indirizzo email personale cta: Modifica email @@ -836,7 +836,7 @@ email: title: Ti abbiamo inviato un’email subtitle: Per confermare il tuo indirizzo, segui le istruzioni che abbiamo inviato a link: Non è corretto? - countdowntext: Richiedi una nuova email tra + countdowntext: Richiedi una nuova email tra buttonlabelsent: Email inviata! buttonlabelsentagain: Invia di nuovo l’email toast: Fatto! Controlla la tua casella di posta. @@ -2935,7 +2935,7 @@ features: bonus: Iniziative welfare cgn: Carta Giovani Nazionale itw: IT Wallet - payment: Metodo di pagamento + payment: Metodi di pagamento webView: error: missingParams: Non sono presenti le informazioni necessarie per accedere a questa pagina diff --git a/ts/features/idpay/wallet/components/IdPayCard.tsx b/ts/features/idpay/wallet/components/IdPayCard.tsx index 5b81f5f8ab5..1489d87709a 100644 --- a/ts/features/idpay/wallet/components/IdPayCard.tsx +++ b/ts/features/idpay/wallet/components/IdPayCard.tsx @@ -30,7 +30,16 @@ export const IdPayCard = (props: IdPayCardProps) => ( -
{props.name}
+
+ {props.name} +
diff --git a/ts/features/idpay/wallet/components/__tests__/__snapshots__/IdPayCard.test.tsx.snap b/ts/features/idpay/wallet/components/__tests__/__snapshots__/IdPayCard.test.tsx.snap index e4fbc15c962..7e205548f18 100644 --- a/ts/features/idpay/wallet/components/__tests__/__snapshots__/IdPayCard.test.tsx.snap +++ b/ts/features/idpay/wallet/components/__tests__/__snapshots__/IdPayCard.test.tsx.snap @@ -48,6 +48,7 @@ exports[`IdPayCard should match the snapshot 1`] = ` color="blueItalia-850" defaultColor="black" defaultWeight="SemiBold" + ellipsizeMode="tail" font="TitilliumWeb" fontStyle={ Object { @@ -55,8 +56,12 @@ exports[`IdPayCard should match the snapshot 1`] = ` "lineHeight": 25, } } + numberOfLines={1} style={ Array [ + Object { + "width": "80%", + }, Object { "fontSize": 18, "lineHeight": 25, diff --git a/ts/features/newWallet/screens/WalletHomeScreen.tsx b/ts/features/newWallet/screens/WalletHomeScreen.tsx index e18f3881a6c..238a2b2a47b 100644 --- a/ts/features/newWallet/screens/WalletHomeScreen.tsx +++ b/ts/features/newWallet/screens/WalletHomeScreen.tsx @@ -14,6 +14,7 @@ import { WalletEmptyScreenContent } from "../components/WalletEmptyScreenContent import { WalletPaymentsRedirectBanner } from "../components/WalletPaymentsRedirectBanner"; import { selectWalletCards } from "../store/selectors"; import { cgnDetails } from "../../bonus/cgn/store/actions/details"; +import { getPaymentsWalletUserMethods } from "../../payments/wallet/store/actions"; const WalletHomeScreen = () => { const dispatch = useIODispatch(); @@ -22,6 +23,7 @@ const WalletHomeScreen = () => { React.useEffect(() => { // TODO SIW-960 Move cards request to app startup + dispatch(getPaymentsWalletUserMethods.request()); dispatch(idPayWalletGet.request()); dispatch(cgnDetails.request()); }, [dispatch]); diff --git a/ts/features/newWallet/store/selectors/index.ts b/ts/features/newWallet/store/selectors/index.ts index e9f68e8e6e1..45fdcf2c0d1 100644 --- a/ts/features/newWallet/store/selectors/index.ts +++ b/ts/features/newWallet/store/selectors/index.ts @@ -1,22 +1,17 @@ -import { pipe } from "fp-ts/lib/function"; -import _ from "lodash"; import { createSelector } from "reselect"; import { GlobalState } from "../../../../store/reducers/types"; import { WalletCard, WalletCardCategory } from "../../types"; const selectWalletFeature = (state: GlobalState) => state.features.wallet; -export const isWalletPaymentsRedirectBannerVisibleSelector = ( - state: GlobalState -) => - pipe( - state, - selectWalletFeature, - wallet => wallet.preferences.shouldShowPaymentsRedirectBanner - ); +export const isWalletPaymentsRedirectBannerVisibleSelector = createSelector( + selectWalletFeature, + wallet => wallet.preferences.shouldShowPaymentsRedirectBanner +); -export const selectWalletCards = (state: GlobalState) => - pipe(state, selectWalletFeature, wallet => Object.values(wallet.cards)); +export const selectWalletCards = createSelector(selectWalletFeature, wallet => + Object.values(wallet.cards) +); export const getWalletCardsByCategorySelector = createSelector( selectWalletCards, diff --git a/ts/features/newWallet/types/index.ts b/ts/features/newWallet/types/index.ts index 6aadd26b6fe..eb90436738d 100644 --- a/ts/features/newWallet/types/index.ts +++ b/ts/features/newWallet/types/index.ts @@ -6,7 +6,7 @@ import { import { PaymentWalletCard, PaymentWalletCardProps -} from "../../payments/common/components/PaymentWalletCard"; +} from "../../payments/wallet/components/PaymentWalletCard"; import { WalletCardBaseComponent } from "../components/WalletCardBaseComponent"; import { CgnWalletCard, diff --git a/ts/features/payments/checkout/components/WalletPaymentConfirmContent.tsx b/ts/features/payments/checkout/components/WalletPaymentConfirmContent.tsx index 13fcfd67e42..9c62585a7ca 100644 --- a/ts/features/payments/checkout/components/WalletPaymentConfirmContent.tsx +++ b/ts/features/payments/checkout/components/WalletPaymentConfirmContent.tsx @@ -20,7 +20,7 @@ import { getPaymentLogo } from "../../common/utils"; import { WalletPaymentStepEnum } from "../types"; -import { UIWalletInfoDetails } from "../../details/types/UIWalletInfoDetails"; +import { UIWalletInfoDetails } from "../../common/types/UIWalletInfoDetails"; import { walletPaymentSetCurrentStep } from "../store/actions/orchestration"; import { walletPaymentPspListSelector } from "../store/selectors"; import { WalletPaymentTotalAmount } from "./WalletPaymentTotalAmount"; diff --git a/ts/features/payments/checkout/screens/WalletPaymentPickMethodScreen.tsx b/ts/features/payments/checkout/screens/WalletPaymentPickMethodScreen.tsx index 64f0f046bcd..cbdf4b3fb5e 100644 --- a/ts/features/payments/checkout/screens/WalletPaymentPickMethodScreen.tsx +++ b/ts/features/payments/checkout/screens/WalletPaymentPickMethodScreen.tsx @@ -26,7 +26,7 @@ import { useIONavigation } from "../../../../navigation/params/AppParamsList"; import { useIODispatch, useIOSelector } from "../../../../store/hooks"; import { ComponentProps } from "../../../../types/react"; import { findFirstCaseInsensitive } from "../../../../utils/object"; -import { UIWalletInfoDetails } from "../../details/types/UIWalletInfoDetails"; +import { UIWalletInfoDetails } from "../../common/types/UIWalletInfoDetails"; import { WalletPaymentMissingMethodsError } from "../components/WalletPaymentMissingMethodsError"; import { useOnTransactionActivationEffect } from "../hooks/useOnTransactionActivationEffect"; import { PaymentsCheckoutRoutes } from "../navigation/routes"; diff --git a/ts/features/payments/checkout/store/__tests__/store.test.ts b/ts/features/payments/checkout/store/__tests__/store.test.ts index 5d5f026f670..c299b9c6f5c 100644 --- a/ts/features/payments/checkout/store/__tests__/store.test.ts +++ b/ts/features/payments/checkout/store/__tests__/store.test.ts @@ -22,19 +22,19 @@ const INITIAL_STATE: PaymentsCheckoutState = { describe("Test Payment reducer", () => { it("should have initial state at startup", () => { const globalState = appReducer(undefined, applicationChangeState("active")); - expect(globalState.features.payments.payment).toStrictEqual(INITIAL_STATE); + expect(globalState.features.payments.checkout).toStrictEqual(INITIAL_STATE); }); it("should correctly update payment step, also when trying to overflow the steps, it should set the steps to WALLET_PAYMENT_STEP_MAX, and in case zero is passed it should set the step to 1", () => { const globalState = appReducer(undefined, applicationChangeState("active")); - expect(globalState.features.payments.payment).toStrictEqual(INITIAL_STATE); + expect(globalState.features.payments.checkout).toStrictEqual(INITIAL_STATE); const store = createStore(appReducer, globalState as any); store.dispatch(walletPaymentSetCurrentStep(2)); - expect(store.getState().features.payments.payment.currentStep).toBe(2); + expect(store.getState().features.payments.checkout.currentStep).toBe(2); store.dispatch(walletPaymentSetCurrentStep(0)); - expect(store.getState().features.payments.payment.currentStep).toBe(1); + expect(store.getState().features.payments.checkout.currentStep).toBe(1); }); }); diff --git a/ts/features/payments/checkout/store/selectors/index.ts b/ts/features/payments/checkout/store/selectors/index.ts index a0f81bca106..640a295994c 100644 --- a/ts/features/payments/checkout/store/selectors/index.ts +++ b/ts/features/payments/checkout/store/selectors/index.ts @@ -4,23 +4,23 @@ import { pipe } from "fp-ts/lib/function"; import { createSelector } from "reselect"; import { GlobalState } from "../../../../../store/reducers/types"; -const selectWalletPayment = (state: GlobalState) => - state.features.payments.payment; +const selectPaymentsCheckoutState = (state: GlobalState) => + state.features.payments.checkout; export const selectWalletPaymentCurrentStep = (state: GlobalState) => - selectWalletPayment(state).currentStep; + selectPaymentsCheckoutState(state).currentStep; export const selectWalletPaymentSessionTokenPot = (state: GlobalState) => - selectWalletPayment(state).sessionToken; + selectPaymentsCheckoutState(state).sessionToken; export const selectWalletPaymentSessionToken = (state: GlobalState) => pot.toUndefined(selectWalletPaymentSessionTokenPot(state)); export const walletPaymentRptIdSelector = (state: GlobalState) => - selectWalletPayment(state).rptId; + selectPaymentsCheckoutState(state).rptId; export const walletPaymentDetailsSelector = (state: GlobalState) => - selectWalletPayment(state).paymentDetails; + selectPaymentsCheckoutState(state).paymentDetails; export const walletPaymentAmountSelector = createSelector( walletPaymentDetailsSelector, @@ -28,7 +28,7 @@ export const walletPaymentAmountSelector = createSelector( ); export const walletPaymentAllMethodsSelector = createSelector( - selectWalletPayment, + selectPaymentsCheckoutState, state => pot.map(state.allPaymentMethods, _ => _.paymentMethods ?? []) ); @@ -43,7 +43,7 @@ export const walletPaymentGenericMethodByIdSelector = createSelector( ); export const walletPaymentUserWalletsSelector = createSelector( - selectWalletPayment, + selectPaymentsCheckoutState, state => pot.map(state.userWallets, _ => _.wallets ?? []) ); @@ -58,19 +58,19 @@ export const walletPaymentSavedMethodByIdSelector = createSelector( ); export const walletPaymentPickedPaymentMethodSelector = (state: GlobalState) => - selectWalletPayment(state).chosenPaymentMethod; + selectPaymentsCheckoutState(state).chosenPaymentMethod; export const walletPaymentPspListSelector = (state: GlobalState) => - selectWalletPayment(state).pspList; + selectPaymentsCheckoutState(state).pspList; export const walletPaymentPickedPspSelector = (state: GlobalState) => - selectWalletPayment(state).chosenPsp; + selectPaymentsCheckoutState(state).chosenPsp; export const walletPaymentTransactionSelector = (state: GlobalState) => - selectWalletPayment(state).transaction; + selectPaymentsCheckoutState(state).transaction; export const walletPaymentAuthorizationUrlSelector = (state: GlobalState) => - selectWalletPayment(state).authorizationUrl; + selectPaymentsCheckoutState(state).authorizationUrl; export const walletPaymentStartRouteSelector = (state: GlobalState) => - selectWalletPayment(state).startRoute; + selectPaymentsCheckoutState(state).startRoute; diff --git a/ts/features/payments/common/components/PaymentCard.tsx b/ts/features/payments/common/components/PaymentCard.tsx index 0e9e9379721..9a4ce88523f 100644 --- a/ts/features/payments/common/components/PaymentCard.tsx +++ b/ts/features/payments/common/components/PaymentCard.tsx @@ -191,15 +191,13 @@ const SkeletonPlaceholder = (props: Pick) => ( /> ); -const borderColor = "#0000001F"; - const styleSheet = StyleSheet.create({ card: { aspectRatio: 16 / 10, backgroundColor: IOColors["grey-100"], borderRadius: 16, borderWidth: 1, - borderColor + borderColor: IOColors["grey-200"] }, wrapper: { padding: 16, diff --git a/ts/features/payments/common/components/__tests__/PaymentCard.test.tsx b/ts/features/payments/common/components/__tests__/PaymentCard.test.tsx index 2ba830cab71..50adb0d3721 100644 --- a/ts/features/payments/common/components/__tests__/PaymentCard.test.tsx +++ b/ts/features/payments/common/components/__tests__/PaymentCard.test.tsx @@ -1,16 +1,8 @@ import { render } from "@testing-library/react-native"; import { format } from "date-fns"; import { default as React } from "react"; -import { createStore } from "redux"; import I18n from "../../../../../i18n"; -import ROUTES from "../../../../../navigation/routes"; -import { applicationChangeState } from "../../../../../store/actions/application"; -import { appReducer } from "../../../../../store/reducers"; -import { GlobalState } from "../../../../../store/reducers/types"; -import { renderScreenWithNavigationStoreContext } from "../../../../../utils/testWrapper"; import { PaymentCard } from "../PaymentCard"; -import { PaymentCardBig, PaymentCardBigProps } from "../PaymentCardBig"; -import { PaymentCardSmall, PaymentCardSmallProps } from "../PaymentCardSmall"; describe("PaymentCard", () => { jest.useFakeTimers(); @@ -65,107 +57,3 @@ describe("PaymentCard", () => { expect(queryByText("abc@abc.it")).not.toBeNull(); }); }); - -describe("PaymentCardSmall component", () => { - const testID = "PaymentCardSmallTestID"; - jest.useFakeTimers(); - it(`matches snapshot for loading`, () => { - const component = renderCardSmall({ isLoading: true }); - expect(component).toMatchSnapshot(); - }); - it(`matches snapshot for paypal`, () => { - const component = renderCardSmall({ cardType: "PAYPAL" }); - expect(component).toMatchSnapshot(); - }); - - it(`should render the Pressable component when passed an OnPress`, () => { - const handler = () => null; - const component = renderCardSmall({ - cardType: "PAYPAL", - onCardPress: handler, - testID - }); - expect(component.queryByTestId(`${testID}-pressable`)).not.toBeNull(); - }); - - it(`should render an error icon in case of error `, () => { - const component = renderCardSmall({ - cardType: "PAYPAL", - isError: true, - testID - }); - expect(component).not.toBeNull(); - expect(component.queryByTestId(`${testID}-errorIcon`)).not.toBeNull(); - }); - it(`should not render a pressable if no handler is passed`, () => { - const component = renderCardSmall({ - cardType: "PAYPAL", - isError: true, - testID - }); - expect(component).not.toBeNull(); - expect(component.queryByTestId(`${testID}-pressable`)).toBeNull(); - }); - it(`should render a skeleton if loading`, () => { - const component = renderCardSmall({ - isLoading: true, - testID - }); - expect(component.queryByTestId(`${testID}-skeleton`)).not.toBeNull(); - }); -}); - -describe("PaymentCardBig component", () => { - const testID = "PaymentCardBigTestID"; - jest.useFakeTimers(); - it(`matches snapshot for loading`, () => { - const component = renderCardBig({ isLoading: true }); - expect(component).toMatchSnapshot(); - }); - it(`matches snapshot for paypal`, () => { - const component = renderCardBig({ - cardType: "PAYPAL", - holderEmail: "someEmail@test.com" - }); - expect(component).toMatchSnapshot(); - }); - - it(`should render a phone number in case of BancomatPay`, () => { - const component = renderCardBig({ - cardType: "BANCOMATPAY", - phoneNumber: "1234567890", - holderName: "holderName", - testID - }); - expect(component).not.toBeNull(); - expect(component.queryByText("1234567890")).not.toBeNull(); - }); - it(`should render a skeleton when loading`, () => { - const component = renderCardBig({ - isLoading: true, - testID - }); - expect(component).not.toBeNull(); - expect(component.queryByTestId(`${testID}-skeleton`)).not.toBeNull(); - }); -}); - -function renderCardBig(props: PaymentCardBigProps) { - const globalState = appReducer(undefined, applicationChangeState("active")); - return renderScreenWithNavigationStoreContext( - () => , - ROUTES.WALLET_HOME, - {}, - createStore(appReducer, globalState as any) - ); -} - -function renderCardSmall(props: PaymentCardSmallProps) { - const globalState = appReducer(undefined, applicationChangeState("active")); - return renderScreenWithNavigationStoreContext( - () => , - ROUTES.WALLET_HOME, - {}, - createStore(appReducer, globalState as any) - ); -} diff --git a/ts/features/payments/common/components/__tests__/PaymentCardBig.test.tsx b/ts/features/payments/common/components/__tests__/PaymentCardBig.test.tsx new file mode 100644 index 00000000000..6ab8e29da3c --- /dev/null +++ b/ts/features/payments/common/components/__tests__/PaymentCardBig.test.tsx @@ -0,0 +1,53 @@ +import { default as React } from "react"; +import { createStore } from "redux"; +import ROUTES from "../../../../../navigation/routes"; +import { applicationChangeState } from "../../../../../store/actions/application"; +import { appReducer } from "../../../../../store/reducers"; +import { GlobalState } from "../../../../../store/reducers/types"; +import { renderScreenWithNavigationStoreContext } from "../../../../../utils/testWrapper"; +import { PaymentCardBig, PaymentCardBigProps } from "../PaymentCardBig"; + +describe("PaymentCardBig component", () => { + const testID = "PaymentCardBigTestID"; + jest.useFakeTimers(); + it(`matches snapshot for loading`, () => { + const component = renderCardBig({ isLoading: true }); + expect(component).toMatchSnapshot(); + }); + it(`matches snapshot for paypal`, () => { + const component = renderCardBig({ + cardType: "PAYPAL", + holderEmail: "someEmail@test.com" + }); + expect(component).toMatchSnapshot(); + }); + + it(`should render a phone number in case of BancomatPay`, () => { + const component = renderCardBig({ + cardType: "BANCOMATPAY", + phoneNumber: "1234567890", + holderName: "holderName", + testID + }); + expect(component).not.toBeNull(); + expect(component.queryByText("1234567890")).not.toBeNull(); + }); + it(`should render a skeleton when loading`, () => { + const component = renderCardBig({ + isLoading: true, + testID + }); + expect(component).not.toBeNull(); + expect(component.queryByTestId(`${testID}-skeleton`)).not.toBeNull(); + }); +}); + +function renderCardBig(props: PaymentCardBigProps) { + const globalState = appReducer(undefined, applicationChangeState("active")); + return renderScreenWithNavigationStoreContext( + () => , + ROUTES.WALLET_HOME, + {}, + createStore(appReducer, globalState as any) + ); +} diff --git a/ts/features/payments/common/components/__tests__/PaymentCardSmall.test.tsx b/ts/features/payments/common/components/__tests__/PaymentCardSmall.test.tsx new file mode 100644 index 00000000000..fcf5118c7da --- /dev/null +++ b/ts/features/payments/common/components/__tests__/PaymentCardSmall.test.tsx @@ -0,0 +1,67 @@ +import { default as React } from "react"; +import { createStore } from "redux"; +import ROUTES from "../../../../../navigation/routes"; +import { applicationChangeState } from "../../../../../store/actions/application"; +import { appReducer } from "../../../../../store/reducers"; +import { GlobalState } from "../../../../../store/reducers/types"; +import { renderScreenWithNavigationStoreContext } from "../../../../../utils/testWrapper"; +import { PaymentCardSmall, PaymentCardSmallProps } from "../PaymentCardSmall"; + +describe("PaymentCardSmall component", () => { + const testID = "PaymentCardSmallTestID"; + jest.useFakeTimers(); + it(`matches snapshot for loading`, () => { + const component = renderCardSmall({ isLoading: true }); + expect(component).toMatchSnapshot(); + }); + it(`matches snapshot for paypal`, () => { + const component = renderCardSmall({ cardType: "PAYPAL" }); + expect(component).toMatchSnapshot(); + }); + + it(`should render the Pressable component when passed an OnPress`, () => { + const handler = () => null; + const component = renderCardSmall({ + cardType: "PAYPAL", + onCardPress: handler, + testID + }); + expect(component.queryByTestId(`${testID}-pressable`)).not.toBeNull(); + }); + + it(`should render an error icon in case of error `, () => { + const component = renderCardSmall({ + cardType: "PAYPAL", + isError: true, + testID + }); + expect(component).not.toBeNull(); + expect(component.queryByTestId(`${testID}-errorIcon`)).not.toBeNull(); + }); + it(`should not render a pressable if no handler is passed`, () => { + const component = renderCardSmall({ + cardType: "PAYPAL", + isError: true, + testID + }); + expect(component).not.toBeNull(); + expect(component.queryByTestId(`${testID}-pressable`)).toBeNull(); + }); + it(`should render a skeleton if loading`, () => { + const component = renderCardSmall({ + isLoading: true, + testID + }); + expect(component.queryByTestId(`${testID}-skeleton`)).not.toBeNull(); + }); +}); + +function renderCardSmall(props: PaymentCardSmallProps) { + const globalState = appReducer(undefined, applicationChangeState("active")); + return renderScreenWithNavigationStoreContext( + () => , + ROUTES.WALLET_HOME, + {}, + createStore(appReducer, globalState as any) + ); +} diff --git a/ts/features/payments/common/components/__tests__/__snapshots__/PaymentCard.test.tsx.snap b/ts/features/payments/common/components/__tests__/__snapshots__/PaymentCard.test.tsx.snap index 1e09b40632f..8a74210a395 100644 --- a/ts/features/payments/common/components/__tests__/__snapshots__/PaymentCard.test.tsx.snap +++ b/ts/features/payments/common/components/__tests__/__snapshots__/PaymentCard.test.tsx.snap @@ -6,7 +6,7 @@ exports[`PaymentCard should match snapshot for loading 1`] = ` Object { "aspectRatio": 1.6, "backgroundColor": "#E8EBF1", - "borderColor": "#0000001F", + "borderColor": "#D2D6E3", "borderRadius": 16, "borderWidth": 1, } @@ -162,1759 +162,3 @@ exports[`PaymentCard should match snapshot for loading 1`] = `
`; - -exports[`PaymentCardBig component matches snapshot for loading 1`] = ` - - - - - - - - - - - - - - - WALLET_HOME - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -`; - -exports[`PaymentCardBig component matches snapshot for paypal 1`] = ` - - - - - - - - - - - - - - - WALLET_HOME - - - - - - - - - - - - - - - - - - - - - someEmail@test.com - - - - - - - - - - - - - -`; - -exports[`PaymentCardSmall component matches snapshot for loading 1`] = ` - - - - - - - - - - - - - - - WALLET_HOME - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -`; - -exports[`PaymentCardSmall component matches snapshot for paypal 1`] = ` - - - - - - - - - - - - - - - WALLET_HOME - - - - - - - - - - - - - - - - - - - - - - - - - - - - - PayPal - - - - - - - - - - - - - -`; diff --git a/ts/features/payments/common/components/__tests__/__snapshots__/PaymentCardBig.test.tsx.snap b/ts/features/payments/common/components/__tests__/__snapshots__/PaymentCardBig.test.tsx.snap new file mode 100644 index 00000000000..6abc3bc74f4 --- /dev/null +++ b/ts/features/payments/common/components/__tests__/__snapshots__/PaymentCardBig.test.tsx.snap @@ -0,0 +1,870 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`PaymentCardBig component matches snapshot for loading 1`] = ` + + + + + + + + + + + + + + + WALLET_HOME + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; + +exports[`PaymentCardBig component matches snapshot for paypal 1`] = ` + + + + + + + + + + + + + + + WALLET_HOME + + + + + + + + + + + + + + + + + + + + + someEmail@test.com + + + + + + + + + + + + + +`; diff --git a/ts/features/payments/common/components/__tests__/__snapshots__/PaymentCardSmall.test.tsx.snap b/ts/features/payments/common/components/__tests__/__snapshots__/PaymentCardSmall.test.tsx.snap new file mode 100644 index 00000000000..38cfcaf2924 --- /dev/null +++ b/ts/features/payments/common/components/__tests__/__snapshots__/PaymentCardSmall.test.tsx.snap @@ -0,0 +1,888 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`PaymentCardSmall component matches snapshot for loading 1`] = ` + + + + + + + + + + + + + + + WALLET_HOME + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; + +exports[`PaymentCardSmall component matches snapshot for paypal 1`] = ` + + + + + + + + + + + + + + + WALLET_HOME + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PayPal + + + + + + + + + + + + + +`; diff --git a/ts/features/payments/common/saga/index.ts b/ts/features/payments/common/saga/index.ts index 3dc547ce47f..c68b76f3a6a 100644 --- a/ts/features/payments/common/saga/index.ts +++ b/ts/features/payments/common/saga/index.ts @@ -7,6 +7,7 @@ import { createPaymentClient, createWalletClient } from "../api/client"; import { walletApiBaseUrl, walletApiUatBaseUrl } from "../../../../config"; import { watchPaymentsMethodDetailsSaga } from "../../details/saga"; import { watchPaymentsTransactionSaga } from "../../transaction/saga"; +import { watchPaymentsWalletSaga } from "../../wallet/saga"; export function* watchPaymentsSaga(walletToken: string): SagaIterator { const isPagoPATestEnabled = yield* select(isPagoPATestEnabledSelector); @@ -18,6 +19,7 @@ export function* watchPaymentsSaga(walletToken: string): SagaIterator { const walletClient = createWalletClient(walletBaseUrl, walletToken); const paymentClient = createPaymentClient(walletBaseUrl, walletToken); + yield* fork(watchPaymentsWalletSaga, walletClient); yield* fork(watchPaymentsOnboardingSaga, walletClient); yield* fork(watchPaymentsMethodDetailsSaga, walletClient); yield* fork(watchPaymentsTransactionSaga, walletClient); diff --git a/ts/features/payments/common/store/actions/index.ts b/ts/features/payments/common/store/actions/index.ts index c25420fcd4e..5a30d06b2b2 100644 --- a/ts/features/payments/common/store/actions/index.ts +++ b/ts/features/payments/common/store/actions/index.ts @@ -3,10 +3,12 @@ import { PaymentsHistoryActions } from "../../../history/store/actions"; import { PaymentsOnboardingActions } from "../../../onboarding/store/actions"; import { PaymentsCheckoutActions } from "../../../checkout/store/actions"; import { PaymentsTransactionActions } from "../../../transaction/store/actions"; +import { PaymentsWalletActions } from "../../../wallet/store/actions"; export type PaymentsActions = | PaymentsOnboardingActions | PaymentsMethodDetailsActions | PaymentsCheckoutActions | PaymentsTransactionActions - | PaymentsHistoryActions; + | PaymentsHistoryActions + | PaymentsWalletActions; diff --git a/ts/features/payments/common/store/reducers/index.ts b/ts/features/payments/common/store/reducers/index.ts index 4cb196e9908..44845a5e129 100644 --- a/ts/features/payments/common/store/reducers/index.ts +++ b/ts/features/payments/common/store/reducers/index.ts @@ -1,5 +1,8 @@ import { combineReducers } from "redux"; import { PersistPartial } from "redux-persist"; +import paymentReducer, { + PaymentsCheckoutState +} from "../../../checkout/store/reducers"; import detailsReducer, { PaymentsMethodDetailsState } from "../../../details/store/reducers"; @@ -9,27 +12,29 @@ import historyReducer, { import onboardingReducer, { PaymentsOnboardingState } from "../../../onboarding/store/reducers"; -import paymentReducer, { - PaymentsCheckoutState -} from "../../../checkout/store/reducers"; import transactionReducer, { PaymentsTransactionState } from "../../../transaction/store/reducers"; +import paymentsWalletReducer, { + PaymentsWalletState +} from "../../../wallet/store/reducers"; export type PaymentsState = { onboarding: PaymentsOnboardingState; details: PaymentsMethodDetailsState; - payment: PaymentsCheckoutState; + checkout: PaymentsCheckoutState; transaction: PaymentsTransactionState; history: PaymentsHistoryState & PersistPartial; + wallet: PaymentsWalletState; }; const paymentsReducer = combineReducers({ onboarding: onboardingReducer, details: detailsReducer, - payment: paymentReducer, + checkout: paymentReducer, transaction: transactionReducer, - history: historyReducer + history: historyReducer, + wallet: paymentsWalletReducer }); export default paymentsReducer; diff --git a/ts/features/payments/details/types/UIWalletInfoDetails.ts b/ts/features/payments/common/types/UIWalletInfoDetails.ts similarity index 100% rename from ts/features/payments/details/types/UIWalletInfoDetails.ts rename to ts/features/payments/common/types/UIWalletInfoDetails.ts diff --git a/ts/features/payments/common/utils/index.ts b/ts/features/payments/common/utils/index.ts index b2eb812a993..61d88efd94e 100644 --- a/ts/features/payments/common/utils/index.ts +++ b/ts/features/payments/common/utils/index.ts @@ -8,13 +8,13 @@ import { pipe } from "fp-ts/lib/function"; import I18n from "i18n-js"; import _ from "lodash"; import { Bundle } from "../../../../../definitions/pagopa/ecommerce/Bundle"; +import { WalletApplicationStatusEnum } from "../../../../../definitions/pagopa/walletv3/WalletApplicationStatus"; import { WalletInfo } from "../../../../../definitions/pagopa/walletv3/WalletInfo"; import { PaymentSupportStatus } from "../../../../types/paymentMethodCapabilities"; import { isExpiredDate } from "../../../../utils/dates"; import { findFirstCaseInsensitive } from "../../../../utils/object"; -import { UIWalletInfoDetails } from "../../details/types/UIWalletInfoDetails"; import { WalletPaymentPspSortType } from "../../checkout/types"; -import { WalletApplicationStatusEnum } from "../../../../../definitions/pagopa/walletv3/WalletApplicationStatus"; +import { UIWalletInfoDetails } from "../types/UIWalletInfoDetails"; /** * A simple function to get the corresponding translated badge text, diff --git a/ts/features/payments/details/screens/PaymentsMethodDetailsScreen.tsx b/ts/features/payments/details/screens/PaymentsMethodDetailsScreen.tsx index a4d0f3cc121..fb5be289b29 100644 --- a/ts/features/payments/details/screens/PaymentsMethodDetailsScreen.tsx +++ b/ts/features/payments/details/screens/PaymentsMethodDetailsScreen.tsx @@ -1,5 +1,5 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; import { IOLogoPaymentExtType } from "@pagopa/io-app-design-system"; +import * as pot from "@pagopa/ts-commons/lib/pot"; import { RouteProp, useNavigation, useRoute } from "@react-navigation/native"; import * as O from "fp-ts/lib/Option"; import { pipe } from "fp-ts/lib/function"; @@ -17,12 +17,12 @@ import { getDateFromExpiryDate } from "../../../../utils/dates"; import { capitalize } from "../../../../utils/strings"; import { idPayAreInitiativesFromInstrumentLoadingSelector } from "../../../idpay/wallet/store/reducers"; import { PaymentCardBig } from "../../common/components/PaymentCardBig"; +import { UIWalletInfoDetails } from "../../common/types/UIWalletInfoDetails"; import WalletDetailsPaymentMethodFeatures from "../components/WalletDetailsPaymentMethodFeatures"; import WalletDetailsPaymentMethodScreen from "../components/WalletDetailsPaymentMethodScreen"; import { PaymentsMethodDetailsParamsList } from "../navigation/params"; import { paymentsGetMethodDetailsAction } from "../store/actions"; import { selectPaymentMethodDetails } from "../store/selectors"; -import { UIWalletInfoDetails } from "../types/UIWalletInfoDetails"; export type PaymentsMethodDetailsScreenNavigationParams = Readonly<{ walletId: string; diff --git a/ts/features/payments/home/components/PaymentsHomeUserMethodsList.tsx b/ts/features/payments/home/components/PaymentsHomeUserMethodsList.tsx index 159cd0f802c..bd421c1c657 100644 --- a/ts/features/payments/home/components/PaymentsHomeUserMethodsList.tsx +++ b/ts/features/payments/home/components/PaymentsHomeUserMethodsList.tsx @@ -8,12 +8,11 @@ import I18n from "../../../../i18n"; import { useIONavigation } from "../../../../navigation/params/AppParamsList"; import { useIODispatch, useIOSelector } from "../../../../store/hooks"; import { paymentsGetPaymentUserMethodsAction } from "../../checkout/store/actions/networking"; -import { walletPaymentUserWalletsSelector } from "../../checkout/store/selectors"; import { PaymentCardSmallProps } from "../../common/components/PaymentCardSmall"; import { PaymentCardsCarousel } from "../../common/components/PaymentCardsCarousel"; -import { UIWalletInfoDetails } from "../../details/types/UIWalletInfoDetails"; +import { UIWalletInfoDetails } from "../../common/types/UIWalletInfoDetails"; import { PaymentsOnboardingRoutes } from "../../onboarding/navigation/routes"; - +import { walletPaymentUserWalletsSelector } from "../../checkout/store/selectors"; const loadingCards: Array = Array.from({ length: 3 }).map(() => ({ diff --git a/ts/features/payments/common/components/PaymentWalletCard.tsx b/ts/features/payments/wallet/components/PaymentWalletCard.tsx similarity index 92% rename from ts/features/payments/common/components/PaymentWalletCard.tsx rename to ts/features/payments/wallet/components/PaymentWalletCard.tsx index 77d8e53af18..0e827d01fff 100644 --- a/ts/features/payments/common/components/PaymentWalletCard.tsx +++ b/ts/features/payments/wallet/components/PaymentWalletCard.tsx @@ -2,8 +2,11 @@ import React from "react"; import { Pressable } from "react-native"; import { useIONavigation } from "../../../../navigation/params/AppParamsList"; import { withWalletCardBaseComponent } from "../../../newWallet/components/WalletCardBaseComponent"; +import { + PaymentCard, + PaymentCardProps +} from "../../common/components/PaymentCard"; import { PaymentsMethodDetailsRoutes } from "../../details/navigation/routes"; -import { PaymentCard, PaymentCardProps } from "./PaymentCard"; export type PaymentWalletCardProps = PaymentCardProps & { walletId: string; diff --git a/ts/features/payments/wallet/saga/__tests__/handleGetPaymentsWalletUserMethods.test.ts b/ts/features/payments/wallet/saga/__tests__/handleGetPaymentsWalletUserMethods.test.ts new file mode 100644 index 00000000000..5a755d441f5 --- /dev/null +++ b/ts/features/payments/wallet/saga/__tests__/handleGetPaymentsWalletUserMethods.test.ts @@ -0,0 +1,127 @@ +import * as E from "fp-ts/lib/Either"; +import { testSaga } from "redux-saga-test-plan"; +import { getType } from "typesafe-actions"; +import { WalletStatusEnum } from "../../../../../../definitions/pagopa/walletv3/WalletStatus"; +import { Wallets } from "../../../../../../definitions/pagopa/walletv3/Wallets"; +import { withRefreshApiCall } from "../../../../fastLogin/saga/utils"; +import { getPaymentsWalletUserMethods } from "../../store/actions"; +import { handleGetPaymentsWalletUserMethods } from "../handleGetPaymentsWalletUserMethods"; +import { BrandEnum } from "../../../../../../definitions/pagopa/walletv3/WalletInfoDetails"; +import { WalletCard } from "../../../../newWallet/types"; +import { walletAddCards } from "../../../../newWallet/store/actions/cards"; +import { getGenericError } from "../../../../../utils/errors"; +import { readablePrivacyReport } from "../../../../../utils/reporters"; + +describe("handleGetPaymentsWalletUserMethods", () => { + it(`should put ${getType(getPaymentsWalletUserMethods.success)} and ${getType( + walletAddCards + )} when response is success`, () => { + const T_WALLETID = "1234"; + const T_HPAN = "0001"; + const T_EXPIRE_DATE = new Date(2027, 10, 1); + const mockGetWalletsByIdUser = jest.fn(); + const getWalletsByIdUserResponse: Wallets = { + wallets: [ + { + walletId: T_WALLETID, + creationDate: new Date(), + paymentMethodId: "paymentMethodId", + paymentMethodAsset: "paymentMethodAsset", + applications: [], + status: WalletStatusEnum.CREATED, + updateDate: new Date(), + details: { + type: "CREDITCARD", + lastFourDigits: T_HPAN, + expiryDate: T_EXPIRE_DATE, + brand: BrandEnum.VISA + } + } + ] + }; + const cards: ReadonlyArray = [ + { + key: `method_${T_WALLETID}`, + type: "payment", + category: "payment", + walletId: T_WALLETID, + hpan: T_HPAN, + brand: BrandEnum.VISA, + expireDate: T_EXPIRE_DATE, + abiCode: undefined, + holderEmail: undefined, + holderPhone: undefined + } + ]; + + testSaga( + handleGetPaymentsWalletUserMethods, + mockGetWalletsByIdUser, + getPaymentsWalletUserMethods.request() + ) + .next() + .call( + withRefreshApiCall, + mockGetWalletsByIdUser(), + getPaymentsWalletUserMethods.request() + ) + .next(E.right({ status: 200, value: getWalletsByIdUserResponse })) + .put(walletAddCards(cards)) + .next() + .put(getPaymentsWalletUserMethods.success(getWalletsByIdUserResponse)) + .next() + .isDone(); + }); + + it(`should put ${getType( + getPaymentsWalletUserMethods.failure + )} when response is not success`, () => { + const mockGetWalletsByIdUser = jest.fn(); + + testSaga( + handleGetPaymentsWalletUserMethods, + mockGetWalletsByIdUser, + getPaymentsWalletUserMethods.request() + ) + .next() + .call( + withRefreshApiCall, + mockGetWalletsByIdUser(), + getPaymentsWalletUserMethods.request() + ) + .next(E.right({ status: 400, value: undefined })) + .put( + getPaymentsWalletUserMethods.failure( + getGenericError(new Error(`Error: 400`)) + ) + ) + .next() + .isDone(); + }); + + it(`should put ${getType( + getPaymentsWalletUserMethods.failure + )} when getWalletsByIdUser encoders returns an error`, () => { + const mockGetWalletsByIdUser = jest.fn(); + + testSaga( + handleGetPaymentsWalletUserMethods, + mockGetWalletsByIdUser, + getPaymentsWalletUserMethods.request() + ) + .next() + .call( + withRefreshApiCall, + mockGetWalletsByIdUser(), + getPaymentsWalletUserMethods.request() + ) + .next(E.left([])) + .put( + getPaymentsWalletUserMethods.failure({ + ...getGenericError(new Error(readablePrivacyReport([]))) + }) + ) + .next() + .isDone(); + }); +}); diff --git a/ts/features/payments/wallet/saga/handleGetPaymentsWalletUserMethods.ts b/ts/features/payments/wallet/saga/handleGetPaymentsWalletUserMethods.ts new file mode 100644 index 00000000000..76b90239775 --- /dev/null +++ b/ts/features/payments/wallet/saga/handleGetPaymentsWalletUserMethods.ts @@ -0,0 +1,80 @@ +import * as E from "fp-ts/lib/Either"; +import { pipe } from "fp-ts/lib/function"; +import { call, put } from "typed-redux-saga/macro"; +import { ActionType } from "typesafe-actions"; +import { WalletInfo } from "../../../../../definitions/pagopa/walletv3/WalletInfo"; +import { SagaCallReturnType } from "../../../../types/utils"; +import { getGenericError, getNetworkError } from "../../../../utils/errors"; +import { readablePrivacyReport } from "../../../../utils/reporters"; +import { withRefreshApiCall } from "../../../fastLogin/saga/utils"; +import { walletAddCards } from "../../../newWallet/store/actions/cards"; +import { WalletCard } from "../../../newWallet/types"; +import { WalletClient } from "../../common/api/client"; +import { UIWalletInfoDetails } from "../../common/types/UIWalletInfoDetails"; +import { getPaymentsWalletUserMethods } from "../store/actions"; + +const mapWalletsToCards = ( + wallets: ReadonlyArray +): ReadonlyArray => + wallets.map(wallet => { + const details = wallet.details as UIWalletInfoDetails; + + return { + key: `method_${wallet.walletId}`, + type: "payment", + category: "payment", + walletId: wallet.walletId, + hpan: details.lastFourDigits, + abiCode: details.abi, + brand: details.brand, + expireDate: details.expiryDate, + holderEmail: details.maskedEmail, + holderPhone: details.maskedNumber + }; + }); + +export function* handleGetPaymentsWalletUserMethods( + getWalletsByIdUser: WalletClient["getWalletsByIdUser"], + action: ActionType<(typeof getPaymentsWalletUserMethods)["request"]> +) { + const getWalletsByIdUserRequest = getWalletsByIdUser({}); + + try { + const getWalletsByIdUserResult = (yield* call( + withRefreshApiCall, + getWalletsByIdUserRequest, + action + )) as SagaCallReturnType; + + yield* pipe( + getWalletsByIdUserResult, + E.fold( + function* (error) { + yield* put( + getPaymentsWalletUserMethods.failure( + getGenericError(new Error(readablePrivacyReport(error))) + ) + ); + }, + function* (res) { + if (res.status === 200) { + yield* put( + walletAddCards(mapWalletsToCards(res.value?.wallets || [])) + ); + yield* put(getPaymentsWalletUserMethods.success(res.value)); + } else if (res.status === 404) { + yield* put(getPaymentsWalletUserMethods.success({ wallets: [] })); + } else { + yield* put( + getPaymentsWalletUserMethods.failure({ + ...getGenericError(new Error(`Error: ${res.status}`)) + }) + ); + } + } + ) + ); + } catch (e) { + yield* put(getPaymentsWalletUserMethods.failure({ ...getNetworkError(e) })); + } +} diff --git a/ts/features/payments/wallet/saga/index.ts b/ts/features/payments/wallet/saga/index.ts new file mode 100644 index 00000000000..df0b90a7190 --- /dev/null +++ b/ts/features/payments/wallet/saga/index.ts @@ -0,0 +1,19 @@ +import { SagaIterator } from "redux-saga"; +import { takeLatest } from "typed-redux-saga/macro"; +import { WalletClient } from "../../common/api/client"; +import { getPaymentsWalletUserMethods } from "../store/actions"; +import { handleGetPaymentsWalletUserMethods } from "./handleGetPaymentsWalletUserMethods"; + +/** + * Handle Wallet onboarding requests + * @param bearerToken + */ +export function* watchPaymentsWalletSaga( + walletClient: WalletClient +): SagaIterator { + yield* takeLatest( + getPaymentsWalletUserMethods.request, + handleGetPaymentsWalletUserMethods, + walletClient.getWalletsByIdUser + ); +} diff --git a/ts/features/payments/wallet/store/actions/index.ts b/ts/features/payments/wallet/store/actions/index.ts new file mode 100644 index 00000000000..39f2c4e519f --- /dev/null +++ b/ts/features/payments/wallet/store/actions/index.ts @@ -0,0 +1,13 @@ +import { ActionType, createAsyncAction } from "typesafe-actions"; +import { Wallets } from "../../../../../../definitions/pagopa/walletv3/Wallets"; +import { NetworkError } from "../../../../../utils/errors"; + +export const getPaymentsWalletUserMethods = createAsyncAction( + "PAYMENTS_WALLET_GET_USER_METHODS_REQUEST", + "PAYMENTS_WALLET_GET_USER_METHODS_SUCCESS", + "PAYMENTS_WALLET_GET_USER_METHODS_FAILURE" +)(); + +export type PaymentsWalletActions = ActionType< + typeof getPaymentsWalletUserMethods +>; diff --git a/ts/features/payments/wallet/store/reducers/index.ts b/ts/features/payments/wallet/store/reducers/index.ts new file mode 100644 index 00000000000..0271e196e58 --- /dev/null +++ b/ts/features/payments/wallet/store/reducers/index.ts @@ -0,0 +1,40 @@ +import * as pot from "@pagopa/ts-commons/lib/pot"; +import { getType } from "typesafe-actions"; +import { Action } from "../../../../../store/actions/types"; +import { NetworkError } from "../../../../../utils/errors"; +import { getPaymentsWalletUserMethods } from "../actions"; +import { Wallets } from "../../../../../../definitions/pagopa/walletv3/Wallets"; + +export type PaymentsWalletState = { + userMethods: pot.Pot; +}; + +const INITIAL_STATE: PaymentsWalletState = { + userMethods: pot.none +}; + +const paymentsWalletReducer = ( + state: PaymentsWalletState = INITIAL_STATE, + action: Action +): PaymentsWalletState => { + switch (action.type) { + case getType(getPaymentsWalletUserMethods.request): + return { + ...state, + userMethods: pot.toLoading(pot.none) + }; + case getType(getPaymentsWalletUserMethods.success): + return { + ...state, + userMethods: pot.some(action.payload) + }; + case getType(getPaymentsWalletUserMethods.failure): + return { + ...state, + userMethods: pot.toError(state.userMethods, action.payload) + }; + } + return state; +}; + +export default paymentsWalletReducer;