Skip to content

Commit

Permalink
chore: [IOBP-479] Add temporary missing methods error in method selec…
Browse files Browse the repository at this point in the history
…tion screen (#5394)

## Short description
This PR adds the (temporary) missing methods error in methods selection
screen

## List of changes proposed in this pull request
- Refactored `WalletPaymentPickMethodScreen` to allow the error to be
displayed
- Added `WalletPaymentMissingMethodsError` component
- Added required locale keys

## How to test
With the `io-dev-api-server`, simulate an empty user wallet. Start a new
payment from **Profile > New Wallet > Payment** and check if the error
is displayed correctly and with working buttons.

## Preview


https://github.com/pagopa/io-app/assets/6160324/65f4d64f-6980-4d36-a82d-873ccf505b5e
  • Loading branch information
mastro993 authored Jan 12, 2024
1 parent 978c689 commit 36e378f
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 64 deletions.
5 changes: 5 additions & 0 deletions locales/en/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1667,6 +1667,11 @@ wallet:
alert:
body: "A causa dell’importo elevato, alcuni metodi non sono disponibili."
cta: "Ok, ho capito!"
missingMethodsError:
title: Aggiungi un metodo per effettuare pagamenti in app
subtitle: Il metodo verrà salvato nel Portafoglio, così la prossima volta potrai pagare più facilmente.
addMethod: Aggiungi metodo
notNow: Non ora
psp:
title: Scegli chi gestirà il pagamento
description: Ogni gestore propone una commissione.
Expand Down
5 changes: 5 additions & 0 deletions locales/it/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1667,6 +1667,11 @@ wallet:
alert:
body: "A causa dell’importo elevato, alcuni metodi non sono disponibili."
cta: "Ok, ho capito!"
missingMethodsError:
title: Aggiungi un metodo per effettuare pagamenti in app
subtitle: Il metodo verrà salvato nel Portafoglio, così la prossima volta potrai pagare più facilmente.
addMethod: Aggiungi metodo
notNow: Non ora
psp:
title: Scegli chi gestirà il pagamento
description: Ogni gestore propone una commissione.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { useNavigation } from "@react-navigation/native";
import React from "react";
import { OperationResultScreenContent } from "../../../../components/screens/OperationResultScreenContent";
import {
AppParamsList,
IOStackNavigationProp
} from "../../../../navigation/params/AppParamsList";
import { WalletOnboardingRoutes } from "../../onboarding/navigation/navigator";
import I18n from "../../../../i18n";

const WalletPaymentMissingMethodsError = () => {
const navigation = useNavigation<IOStackNavigationProp<AppParamsList>>();

React.useEffect(() => {
navigation.setOptions({
header: () => undefined,
headerTransparent: true
});
}, [navigation]);

const handleAddMethod = () => {
navigation.push(WalletOnboardingRoutes.WALLET_ONBOARDING_MAIN, {
screen: WalletOnboardingRoutes.WALLET_ONBOARDING_SELECT_PAYMENT_METHOD
});
};

const handleNotNow = () => {
navigation.pop();
};

return (
<OperationResultScreenContent
pictogram="cardAdd"
title={I18n.t("wallet.payment.methodSelection.missingMethodsError.title")}
subtitle={I18n.t(
"wallet.payment.methodSelection.missingMethodsError.subtitle"
)}
action={{
label: I18n.t(
"wallet.payment.methodSelection.missingMethodsError.addMethod"
),
accessibilityLabel: I18n.t(
"wallet.payment.methodSelection.missingMethodsError.addMethod"
),
onPress: handleAddMethod
}}
secondaryAction={{
label: I18n.t(
"wallet.payment.methodSelection.missingMethodsError.notNow"
),
accessibilityLabel: I18n.t(
"wallet.payment.methodSelection.missingMethodsError.notNow"
),
onPress: handleNotNow
}}
/>
);
};

export { WalletPaymentMissingMethodsError };
104 changes: 45 additions & 59 deletions ts/features/walletV3/payment/screens/WalletPaymentPickMethodScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,7 @@ import { ComponentProps } from "../../../../types/react";
import { emptyContextualHelp } from "../../../../utils/emptyContextualHelp";
import { findFirstCaseInsensitive } from "../../../../utils/object";
import { WalletPaymentRoutes } from "../navigation/routes";
import {
walletPaymentGetAllMethods,
walletPaymentGetUserWallets
} from "../store/actions/networking";
import { walletPaymentGetUserWallets } from "../store/actions/networking";
import { walletPaymentPickPaymentMethod } from "../store/actions/orchestration";
import {
walletPaymentAllMethodsSelector,
Expand All @@ -44,23 +41,22 @@ import {
walletPaymentTransactionSelector,
walletPaymentUserWalletsSelector
} from "../store/selectors";
import { WalletPaymentMissingMethodsError } from "../components/WalletPaymentMissingMethodsError";
import { useWalletPaymentGoBackHandler } from "../hooks/useWalletPaymentGoBackHandler";

// ----------------- TYPES -----------------

type SavedMethodState = {
kind: "saved";
walletId: string;
methodId?: undefined;
};

type NotSavedMethodState = {
kind: "generic";
methodId: string;
walletId?: undefined;
};

type SelectedMethodState = SavedMethodState | NotSavedMethodState | undefined;
// ----------------- SCREEN -----------------

const WalletPaymentPickMethodScreen = () => {
const dispatch = useIODispatch();
Expand All @@ -81,17 +77,12 @@ const WalletPaymentPickMethodScreen = () => {
walletPaymentSavedMethodByIdSelector
);
const paymentAmountPot = useIOSelector(walletPaymentAmountSelector);
const paymentMethodsPot = pot.none as ReturnType<
typeof walletPaymentAllMethodsSelector
>;
// substitute line over this with the one under once
// generic methods are implemented
// useIOSelector(walletPaymentAllMethodsSelector);
const paymentMethodsPot = useIOSelector(walletPaymentAllMethodsSelector);
const userWalletsPots = useIOSelector(walletPaymentUserWalletsSelector);
// todo:: will be needed when generic method selection is implemented
// const getGenericMethodById = useIOSelector(
// walletPaymentGenericMethodByIdSelector
// );
// const getGenericMethodById = useIOSelector(walletPaymentGenericMethodByIdSelector);

const alertRef = React.useRef<View>(null);

const isLoading =
pot.isLoading(paymentMethodsPot) ||
pot.isLoading(userWalletsPots) ||
Expand All @@ -102,10 +93,23 @@ const WalletPaymentPickMethodScreen = () => {
const [selectedMethod, setSelectedMethod] =
React.useState<SelectedMethodState>(undefined);

useHeaderSecondLevel({
title: "",
contextualHelp: emptyContextualHelp,
faqCategories: ["payment"],
supportRequest: true
});

useFocusEffect(
React.useCallback(() => {
// dispatch(walletPaymentGetAllMethods.request()); // currently we do not allow onboarding new methods in payment flow
dispatch(walletPaymentGetUserWallets.request());
}, [dispatch])
);

const paymentAmount = pot.getOrElse(paymentAmountPot, undefined);
const canContinue = selectedMethod !== undefined;

// -------------------------- LISTITEMS --------------------------
const savedMethodsListItems = useMemo(
() =>
pipe(
Expand All @@ -118,6 +122,7 @@ const WalletPaymentPickMethodScreen = () => {
),
[userWalletsPots]
);

const genericMethodsListItems = useMemo(
() =>
pipe(
Expand All @@ -131,49 +136,39 @@ const WalletPaymentPickMethodScreen = () => {
[paymentMethodsPot, paymentAmount]
);

// ------------------------ HANDLERS --------------------------

const handleSelectSavedMethod = (walletId: string) => {
setSelectedMethod({
kind: "saved",
walletId
});
};
//
// will be decommented once generic methods are implemented
// const handleSelectNotSavedMethod = (methodId: string) => {
// setSelectedMethod({
// kind: "generic",
// methodId
// });
// };

/* Will be decommented once generic methods are implemented
const handleSelectNotSavedMethod = (methodId: string) => {
setSelectedMethod({
kind: "generic",
methodId
});
};
*/

const handleContinue = () => {
// todo:: should handle the case where the user
// selects a non saved method
if (paymentAmount && selectedMethod?.kind === "saved") {
if (selectedMethod?.kind === "saved") {
pipe(
getSavedtMethodById(selectedMethod.walletId),
pot.toOption,
O.chainNullableK(
method => method && dispatch(walletPaymentPickPaymentMethod(method))
O.map(walletPaymentPickPaymentMethod),
O.map(dispatch),
O.map(() =>
navigation.navigate(WalletPaymentRoutes.WALLET_PAYMENT_MAIN, {
screen: WalletPaymentRoutes.WALLET_PAYMENT_PICK_PSP
})
)
);
navigation.navigate(WalletPaymentRoutes.WALLET_PAYMENT_MAIN, {
screen: WalletPaymentRoutes.WALLET_PAYMENT_PICK_PSP
});
}
};

// --------------------------- EFFECTS ------------------------------

useFocusEffect(
React.useCallback(() => {
dispatch(walletPaymentGetAllMethods.request());
dispatch(walletPaymentGetUserWallets.request());
}, [dispatch])
);

useEffect(() => {
if (!isLoading) {
const hasDisabledMethods =
Expand All @@ -184,9 +179,9 @@ const WalletPaymentPickMethodScreen = () => {
}
}, [isLoading, genericMethodsListItems, savedMethodsListItems]);

// -------------------------- RENDER --------------------------

const alertRef = React.useRef<View>(null);
if (!isLoading && savedMethodsListItems.length === 0) {
return <WalletPaymentMissingMethodsError />;
}

return (
<GradientScrollView
Expand All @@ -212,14 +207,12 @@ const WalletPaymentPickMethodScreen = () => {
<ListItemHeader
label={I18n.t("wallet.payment.methodSelection.yourMethods")}
/>

<RadioGroup<string>
type="radioListItem"
selectedItem={selectedMethod?.walletId}
items={isLoading ? loadingRadios : savedMethodsListItems}
onPress={handleSelectSavedMethod}
/>

{
// since there will be a transitory phase where this list is not
// returned, this is commented until the generic methods are implemented
Expand All @@ -241,8 +234,6 @@ const WalletPaymentPickMethodScreen = () => {
);
};

// ----------------------- UTILS -----------------------

const getIconWithFallback = (
brand?: string
): ComponentProps<typeof ListItemRadio>["startImage"] => {
Expand All @@ -260,17 +251,17 @@ const getIconWithFallback = (
)
);
};

const mapGenericToRadioItem = (
method: PaymentMethodResponse,
transactionAmount?: number
): RadioItem<string> => ({
id: method.id,
value: method.description,
disabled: isDisabled(method, transactionAmount),
disabled: isMethodDisabledForAmount(method, transactionAmount),
startImage: getIconWithFallback(method.asset)
});

// should never return void, but since this is a map function it's expectable
const mapSavedToRadioItem = (
method: WalletInfo
): RadioItem<string> | undefined => {
Expand Down Expand Up @@ -299,10 +290,7 @@ const mapSavedToRadioItem = (
}
};

// not sure if this ranges[0] thing is the right way, but
// it's pretty easy to add full traversal, even though it
// makes the code more complex
const isDisabled = (
const isMethodDisabledForAmount = (
method: PaymentMethodResponse,
transactionAmount?: number
): boolean =>
Expand All @@ -328,6 +316,4 @@ const loadingRadios: Array<RadioItem<string>> = Array.from(
})
);

// ------------- EXPORTS -------------

export { WalletPaymentPickMethodScreen };
22 changes: 17 additions & 5 deletions ts/features/walletV3/payment/store/selectors/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { createSelector } from "reselect";
import * as pot from "@pagopa/ts-commons/lib/pot";
import * as O from "fp-ts/lib/Option";
import { pipe } from "fp-ts/lib/function";
import { createSelector } from "reselect";
import { GlobalState } from "../../../../../store/reducers/types";

const selectWalletPayment = (state: GlobalState) =>
Expand All @@ -20,17 +22,27 @@ export const walletPaymentAllMethodsSelector = createSelector(
);
export const walletPaymentGenericMethodByIdSelector = createSelector(
walletPaymentAllMethodsSelector,
state => (id: string) =>
pot.map(state, methods => methods.find(_ => _.id === id))
methodsPot => (id: string) =>
pipe(
methodsPot,
pot.toOption,
O.chainNullableK(methods => methods.find(_ => _.id === id))
)
);

export const walletPaymentUserWalletsSelector = createSelector(
selectWalletPayment,
state => pot.map(state.userWallets, _ => _.wallets ?? [])
);

export const walletPaymentSavedMethodByIdSelector = createSelector(
walletPaymentUserWalletsSelector,
state => (id: string) =>
pot.map(state, methods => methods.find(_ => _.walletId === id))
methodsPot => (id: string) =>
pipe(
methodsPot,
pot.toOption,
O.chainNullableK(methods => methods.find(_ => _.walletId === id))
)
);

export const walletPaymentPickedPaymentMethodSelector = createSelector(
Expand Down

0 comments on commit 36e378f

Please sign in to comment.