From 930ba3e129a6bcee55ed2720c580466c628605d5 Mon Sep 17 00:00:00 2001 From: Gianluca Spada Date: Wed, 18 Dec 2024 14:06:16 +0100 Subject: [PATCH 01/15] chore: handle errors during wallet instance status fetch --- locales/en/index.yml | 3 ++ locales/it/index.yml | 3 ++ .../itwallet/common/store/selectors/index.ts | 4 ++- .../itwallet/common/utils/itwTypesUtils.ts | 13 ++++++- .../saga/checkWalletInstanceStateSaga.ts | 23 ++++++++---- .../walletInstance/store/actions/index.ts | 15 ++++++-- .../walletInstance/store/reducers/index.ts | 27 ++++++++++++-- .../components/WalletCardsContainer.tsx | 8 +++++ .../components/WalletCategoryFilterTabs.tsx | 8 ++--- .../WalletItwNotAvailableErrorBanner.tsx | 35 +++++++++++++++++++ ts/features/wallet/store/selectors/index.ts | 8 +++++ 11 files changed, 129 insertions(+), 18 deletions(-) create mode 100644 ts/features/wallet/components/WalletItwNotAvailableErrorBanner.tsx diff --git a/locales/en/index.yml b/locales/en/index.yml index 0351bdc8b29..08f3c53c397 100644 --- a/locales/en/index.yml +++ b/locales/en/index.yml @@ -3297,6 +3297,9 @@ features: claimNotAvailable: "Attributo non riconosciuto" claimLabelNotAvailable: "Etichetta attributo non presente" organizationName: "Nome ente non disponibile" + walletNotAvailable: + message: Abbiamo avuto un problema nel recuperare i tuoi documenti. + cta: Chiudi e riapri l'app per riprovare. verifiableCredentials: claims: uniqueId: "ID univoco" diff --git a/locales/it/index.yml b/locales/it/index.yml index 38adc05833c..c1fbbae8503 100644 --- a/locales/it/index.yml +++ b/locales/it/index.yml @@ -3297,6 +3297,9 @@ features: claimNotAvailable: "Attributo non riconosciuto" claimLabelNotAvailable: "Etichetta attributo non presente" organizationName: "Nome ente non disponibile" + walletNotAvailable: + message: Abbiamo avuto un problema nel recuperare i tuoi documenti. + cta: Chiudi e riapri l'app per riprovare. verifiableCredentials: claims: uniqueId: "ID univoco" diff --git a/ts/features/itwallet/common/store/selectors/index.ts b/ts/features/itwallet/common/store/selectors/index.ts index b4aa0bc14ff..b7a8f94e854 100644 --- a/ts/features/itwallet/common/store/selectors/index.ts +++ b/ts/features/itwallet/common/store/selectors/index.ts @@ -8,6 +8,7 @@ import { itwIsWalletEmptySelector } from "../../../credentials/store/selectors"; import { itwLifecycleIsValidSelector } from "../../../lifecycle/store/selectors"; +import { itwIsWalletInstanceStatusFailedSelector } from "../../../walletInstance/store/reducers"; import { itwIsFeedbackBannerHiddenSelector, itwIsDiscoveryBannerHiddenSelector @@ -49,7 +50,7 @@ export const itwShouldRenderFeedbackBannerSelector = (state: GlobalState) => /** * Returns if the wallet ready banner should be visible. The banner is visible if: - * - The Wallet has valid Wallet Instance and a valid eID + * - The Wallet has valid Wallet Instance with a known status, and a valid eID * - The eID is not expired * - The Wallet is empty * @param state the application global state @@ -57,5 +58,6 @@ export const itwShouldRenderFeedbackBannerSelector = (state: GlobalState) => */ export const itwShouldRenderWalletReadyBannerSelector = (state: GlobalState) => itwLifecycleIsValidSelector(state) && + !itwIsWalletInstanceStatusFailedSelector(state) && itwCredentialsEidStatusSelector(state) !== "jwtExpired" && itwIsWalletEmptySelector(state); diff --git a/ts/features/itwallet/common/utils/itwTypesUtils.ts b/ts/features/itwallet/common/utils/itwTypesUtils.ts index fe04304f663..8be73437e2f 100644 --- a/ts/features/itwallet/common/utils/itwTypesUtils.ts +++ b/ts/features/itwallet/common/utils/itwTypesUtils.ts @@ -1,4 +1,8 @@ -import { Credential, Trust } from "@pagopa/io-react-native-wallet"; +import { + Credential, + Trust, + WalletInstance +} from "@pagopa/io-react-native-wallet"; /** * Alias type for the return type of the start issuance flow operation. @@ -43,6 +47,13 @@ export type ParsedStatusAttestation = Awaited< ReturnType >["parsedStatusAttestation"]["payload"]; +/** + * Alias for the WalletInstanceStatus type + */ +export type WalletInstanceStatus = Awaited< + ReturnType +>; + export type StoredStatusAttestation = | { credentialStatus: "valid"; diff --git a/ts/features/itwallet/lifecycle/saga/checkWalletInstanceStateSaga.ts b/ts/features/itwallet/lifecycle/saga/checkWalletInstanceStateSaga.ts index 779a11a929e..f14e7424020 100644 --- a/ts/features/itwallet/lifecycle/saga/checkWalletInstanceStateSaga.ts +++ b/ts/features/itwallet/lifecycle/saga/checkWalletInstanceStateSaga.ts @@ -1,11 +1,13 @@ import * as O from "fp-ts/lib/Option"; import { call, put, select } from "typed-redux-saga/macro"; import { sessionTokenSelector } from "../../../../store/reducers/authentication"; +import { UNKNOWN_STATUS } from "../../walletInstance/store/reducers"; import { ReduxSagaEffect } from "../../../../types/utils"; import { assert } from "../../../../utils/assert"; import { getWalletInstanceStatus } from "../../common/utils/itwAttestationUtils"; import { ensureIntegrityServiceIsReady } from "../../common/utils/itwIntegrityUtils"; import { itwIntegrityKeyTagSelector } from "../../issuance/store/selectors"; +import { itwUpdateWalletInstanceStatus } from "../../walletInstance/store/actions"; import { itwLifecycleIsOperationalOrValid } from "../store/selectors"; import { itwIntegritySetServiceIsReady } from "../../issuance/store/actions"; import { handleWalletInstanceResetSaga } from "./handleWalletInstanceResetSaga"; @@ -14,14 +16,21 @@ export function* getStatusOrResetWalletInstance(integrityKeyTag: string) { const sessionToken = yield* select(sessionTokenSelector); assert(sessionToken, "Missing session token"); - const walletInstanceStatus = yield* call( - getWalletInstanceStatus, - integrityKeyTag, - sessionToken - ); + try { + const walletInstanceStatus = yield* call( + getWalletInstanceStatus, + integrityKeyTag, + sessionToken + ); + + if (walletInstanceStatus.is_revoked) { + yield* call(handleWalletInstanceResetSaga); + } - if (walletInstanceStatus.is_revoked) { - yield* call(handleWalletInstanceResetSaga); + // Update wallet instance status + yield* put(itwUpdateWalletInstanceStatus(walletInstanceStatus)); + } catch (_) { + yield* put(itwUpdateWalletInstanceStatus(UNKNOWN_STATUS)); } } diff --git a/ts/features/itwallet/walletInstance/store/actions/index.ts b/ts/features/itwallet/walletInstance/store/actions/index.ts index 3d0c05b05c2..6d53f5420b2 100644 --- a/ts/features/itwallet/walletInstance/store/actions/index.ts +++ b/ts/features/itwallet/walletInstance/store/actions/index.ts @@ -1,4 +1,6 @@ import { ActionType, createStandardAction } from "typesafe-actions"; +import { WalletInstanceStatus } from "../../../common/utils/itwTypesUtils"; +import { UnknownStatus } from "../reducers"; /** * This action stores the Wallet Instance Attestation @@ -7,6 +9,13 @@ export const itwWalletInstanceAttestationStore = createStandardAction( "ITW_WALLET_INSTANCE_ATTESTATION_STORE" )(); -export type ItwWalletInstanceActions = ActionType< - typeof itwWalletInstanceAttestationStore ->; +/** + * This action update the Wallet Instance Status + */ +export const itwUpdateWalletInstanceStatus = createStandardAction( + "ITW_WALLET_INSTANCE_STATUS_UPDATE" +)(); + +export type ItwWalletInstanceActions = + | ActionType + | ActionType; diff --git a/ts/features/itwallet/walletInstance/store/reducers/index.ts b/ts/features/itwallet/walletInstance/store/reducers/index.ts index 2494833ff35..d65ef896300 100644 --- a/ts/features/itwallet/walletInstance/store/reducers/index.ts +++ b/ts/features/itwallet/walletInstance/store/reducers/index.ts @@ -8,14 +8,23 @@ import { GlobalState } from "../../../../../store/reducers/types"; import itwCreateSecureStorage from "../../../common/store/storages/itwSecureStorage"; import { isWalletInstanceAttestationValid } from "../../../common/utils/itwAttestationUtils"; import { itwLifecycleStoresReset } from "../../../lifecycle/store/actions"; -import { itwWalletInstanceAttestationStore } from "../actions"; +import { + itwUpdateWalletInstanceStatus, + itwWalletInstanceAttestationStore +} from "../actions"; +import { WalletInstanceStatus } from "../../../common/utils/itwTypesUtils"; + +export const UNKNOWN_STATUS = "unknown"; +export type UnknownStatus = typeof UNKNOWN_STATUS; export type ItwWalletInstanceState = { attestation: string | undefined; + status: WalletInstanceStatus | UnknownStatus | undefined; }; export const itwWalletInstanceInitialState: ItwWalletInstanceState = { - attestation: undefined + attestation: undefined, + status: undefined }; const CURRENT_REDUX_ITW_WALLET_INSTANCE_STORE_VERSION = -1; @@ -27,10 +36,17 @@ const reducer = ( switch (action.type) { case getType(itwWalletInstanceAttestationStore): { return { + ...state, attestation: action.payload }; } + case getType(itwUpdateWalletInstanceStatus): + return { + ...state, + status: action.payload + }; + case getType(itwLifecycleStoresReset): return { ...itwWalletInstanceInitialState }; @@ -62,4 +78,11 @@ export const itwIsWalletInstanceAttestationValidSelector = createSelector( ) ); +/** + * Returns true when it was not possible to retrieve the wallet instance status, + * for instance because of unexpected errors. + */ +export const itwIsWalletInstanceStatusUnknownSelector = (state: GlobalState) => + state.features.itWallet.walletInstance.status === UNKNOWN_STATUS; + export default persistedReducer; diff --git a/ts/features/wallet/components/WalletCardsContainer.tsx b/ts/features/wallet/components/WalletCardsContainer.tsx index 394dc54ee19..e42de17906b 100644 --- a/ts/features/wallet/components/WalletCardsContainer.tsx +++ b/ts/features/wallet/components/WalletCardsContainer.tsx @@ -27,11 +27,13 @@ import { selectWalletOtherCards, shouldRenderWalletEmptyStateSelector } from "../store/selectors"; +import { itwIsWalletInstanceStatusUnknownSelector } from "../../itwallet/walletInstance/store/reducers"; import { WalletCardCategoryFilter } from "../types"; import { WalletCardsCategoryContainer } from "./WalletCardsCategoryContainer"; import { WalletCardsCategoryRetryErrorBanner } from "./WalletCardsCategoryRetryErrorBanner"; import { WalletCardSkeleton } from "./WalletCardSkeleton"; import { WalletEmptyScreenContent } from "./WalletEmptyScreenContent"; +import { WalletItwNotAvailableErrorBanner } from "./WalletItwNotAvailableErrorBanner"; const EID_INFO_BOTTOM_PADDING = 128; @@ -100,6 +102,7 @@ const ItwWalletCardsContainer = () => { const isItwValid = useIOSelector(itwLifecycleIsValidSelector); const isItwEnabled = useIOSelector(isItwEnabledSelector); const eidStatus = useIOSelector(itwCredentialsEidStatusSelector); + const unknownStatus = useIOSelector(itwIsWalletInstanceStatusUnknownSelector); const isEidExpired = eidStatus === "jwtExpired"; @@ -149,6 +152,11 @@ const ItwWalletCardsContainer = () => { return null; } + // When it's not possible to retrieve the wallet instance status from the backend we disable the wallet + if (unknownStatus) { + return ; + } + return ( <> { const dispatch = useIODispatch(); const selectedCategory = useIOSelector(selectWalletCategoryFilter); - const categories = useIOSelector(selectWalletCategories); + const shouldRender = useIOSelector(shouldRenderCategoryFiltersSelector); const selectedIndex = React.useMemo( () => @@ -34,7 +34,7 @@ const WalletCategoryFilterTabs = () => { [selectedCategory] ); - if (categories.size <= 1) { + if (!shouldRender) { return null; } diff --git a/ts/features/wallet/components/WalletItwNotAvailableErrorBanner.tsx b/ts/features/wallet/components/WalletItwNotAvailableErrorBanner.tsx new file mode 100644 index 00000000000..24703db5e7a --- /dev/null +++ b/ts/features/wallet/components/WalletItwNotAvailableErrorBanner.tsx @@ -0,0 +1,35 @@ +import React from "react"; +import { StyleSheet, View } from "react-native"; +import { + BodySmall, + IOAlertSpacing, + IOColors, + Icon +} from "@pagopa/io-app-design-system"; +import I18n from "../../../i18n"; + +export const WalletItwNotAvailableErrorBanner = () => ( + + + + {I18n.t("features.itWallet.generic.walletNotAvailable.message")} + + + {I18n.t("features.itWallet.generic.walletNotAvailable.cta")} + + +); + +const styles = StyleSheet.create({ + bannerContainer: { + padding: IOAlertSpacing[1], + marginVertical: 16, + backgroundColor: IOColors["grey-50"], + borderRadius: 8, + alignItems: "center", + gap: 8 + }, + textCenter: { + textAlign: "center" + } +}); diff --git a/ts/features/wallet/store/selectors/index.ts b/ts/features/wallet/store/selectors/index.ts index 0fc70d8a3fd..620ded04bc0 100644 --- a/ts/features/wallet/store/selectors/index.ts +++ b/ts/features/wallet/store/selectors/index.ts @@ -7,6 +7,7 @@ import { itwLifecycleIsValidSelector } from "../../../itwallet/lifecycle/store/s import { paymentsWalletUserMethodsSelector } from "../../../payments/wallet/store/selectors"; import { WalletCard, walletCardCategories } from "../../types"; import { isSomeLoadingOrSomeUpdating } from "../../../../utils/pot"; +import { itwIsWalletInstanceStatusUnknownSelector } from "../../../itwallet/walletInstance/store/reducers"; const selectWalletFeature = (state: GlobalState) => state.features.wallet; @@ -127,3 +128,10 @@ export const isWalletScreenRefreshingSelector = (state: GlobalState) => isSomeLoadingOrSomeUpdating(paymentsWalletUserMethodsSelector(state)) || isSomeLoadingOrSomeUpdating(idPayWalletInitiativeListSelector(state)) || isSomeLoadingOrSomeUpdating(cgnDetailSelector(state)); + +/** + * Selector that handles the visibility of the category filter tabs shown on the wallet screen. + */ +export const shouldRenderCategoryFiltersSelector = (state: GlobalState) => + selectWalletCategories(state).size > 1 && + !itwIsWalletInstanceStatusUnknownSelector(state); From 156eb45faa4821d7e4d0e3a2071c26c0c601ff2d Mon Sep 17 00:00:00 2001 From: Gianluca Spada Date: Wed, 18 Dec 2024 14:07:59 +0100 Subject: [PATCH 02/15] chore: fix wrong import --- ts/features/itwallet/common/store/selectors/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ts/features/itwallet/common/store/selectors/index.ts b/ts/features/itwallet/common/store/selectors/index.ts index b7a8f94e854..07e17fff0a7 100644 --- a/ts/features/itwallet/common/store/selectors/index.ts +++ b/ts/features/itwallet/common/store/selectors/index.ts @@ -8,7 +8,7 @@ import { itwIsWalletEmptySelector } from "../../../credentials/store/selectors"; import { itwLifecycleIsValidSelector } from "../../../lifecycle/store/selectors"; -import { itwIsWalletInstanceStatusFailedSelector } from "../../../walletInstance/store/reducers"; +import { itwIsWalletInstanceStatusUnknownSelector } from "../../../walletInstance/store/reducers"; import { itwIsFeedbackBannerHiddenSelector, itwIsDiscoveryBannerHiddenSelector @@ -58,6 +58,6 @@ export const itwShouldRenderFeedbackBannerSelector = (state: GlobalState) => */ export const itwShouldRenderWalletReadyBannerSelector = (state: GlobalState) => itwLifecycleIsValidSelector(state) && - !itwIsWalletInstanceStatusFailedSelector(state) && + !itwIsWalletInstanceStatusUnknownSelector(state) && itwCredentialsEidStatusSelector(state) !== "jwtExpired" && itwIsWalletEmptySelector(state); From 52a977b9015cb93e92fe270f2c1edd942d43c2de Mon Sep 17 00:00:00 2001 From: Gianluca Spada Date: Wed, 18 Dec 2024 15:33:53 +0100 Subject: [PATCH 03/15] chore: test shouldRenderCategoryFiltersSelector --- .../store/selectors/__tests__/index.test.ts | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/ts/features/wallet/store/selectors/__tests__/index.test.ts b/ts/features/wallet/store/selectors/__tests__/index.test.ts index bf524fdbf2d..3452ae949f2 100644 --- a/ts/features/wallet/store/selectors/__tests__/index.test.ts +++ b/ts/features/wallet/store/selectors/__tests__/index.test.ts @@ -5,6 +5,7 @@ import { isWalletEmptySelector, selectWalletCards, selectWalletCategories, + shouldRenderCategoryFiltersSelector, shouldRenderWalletEmptyStateSelector } from ".."; import { applicationChangeState } from "../../../../../store/actions/application"; @@ -16,6 +17,7 @@ import { import { ItwLifecycleState } from "../../../../itwallet/lifecycle/store/reducers"; import * as itwLifecycleSelectors from "../../../../itwallet/lifecycle/store/selectors"; import { WalletCardsState } from "../../reducers/cards"; +import { UNKNOWN_STATUS } from "../../../../itwallet/walletInstance/store/reducers"; const T_CARDS: WalletCardsState = { "1": { @@ -220,3 +222,34 @@ describe("shouldRenderWalletEmptyStateSelector", () => { } ); }); + +describe("shouldRenderCategoryFiltersSelector", () => { + it.each` + walletCards | walletInstanceStatus | expected + ${[{ category: "itw" }]} | ${undefined} | ${false} + ${[{ category: "itw" }]} | ${UNKNOWN_STATUS} | ${false} + ${[{ category: "itw" }, { category: "cgn" }]} | ${undefined} | ${true} + ${[{ category: "itw" }, { category: "cgn" }]} | ${UNKNOWN_STATUS} | ${false} + `( + "should return $expected when walletCards are $walletCards.length and walletInstanceStatus is $walletInstanceStatus", + ({ walletCards, walletInstanceStatus, expected }) => { + const globalState = appReducer( + undefined, + applicationChangeState("active") + ); + + const shouldRenderCategoryFilters = shouldRenderCategoryFiltersSelector( + _.merge( + globalState, + _.set( + globalState, + "features.itWallet.walletInstance.status", + walletInstanceStatus + ), + _.set(globalState, "features.wallet.cards", walletCards) + ) + ); + expect(shouldRenderCategoryFilters).toBe(expected); + } + ); +}); From 3fc927e308a4ac88909cd04892472716cc19b868 Mon Sep 17 00:00:00 2001 From: Gianluca Spada Date: Wed, 18 Dec 2024 17:03:12 +0100 Subject: [PATCH 04/15] refactor: move wallet not available component to itWallet feature --- .../ItwWalletNotAvailableBanner.tsx | 47 +++++++++++++++++++ .../components/WalletCardsContainer.tsx | 23 +++++---- .../WalletItwNotAvailableErrorBanner.tsx | 35 -------------- 3 files changed, 61 insertions(+), 44 deletions(-) create mode 100644 ts/features/itwallet/common/components/ItwWalletNotAvailableBanner.tsx delete mode 100644 ts/features/wallet/components/WalletItwNotAvailableErrorBanner.tsx diff --git a/ts/features/itwallet/common/components/ItwWalletNotAvailableBanner.tsx b/ts/features/itwallet/common/components/ItwWalletNotAvailableBanner.tsx new file mode 100644 index 00000000000..dac155292fe --- /dev/null +++ b/ts/features/itwallet/common/components/ItwWalletNotAvailableBanner.tsx @@ -0,0 +1,47 @@ +import React from "react"; +import { StyleSheet, View } from "react-native"; +import { + BodySmall, + IOAlertSpacing, + IOColors, + Icon +} from "@pagopa/io-app-design-system"; +import I18n from "../../../../i18n"; +import { useIOSelector } from "../../../../store/hooks"; +import { itwIsWalletInstanceStatusUnknownSelector } from "../../walletInstance/store/reducers"; + +export const ItwWalletNotAvailableBanner = () => { + const isWalletInstanceStatusUnknown = useIOSelector( + itwIsWalletInstanceStatusUnknownSelector + ); + + if (!isWalletInstanceStatusUnknown) { + return null; + } + + return ( + + + + {I18n.t("features.itWallet.generic.walletNotAvailable.message")} + + + {I18n.t("features.itWallet.generic.walletNotAvailable.cta")} + + + ); +}; + +const styles = StyleSheet.create({ + bannerContainer: { + padding: IOAlertSpacing[1], + marginVertical: 16, + backgroundColor: IOColors["grey-50"], + borderRadius: 8, + alignItems: "center", + gap: 8 + }, + textCenter: { + textAlign: "center" + } +}); diff --git a/ts/features/wallet/components/WalletCardsContainer.tsx b/ts/features/wallet/components/WalletCardsContainer.tsx index e42de17906b..84d8601c587 100644 --- a/ts/features/wallet/components/WalletCardsContainer.tsx +++ b/ts/features/wallet/components/WalletCardsContainer.tsx @@ -16,6 +16,7 @@ import { import { ItwEidLifecycleAlert } from "../../itwallet/common/components/ItwEidLifecycleAlert"; import { ItwFeedbackBanner } from "../../itwallet/common/components/ItwFeedbackBanner"; import { ItwWalletReadyBanner } from "../../itwallet/common/components/ItwWalletReadyBanner"; +import { ItwWalletNotAvailableBanner } from "../../itwallet/common/components/ItwWalletNotAvailableBanner"; import { itwCredentialsEidStatusSelector } from "../../itwallet/credentials/store/selectors"; import { itwLifecycleIsValidSelector } from "../../itwallet/lifecycle/store/selectors"; import { @@ -33,7 +34,6 @@ import { WalletCardsCategoryContainer } from "./WalletCardsCategoryContainer"; import { WalletCardsCategoryRetryErrorBanner } from "./WalletCardsCategoryRetryErrorBanner"; import { WalletCardSkeleton } from "./WalletCardSkeleton"; import { WalletEmptyScreenContent } from "./WalletEmptyScreenContent"; -import { WalletItwNotAvailableErrorBanner } from "./WalletItwNotAvailableErrorBanner"; const EID_INFO_BOTTOM_PADDING = 128; @@ -49,6 +49,9 @@ const WalletCardsContainer = () => { const shouldRenderEmptyState = useIOSelector( shouldRenderWalletEmptyStateSelector ); + const isWalletInstanceStatusUnknown = useIOSelector( + itwIsWalletInstanceStatusUnknownSelector + ); // Loading state is only displayed if there is the initial loading and there are no cards or // placeholders in the wallet @@ -71,17 +74,25 @@ const WalletCardsContainer = () => { } return ( - {shouldRenderCategory("itw") && } + {!isWalletInstanceStatusUnknown && shouldRenderCategory("itw") && ( + + )} {shouldRenderCategory("other") && } ); - }, [shouldRenderEmptyState, shouldRenderCategory, shouldRenderLoadingState]); + }, [ + shouldRenderEmptyState, + shouldRenderCategory, + shouldRenderLoadingState, + isWalletInstanceStatusUnknown + ]); return ( + {walletContent} @@ -102,7 +113,6 @@ const ItwWalletCardsContainer = () => { const isItwValid = useIOSelector(itwLifecycleIsValidSelector); const isItwEnabled = useIOSelector(isItwEnabledSelector); const eidStatus = useIOSelector(itwCredentialsEidStatusSelector); - const unknownStatus = useIOSelector(itwIsWalletInstanceStatusUnknownSelector); const isEidExpired = eidStatus === "jwtExpired"; @@ -152,11 +162,6 @@ const ItwWalletCardsContainer = () => { return null; } - // When it's not possible to retrieve the wallet instance status from the backend we disable the wallet - if (unknownStatus) { - return ; - } - return ( <> ( - - - - {I18n.t("features.itWallet.generic.walletNotAvailable.message")} - - - {I18n.t("features.itWallet.generic.walletNotAvailable.cta")} - - -); - -const styles = StyleSheet.create({ - bannerContainer: { - padding: IOAlertSpacing[1], - marginVertical: 16, - backgroundColor: IOColors["grey-50"], - borderRadius: 8, - alignItems: "center", - gap: 8 - }, - textCenter: { - textAlign: "center" - } -}); From d9c8551bf655ea92e06322f69e57aa9e7a5a5a9c Mon Sep 17 00:00:00 2001 From: Gianluca Spada Date: Wed, 18 Dec 2024 17:23:33 +0100 Subject: [PATCH 05/15] chore: update tests --- .../__tests__/WalletCategoryFilterTabs.test.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ts/features/wallet/components/__tests__/WalletCategoryFilterTabs.test.tsx b/ts/features/wallet/components/__tests__/WalletCategoryFilterTabs.test.tsx index 06452f7ab0c..abbb5f2824a 100644 --- a/ts/features/wallet/components/__tests__/WalletCategoryFilterTabs.test.tsx +++ b/ts/features/wallet/components/__tests__/WalletCategoryFilterTabs.test.tsx @@ -12,25 +12,25 @@ import * as selectors from "../../store/selectors"; import { WalletCategoryFilterTabs } from "../WalletCategoryFilterTabs"; describe("WalletCategoryFilterTabs", () => { - it("should not render the component if there is only one cards category in the wallet", () => { + it("should not render the component when its rendering conditions are not met", () => { jest .spyOn(selectors, "selectWalletCategoryFilter") .mockImplementation(() => undefined); jest - .spyOn(selectors, "selectWalletCategories") - .mockImplementation(() => new Set(["itw"])); + .spyOn(selectors, "shouldRenderCategoryFiltersSelector") + .mockImplementation(() => false); const { queryByTestId } = renderComponent(); expect(queryByTestId("CategoryTabsContainerTestID")).toBeNull(); }); - it("should render the component if there is more than one cards category in the wallet", () => { + it("should render the component when its rendering conditions are met", () => { jest .spyOn(selectors, "selectWalletCategoryFilter") .mockImplementation(() => undefined); jest - .spyOn(selectors, "selectWalletCategories") - .mockImplementation(() => new Set(["itw", "other"])); + .spyOn(selectors, "shouldRenderCategoryFiltersSelector") + .mockImplementation(() => true); const { queryByTestId } = renderComponent(); expect(queryByTestId("CategoryTabsContainerTestID")).not.toBeNull(); @@ -45,8 +45,8 @@ describe("WalletCategoryFilterTabs", () => { .spyOn(selectors, "selectWalletCategoryFilter") .mockImplementation(() => undefined); jest - .spyOn(selectors, "selectWalletCategories") - .mockImplementation(() => new Set(["itw", "other"])); + .spyOn(selectors, "shouldRenderCategoryFiltersSelector") + .mockImplementation(() => true); const { getByTestId } = renderComponent(); const itwTab = getByTestId("CategoryTabTestID-itw"); From fdab420a064e385f8b8bb3b9b53186220d504fae Mon Sep 17 00:00:00 2001 From: Gianluca Spada Date: Wed, 18 Dec 2024 17:39:49 +0100 Subject: [PATCH 06/15] chore: add comments --- .../common/components/ItwWalletNotAvailableBanner.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ts/features/itwallet/common/components/ItwWalletNotAvailableBanner.tsx b/ts/features/itwallet/common/components/ItwWalletNotAvailableBanner.tsx index dac155292fe..71511bec7ca 100644 --- a/ts/features/itwallet/common/components/ItwWalletNotAvailableBanner.tsx +++ b/ts/features/itwallet/common/components/ItwWalletNotAvailableBanner.tsx @@ -10,6 +10,10 @@ import I18n from "../../../../i18n"; import { useIOSelector } from "../../../../store/hooks"; import { itwIsWalletInstanceStatusUnknownSelector } from "../../walletInstance/store/reducers"; +/** + * Component shown when it is not possible to retrieve the wallet instance status + * from the backend. In this scenario the wallet instance status is unknown. + */ export const ItwWalletNotAvailableBanner = () => { const isWalletInstanceStatusUnknown = useIOSelector( itwIsWalletInstanceStatusUnknownSelector From 7a83669e84d6eaa2eb272e4b0ec8504428149063 Mon Sep 17 00:00:00 2001 From: Gianluca Spada Date: Wed, 18 Dec 2024 18:02:04 +0100 Subject: [PATCH 07/15] refactor: rename unknown status to failure status --- .../common/components/ItwWalletNotAvailableBanner.tsx | 10 +++++----- ts/features/itwallet/common/store/selectors/index.ts | 4 ++-- .../lifecycle/saga/checkWalletInstanceStateSaga.ts | 4 ++-- .../onboarding/screens/WalletCardOnboardingScreen.tsx | 7 ++++++- .../itwallet/walletInstance/store/actions/index.ts | 4 ++-- .../itwallet/walletInstance/store/reducers/index.ts | 10 +++++----- ts/features/wallet/components/WalletCardsContainer.tsx | 10 +++++----- .../wallet/store/selectors/__tests__/index.test.ts | 6 +++--- ts/features/wallet/store/selectors/index.ts | 4 ++-- 9 files changed, 32 insertions(+), 27 deletions(-) diff --git a/ts/features/itwallet/common/components/ItwWalletNotAvailableBanner.tsx b/ts/features/itwallet/common/components/ItwWalletNotAvailableBanner.tsx index 71511bec7ca..04d11052a0a 100644 --- a/ts/features/itwallet/common/components/ItwWalletNotAvailableBanner.tsx +++ b/ts/features/itwallet/common/components/ItwWalletNotAvailableBanner.tsx @@ -8,18 +8,18 @@ import { } from "@pagopa/io-app-design-system"; import I18n from "../../../../i18n"; import { useIOSelector } from "../../../../store/hooks"; -import { itwIsWalletInstanceStatusUnknownSelector } from "../../walletInstance/store/reducers"; +import { itwIsWalletInstanceStatusFailureSelector } from "../../walletInstance/store/reducers"; /** * Component shown when it is not possible to retrieve the wallet instance status - * from the backend. In this scenario the wallet instance status is unknown. + * from the backend, because of unexpected errors. */ export const ItwWalletNotAvailableBanner = () => { - const isWalletInstanceStatusUnknown = useIOSelector( - itwIsWalletInstanceStatusUnknownSelector + const isWalletInstanceStatusFailed = useIOSelector( + itwIsWalletInstanceStatusFailureSelector ); - if (!isWalletInstanceStatusUnknown) { + if (!isWalletInstanceStatusFailed) { return null; } diff --git a/ts/features/itwallet/common/store/selectors/index.ts b/ts/features/itwallet/common/store/selectors/index.ts index 07e17fff0a7..53245186302 100644 --- a/ts/features/itwallet/common/store/selectors/index.ts +++ b/ts/features/itwallet/common/store/selectors/index.ts @@ -8,7 +8,7 @@ import { itwIsWalletEmptySelector } from "../../../credentials/store/selectors"; import { itwLifecycleIsValidSelector } from "../../../lifecycle/store/selectors"; -import { itwIsWalletInstanceStatusUnknownSelector } from "../../../walletInstance/store/reducers"; +import { itwIsWalletInstanceStatusFailureSelector } from "../../../walletInstance/store/reducers"; import { itwIsFeedbackBannerHiddenSelector, itwIsDiscoveryBannerHiddenSelector @@ -58,6 +58,6 @@ export const itwShouldRenderFeedbackBannerSelector = (state: GlobalState) => */ export const itwShouldRenderWalletReadyBannerSelector = (state: GlobalState) => itwLifecycleIsValidSelector(state) && - !itwIsWalletInstanceStatusUnknownSelector(state) && + !itwIsWalletInstanceStatusFailureSelector(state) && itwCredentialsEidStatusSelector(state) !== "jwtExpired" && itwIsWalletEmptySelector(state); diff --git a/ts/features/itwallet/lifecycle/saga/checkWalletInstanceStateSaga.ts b/ts/features/itwallet/lifecycle/saga/checkWalletInstanceStateSaga.ts index f14e7424020..b75d230c6c0 100644 --- a/ts/features/itwallet/lifecycle/saga/checkWalletInstanceStateSaga.ts +++ b/ts/features/itwallet/lifecycle/saga/checkWalletInstanceStateSaga.ts @@ -1,7 +1,7 @@ import * as O from "fp-ts/lib/Option"; import { call, put, select } from "typed-redux-saga/macro"; import { sessionTokenSelector } from "../../../../store/reducers/authentication"; -import { UNKNOWN_STATUS } from "../../walletInstance/store/reducers"; +import { FAILURE_STATUS } from "../../walletInstance/store/reducers"; import { ReduxSagaEffect } from "../../../../types/utils"; import { assert } from "../../../../utils/assert"; import { getWalletInstanceStatus } from "../../common/utils/itwAttestationUtils"; @@ -30,7 +30,7 @@ export function* getStatusOrResetWalletInstance(integrityKeyTag: string) { // Update wallet instance status yield* put(itwUpdateWalletInstanceStatus(walletInstanceStatus)); } catch (_) { - yield* put(itwUpdateWalletInstanceStatus(UNKNOWN_STATUS)); + yield* put(itwUpdateWalletInstanceStatus(FAILURE_STATUS)); } } diff --git a/ts/features/itwallet/onboarding/screens/WalletCardOnboardingScreen.tsx b/ts/features/itwallet/onboarding/screens/WalletCardOnboardingScreen.tsx index 0efb8a6768f..e6c9c458877 100644 --- a/ts/features/itwallet/onboarding/screens/WalletCardOnboardingScreen.tsx +++ b/ts/features/itwallet/onboarding/screens/WalletCardOnboardingScreen.tsx @@ -41,6 +41,7 @@ import { } from "../../machine/credential/selectors"; import { ItwCredentialIssuanceMachineContext } from "../../machine/provider"; import { ItwOnboardingModuleCredential } from "../components/ItwOnboardingModuleCredential"; +import { itwIsWalletInstanceStatusUnknownSelector } from "../../walletInstance/store/reducers"; // List of available credentials to show to the user const availableCredentials = [ @@ -57,15 +58,19 @@ const activeBadge: Badge = { const WalletCardOnboardingScreen = () => { const isItwValid = useIOSelector(itwLifecycleIsValidSelector); const isItwEnabled = useIOSelector(isItwEnabledSelector); + const isWalletInstanceStatusUnknown = useIOSelector( + itwIsWalletInstanceStatusUnknownSelector + ); useFocusEffect(trackShowCredentialsList); const isItwSectionVisible = React.useMemo( // IT Wallet credential catalog should be visible if () => + !isWalletInstanceStatusUnknown && isItwValid && // An eID has ben obtained and wallet is valid isItwEnabled, // Remote FF is enabled - [isItwValid, isItwEnabled] + [isItwValid, isItwEnabled, isWalletInstanceStatusUnknown] ); return ( diff --git a/ts/features/itwallet/walletInstance/store/actions/index.ts b/ts/features/itwallet/walletInstance/store/actions/index.ts index 6d53f5420b2..19a6a585d0f 100644 --- a/ts/features/itwallet/walletInstance/store/actions/index.ts +++ b/ts/features/itwallet/walletInstance/store/actions/index.ts @@ -1,6 +1,6 @@ import { ActionType, createStandardAction } from "typesafe-actions"; import { WalletInstanceStatus } from "../../../common/utils/itwTypesUtils"; -import { UnknownStatus } from "../reducers"; +import { FailureStatus } from "../reducers"; /** * This action stores the Wallet Instance Attestation @@ -14,7 +14,7 @@ export const itwWalletInstanceAttestationStore = createStandardAction( */ export const itwUpdateWalletInstanceStatus = createStandardAction( "ITW_WALLET_INSTANCE_STATUS_UPDATE" -)(); +)(); export type ItwWalletInstanceActions = | ActionType diff --git a/ts/features/itwallet/walletInstance/store/reducers/index.ts b/ts/features/itwallet/walletInstance/store/reducers/index.ts index d65ef896300..15b351ad6b9 100644 --- a/ts/features/itwallet/walletInstance/store/reducers/index.ts +++ b/ts/features/itwallet/walletInstance/store/reducers/index.ts @@ -14,12 +14,12 @@ import { } from "../actions"; import { WalletInstanceStatus } from "../../../common/utils/itwTypesUtils"; -export const UNKNOWN_STATUS = "unknown"; -export type UnknownStatus = typeof UNKNOWN_STATUS; +export const FAILURE_STATUS = "failure"; +export type FailureStatus = typeof FAILURE_STATUS; export type ItwWalletInstanceState = { attestation: string | undefined; - status: WalletInstanceStatus | UnknownStatus | undefined; + status: WalletInstanceStatus | FailureStatus | undefined; }; export const itwWalletInstanceInitialState: ItwWalletInstanceState = { @@ -82,7 +82,7 @@ export const itwIsWalletInstanceAttestationValidSelector = createSelector( * Returns true when it was not possible to retrieve the wallet instance status, * for instance because of unexpected errors. */ -export const itwIsWalletInstanceStatusUnknownSelector = (state: GlobalState) => - state.features.itWallet.walletInstance.status === UNKNOWN_STATUS; +export const itwIsWalletInstanceStatusFailureSelector = (state: GlobalState) => + state.features.itWallet.walletInstance.status === FAILURE_STATUS; export default persistedReducer; diff --git a/ts/features/wallet/components/WalletCardsContainer.tsx b/ts/features/wallet/components/WalletCardsContainer.tsx index 84d8601c587..6efff374d2c 100644 --- a/ts/features/wallet/components/WalletCardsContainer.tsx +++ b/ts/features/wallet/components/WalletCardsContainer.tsx @@ -28,7 +28,7 @@ import { selectWalletOtherCards, shouldRenderWalletEmptyStateSelector } from "../store/selectors"; -import { itwIsWalletInstanceStatusUnknownSelector } from "../../itwallet/walletInstance/store/reducers"; +import { itwIsWalletInstanceStatusFailureSelector } from "../../itwallet/walletInstance/store/reducers"; import { WalletCardCategoryFilter } from "../types"; import { WalletCardsCategoryContainer } from "./WalletCardsCategoryContainer"; import { WalletCardsCategoryRetryErrorBanner } from "./WalletCardsCategoryRetryErrorBanner"; @@ -49,8 +49,8 @@ const WalletCardsContainer = () => { const shouldRenderEmptyState = useIOSelector( shouldRenderWalletEmptyStateSelector ); - const isWalletInstanceStatusUnknown = useIOSelector( - itwIsWalletInstanceStatusUnknownSelector + const isWalletInstanceStatusFailure = useIOSelector( + itwIsWalletInstanceStatusFailureSelector ); // Loading state is only displayed if there is the initial loading and there are no cards or @@ -74,7 +74,7 @@ const WalletCardsContainer = () => { } return ( - {!isWalletInstanceStatusUnknown && shouldRenderCategory("itw") && ( + {!isWalletInstanceStatusFailure && shouldRenderCategory("itw") && ( )} {shouldRenderCategory("other") && } @@ -84,7 +84,7 @@ const WalletCardsContainer = () => { shouldRenderEmptyState, shouldRenderCategory, shouldRenderLoadingState, - isWalletInstanceStatusUnknown + isWalletInstanceStatusFailure ]); return ( diff --git a/ts/features/wallet/store/selectors/__tests__/index.test.ts b/ts/features/wallet/store/selectors/__tests__/index.test.ts index 3452ae949f2..a8833b86241 100644 --- a/ts/features/wallet/store/selectors/__tests__/index.test.ts +++ b/ts/features/wallet/store/selectors/__tests__/index.test.ts @@ -17,7 +17,7 @@ import { import { ItwLifecycleState } from "../../../../itwallet/lifecycle/store/reducers"; import * as itwLifecycleSelectors from "../../../../itwallet/lifecycle/store/selectors"; import { WalletCardsState } from "../../reducers/cards"; -import { UNKNOWN_STATUS } from "../../../../itwallet/walletInstance/store/reducers"; +import { FAILURE_STATUS } from "../../../../itwallet/walletInstance/store/reducers"; const T_CARDS: WalletCardsState = { "1": { @@ -227,9 +227,9 @@ describe("shouldRenderCategoryFiltersSelector", () => { it.each` walletCards | walletInstanceStatus | expected ${[{ category: "itw" }]} | ${undefined} | ${false} - ${[{ category: "itw" }]} | ${UNKNOWN_STATUS} | ${false} + ${[{ category: "itw" }]} | ${FAILURE_STATUS} | ${false} ${[{ category: "itw" }, { category: "cgn" }]} | ${undefined} | ${true} - ${[{ category: "itw" }, { category: "cgn" }]} | ${UNKNOWN_STATUS} | ${false} + ${[{ category: "itw" }, { category: "cgn" }]} | ${FAILURE_STATUS} | ${false} `( "should return $expected when walletCards are $walletCards.length and walletInstanceStatus is $walletInstanceStatus", ({ walletCards, walletInstanceStatus, expected }) => { diff --git a/ts/features/wallet/store/selectors/index.ts b/ts/features/wallet/store/selectors/index.ts index 620ded04bc0..79bfe968fcf 100644 --- a/ts/features/wallet/store/selectors/index.ts +++ b/ts/features/wallet/store/selectors/index.ts @@ -7,7 +7,7 @@ import { itwLifecycleIsValidSelector } from "../../../itwallet/lifecycle/store/s import { paymentsWalletUserMethodsSelector } from "../../../payments/wallet/store/selectors"; import { WalletCard, walletCardCategories } from "../../types"; import { isSomeLoadingOrSomeUpdating } from "../../../../utils/pot"; -import { itwIsWalletInstanceStatusUnknownSelector } from "../../../itwallet/walletInstance/store/reducers"; +import { itwIsWalletInstanceStatusFailureSelector } from "../../../itwallet/walletInstance/store/reducers"; const selectWalletFeature = (state: GlobalState) => state.features.wallet; @@ -134,4 +134,4 @@ export const isWalletScreenRefreshingSelector = (state: GlobalState) => */ export const shouldRenderCategoryFiltersSelector = (state: GlobalState) => selectWalletCategories(state).size > 1 && - !itwIsWalletInstanceStatusUnknownSelector(state); + !itwIsWalletInstanceStatusFailureSelector(state); From d4f8ffb3c5a032fe7d28f6691169dfca94879af3 Mon Sep 17 00:00:00 2001 From: Gianluca Spada Date: Wed, 18 Dec 2024 18:05:27 +0100 Subject: [PATCH 08/15] refactor: rename --- .../onboarding/screens/WalletCardOnboardingScreen.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ts/features/itwallet/onboarding/screens/WalletCardOnboardingScreen.tsx b/ts/features/itwallet/onboarding/screens/WalletCardOnboardingScreen.tsx index e6c9c458877..c25c95b63ca 100644 --- a/ts/features/itwallet/onboarding/screens/WalletCardOnboardingScreen.tsx +++ b/ts/features/itwallet/onboarding/screens/WalletCardOnboardingScreen.tsx @@ -41,7 +41,7 @@ import { } from "../../machine/credential/selectors"; import { ItwCredentialIssuanceMachineContext } from "../../machine/provider"; import { ItwOnboardingModuleCredential } from "../components/ItwOnboardingModuleCredential"; -import { itwIsWalletInstanceStatusUnknownSelector } from "../../walletInstance/store/reducers"; +import { itwIsWalletInstanceStatusFailureSelector } from "../../walletInstance/store/reducers"; // List of available credentials to show to the user const availableCredentials = [ @@ -58,8 +58,8 @@ const activeBadge: Badge = { const WalletCardOnboardingScreen = () => { const isItwValid = useIOSelector(itwLifecycleIsValidSelector); const isItwEnabled = useIOSelector(isItwEnabledSelector); - const isWalletInstanceStatusUnknown = useIOSelector( - itwIsWalletInstanceStatusUnknownSelector + const isWalletInstanceStatusFailure = useIOSelector( + itwIsWalletInstanceStatusFailureSelector ); useFocusEffect(trackShowCredentialsList); @@ -67,10 +67,10 @@ const WalletCardOnboardingScreen = () => { const isItwSectionVisible = React.useMemo( // IT Wallet credential catalog should be visible if () => - !isWalletInstanceStatusUnknown && + !isWalletInstanceStatusFailure && isItwValid && // An eID has ben obtained and wallet is valid isItwEnabled, // Remote FF is enabled - [isItwValid, isItwEnabled, isWalletInstanceStatusUnknown] + [isItwValid, isItwEnabled, isWalletInstanceStatusFailure] ); return ( From 7700dfaa30d4ec6727ac356bc16f207d82321831 Mon Sep 17 00:00:00 2001 From: Gianluca Spada Date: Fri, 20 Dec 2024 16:23:44 +0100 Subject: [PATCH 09/15] refactor: use pot to handle async wallet instance status --- .../ItwWalletNotAvailableBanner.tsx | 4 +-- .../itwallet/common/store/selectors/index.ts | 2 +- .../saga/checkWalletInstanceStateSaga.ts | 10 +++--- .../useItwWalletInstanceRevocationAlert.ts | 2 +- .../walletInstance/store/actions/index.ts | 16 ++++++--- .../walletInstance/store/reducers/index.ts | 36 ++++++++++++++----- .../walletInstance/store/selectors/index.ts | 10 +++--- 7 files changed, 54 insertions(+), 26 deletions(-) diff --git a/ts/features/itwallet/common/components/ItwWalletNotAvailableBanner.tsx b/ts/features/itwallet/common/components/ItwWalletNotAvailableBanner.tsx index 1735284dd38..c34b3454b7c 100644 --- a/ts/features/itwallet/common/components/ItwWalletNotAvailableBanner.tsx +++ b/ts/features/itwallet/common/components/ItwWalletNotAvailableBanner.tsx @@ -15,11 +15,11 @@ import { itwIsWalletInstanceStatusFailureSelector } from "../../walletInstance/s * from the backend, because of unexpected errors. */ export const ItwWalletNotAvailableBanner = () => { - const isWalletInstanceStatusFailed = useIOSelector( + const isWalletInstanceStatusFailure = useIOSelector( itwIsWalletInstanceStatusFailureSelector ); - if (!isWalletInstanceStatusFailed) { + if (!isWalletInstanceStatusFailure) { return null; } diff --git a/ts/features/itwallet/common/store/selectors/index.ts b/ts/features/itwallet/common/store/selectors/index.ts index 53245186302..f45a0cdab17 100644 --- a/ts/features/itwallet/common/store/selectors/index.ts +++ b/ts/features/itwallet/common/store/selectors/index.ts @@ -8,7 +8,7 @@ import { itwIsWalletEmptySelector } from "../../../credentials/store/selectors"; import { itwLifecycleIsValidSelector } from "../../../lifecycle/store/selectors"; -import { itwIsWalletInstanceStatusFailureSelector } from "../../../walletInstance/store/reducers"; +import { itwIsWalletInstanceStatusFailureSelector } from "../../../walletInstance/store/selectors"; import { itwIsFeedbackBannerHiddenSelector, itwIsDiscoveryBannerHiddenSelector diff --git a/ts/features/itwallet/lifecycle/saga/checkWalletInstanceStateSaga.ts b/ts/features/itwallet/lifecycle/saga/checkWalletInstanceStateSaga.ts index b75d230c6c0..0baea52a249 100644 --- a/ts/features/itwallet/lifecycle/saga/checkWalletInstanceStateSaga.ts +++ b/ts/features/itwallet/lifecycle/saga/checkWalletInstanceStateSaga.ts @@ -1,7 +1,6 @@ import * as O from "fp-ts/lib/Option"; import { call, put, select } from "typed-redux-saga/macro"; import { sessionTokenSelector } from "../../../../store/reducers/authentication"; -import { FAILURE_STATUS } from "../../walletInstance/store/reducers"; import { ReduxSagaEffect } from "../../../../types/utils"; import { assert } from "../../../../utils/assert"; import { getWalletInstanceStatus } from "../../common/utils/itwAttestationUtils"; @@ -10,6 +9,7 @@ import { itwIntegrityKeyTagSelector } from "../../issuance/store/selectors"; import { itwUpdateWalletInstanceStatus } from "../../walletInstance/store/actions"; import { itwLifecycleIsOperationalOrValid } from "../store/selectors"; import { itwIntegritySetServiceIsReady } from "../../issuance/store/actions"; +import { getNetworkError } from "../../../../utils/errors"; import { handleWalletInstanceResetSaga } from "./handleWalletInstanceResetSaga"; export function* getStatusOrResetWalletInstance(integrityKeyTag: string) { @@ -17,6 +17,8 @@ export function* getStatusOrResetWalletInstance(integrityKeyTag: string) { assert(sessionToken, "Missing session token"); try { + yield* put(itwUpdateWalletInstanceStatus.request()); + const walletInstanceStatus = yield* call( getWalletInstanceStatus, integrityKeyTag, @@ -28,9 +30,9 @@ export function* getStatusOrResetWalletInstance(integrityKeyTag: string) { } // Update wallet instance status - yield* put(itwUpdateWalletInstanceStatus(walletInstanceStatus)); - } catch (_) { - yield* put(itwUpdateWalletInstanceStatus(FAILURE_STATUS)); + yield* put(itwUpdateWalletInstanceStatus.success(walletInstanceStatus)); + } catch (e) { + yield* put(itwUpdateWalletInstanceStatus.failure(getNetworkError(e))); } } diff --git a/ts/features/itwallet/walletInstance/hook/useItwWalletInstanceRevocationAlert.ts b/ts/features/itwallet/walletInstance/hook/useItwWalletInstanceRevocationAlert.ts index 15698a1baf2..9cd5ca96cd9 100644 --- a/ts/features/itwallet/walletInstance/hook/useItwWalletInstanceRevocationAlert.ts +++ b/ts/features/itwallet/walletInstance/hook/useItwWalletInstanceRevocationAlert.ts @@ -30,7 +30,7 @@ export const useItwWalletInstanceRevocationAlert = () => { useCallback(() => { if (walletInstanceStatus?.is_revoked) { showWalletRevocationAlert(walletInstanceStatus.revocation_reason); - dispatch(itwUpdateWalletInstanceStatus(undefined)); + dispatch(itwUpdateWalletInstanceStatus.cancel); } }, [walletInstanceStatus, dispatch]) ); diff --git a/ts/features/itwallet/walletInstance/store/actions/index.ts b/ts/features/itwallet/walletInstance/store/actions/index.ts index c4401165f24..27098afb6bc 100644 --- a/ts/features/itwallet/walletInstance/store/actions/index.ts +++ b/ts/features/itwallet/walletInstance/store/actions/index.ts @@ -1,6 +1,10 @@ -import { ActionType, createStandardAction } from "typesafe-actions"; +import { + ActionType, + createStandardAction, + createAsyncAction +} from "typesafe-actions"; import { WalletInstanceStatus } from "../../../common/utils/itwTypesUtils"; -import { FailureStatus } from "../reducers"; +import { NetworkError } from "../../../../../utils/errors"; /** * This action stores the Wallet Instance Attestation @@ -12,9 +16,11 @@ export const itwWalletInstanceAttestationStore = createStandardAction( /** * This action update the Wallet Instance Status */ -export const itwUpdateWalletInstanceStatus = createStandardAction( - "ITW_WALLET_INSTANCE_STATUS_UPDATE" -)(); +export const itwUpdateWalletInstanceStatus = createAsyncAction( + "ITW_WALLET_INSTANCE_STATUS_REQUEST", + "ITW_WALLET_INSTANCE_STATUS_SUCCESS", + "ITW_WALLET_INSTANCE_STATUS_FAILURE" +)(); export type ItwWalletInstanceActions = | ActionType diff --git a/ts/features/itwallet/walletInstance/store/reducers/index.ts b/ts/features/itwallet/walletInstance/store/reducers/index.ts index 578b8630f77..1a9b5500eeb 100644 --- a/ts/features/itwallet/walletInstance/store/reducers/index.ts +++ b/ts/features/itwallet/walletInstance/store/reducers/index.ts @@ -1,5 +1,6 @@ import { PersistConfig, persistReducer } from "redux-persist"; import { getType } from "typesafe-actions"; +import * as pot from "@pagopa/ts-commons/lib/pot"; import { Action } from "../../../../../store/actions/types"; import itwCreateSecureStorage from "../../../common/store/storages/itwSecureStorage"; import { itwLifecycleStoresReset } from "../../../lifecycle/store/actions"; @@ -8,18 +9,16 @@ import { itwUpdateWalletInstanceStatus } from "../actions"; import { WalletInstanceStatus } from "../../../common/utils/itwTypesUtils"; - -export const FAILURE_STATUS = "failure"; -export type FailureStatus = typeof FAILURE_STATUS; +import { NetworkError } from "../../../../../utils/errors"; export type ItwWalletInstanceState = { attestation: string | undefined; - status: WalletInstanceStatus | FailureStatus | undefined; + status: pot.Pot; }; export const itwWalletInstanceInitialState: ItwWalletInstanceState = { attestation: undefined, - status: undefined + status: pot.none }; const CURRENT_REDUX_ITW_WALLET_INSTANCE_STORE_VERSION = -1; @@ -31,15 +30,36 @@ const reducer = ( switch (action.type) { case getType(itwWalletInstanceAttestationStore): { return { - status: undefined, + status: pot.none, attestation: action.payload }; } - case getType(itwUpdateWalletInstanceStatus): { + case getType(itwUpdateWalletInstanceStatus.request): { + return { + ...state, + status: pot.toLoading(state.status) + }; + } + + case getType(itwUpdateWalletInstanceStatus.success): { + return { + ...state, + status: pot.some(action.payload) + }; + } + + case getType(itwUpdateWalletInstanceStatus.failure): { + return { + ...state, + status: pot.toError(state.status, action.payload) + }; + } + + case getType(itwUpdateWalletInstanceStatus.cancel): { return { ...state, - status: action.payload + status: pot.none }; } diff --git a/ts/features/itwallet/walletInstance/store/selectors/index.ts b/ts/features/itwallet/walletInstance/store/selectors/index.ts index 98f900cfd4b..f0eddea146b 100644 --- a/ts/features/itwallet/walletInstance/store/selectors/index.ts +++ b/ts/features/itwallet/walletInstance/store/selectors/index.ts @@ -1,9 +1,9 @@ import * as O from "fp-ts/lib/Option"; import { flow } from "fp-ts/lib/function"; +import * as pot from "@pagopa/ts-commons/lib/pot"; import { createSelector } from "reselect"; import { GlobalState } from "../../../../../store/reducers/types"; import { isWalletInstanceAttestationValid } from "../../../common/utils/itwAttestationUtils"; -import { FAILURE_STATUS } from "../reducers"; /* Selector to get the wallet instance attestation */ export const itwWalletInstanceAttestationSelector = (state: GlobalState) => @@ -21,11 +21,11 @@ export const itwIsWalletInstanceAttestationValidSelector = createSelector( /* Selector to get the wallet instance status */ export const itwWalletInstanceStatusSelector = (state: GlobalState) => - state.features.itWallet.walletInstance.status; + pot.toUndefined(state.features.itWallet.walletInstance.status); /** - * Returns true when it was not possible to retrieve the wallet instance status, - * for instance because of unexpected errors. + * Returns true when it was not possible to retrieve the wallet instance status because of unexpected errors, + * hence we cannot know whether the wallet instance is valid or has been revoked. */ export const itwIsWalletInstanceStatusFailureSelector = (state: GlobalState) => - state.features.itWallet.walletInstance.status === FAILURE_STATUS; + pot.isError(state.features.itWallet.walletInstance.status); From 9d4a4c05b1c7cf5b796d65ec6e6ca73a4ccfb3c3 Mon Sep 17 00:00:00 2001 From: Gianluca Spada Date: Fri, 20 Dec 2024 16:36:11 +0100 Subject: [PATCH 10/15] chore: use cancel action to reset itwUpdateWalletInstanceStatus pot --- .../hook/useItwWalletInstanceRevocationAlert.ts | 2 +- ts/features/itwallet/walletInstance/store/actions/index.ts | 5 +++-- ts/features/wallet/store/selectors/__tests__/index.test.ts | 1 - 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ts/features/itwallet/walletInstance/hook/useItwWalletInstanceRevocationAlert.ts b/ts/features/itwallet/walletInstance/hook/useItwWalletInstanceRevocationAlert.ts index 9cd5ca96cd9..5f50b73100f 100644 --- a/ts/features/itwallet/walletInstance/hook/useItwWalletInstanceRevocationAlert.ts +++ b/ts/features/itwallet/walletInstance/hook/useItwWalletInstanceRevocationAlert.ts @@ -30,7 +30,7 @@ export const useItwWalletInstanceRevocationAlert = () => { useCallback(() => { if (walletInstanceStatus?.is_revoked) { showWalletRevocationAlert(walletInstanceStatus.revocation_reason); - dispatch(itwUpdateWalletInstanceStatus.cancel); + dispatch(itwUpdateWalletInstanceStatus.cancel()); } }, [walletInstanceStatus, dispatch]) ); diff --git a/ts/features/itwallet/walletInstance/store/actions/index.ts b/ts/features/itwallet/walletInstance/store/actions/index.ts index 27098afb6bc..5d97c7da055 100644 --- a/ts/features/itwallet/walletInstance/store/actions/index.ts +++ b/ts/features/itwallet/walletInstance/store/actions/index.ts @@ -19,8 +19,9 @@ export const itwWalletInstanceAttestationStore = createStandardAction( export const itwUpdateWalletInstanceStatus = createAsyncAction( "ITW_WALLET_INSTANCE_STATUS_REQUEST", "ITW_WALLET_INSTANCE_STATUS_SUCCESS", - "ITW_WALLET_INSTANCE_STATUS_FAILURE" -)(); + "ITW_WALLET_INSTANCE_STATUS_FAILURE", + "ITW_WALLET_INSTANCE_STATUS_CANCEL" +)(); export type ItwWalletInstanceActions = | ActionType diff --git a/ts/features/wallet/store/selectors/__tests__/index.test.ts b/ts/features/wallet/store/selectors/__tests__/index.test.ts index 27dca2ff732..c1030f3dc6d 100644 --- a/ts/features/wallet/store/selectors/__tests__/index.test.ts +++ b/ts/features/wallet/store/selectors/__tests__/index.test.ts @@ -21,7 +21,6 @@ import { ItwLifecycleState } from "../../../../itwallet/lifecycle/store/reducers import * as itwLifecycleSelectors from "../../../../itwallet/lifecycle/store/selectors"; import { walletCardCategoryFilters } from "../../../types"; import { WalletCardsState } from "../../reducers/cards"; -import { FAILURE_STATUS } from "../../../../itwallet/walletInstance/store/reducers"; const T_ITW_CARDS: WalletCardsState = { "4": { From 1b57a64e4f4b0dfdc4582f17221a41eae88ae14e Mon Sep 17 00:00:00 2001 From: Gianluca Spada Date: Fri, 20 Dec 2024 16:39:20 +0100 Subject: [PATCH 11/15] chore: re-enable credential issuing with an unknown wallet instance status --- .../onboarding/screens/WalletCardOnboardingScreen.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/ts/features/itwallet/onboarding/screens/WalletCardOnboardingScreen.tsx b/ts/features/itwallet/onboarding/screens/WalletCardOnboardingScreen.tsx index 105e941a778..cdaf466893f 100644 --- a/ts/features/itwallet/onboarding/screens/WalletCardOnboardingScreen.tsx +++ b/ts/features/itwallet/onboarding/screens/WalletCardOnboardingScreen.tsx @@ -41,7 +41,6 @@ import { } from "../../machine/credential/selectors"; import { ItwCredentialIssuanceMachineContext } from "../../machine/provider"; import { ItwOnboardingModuleCredential } from "../components/ItwOnboardingModuleCredential"; -import { itwIsWalletInstanceStatusFailureSelector } from "../../walletInstance/store/reducers"; // List of available credentials to show to the user const availableCredentials = [ @@ -58,19 +57,15 @@ const activeBadge: Badge = { const WalletCardOnboardingScreen = () => { const isItwValid = useIOSelector(itwLifecycleIsValidSelector); const isItwEnabled = useIOSelector(isItwEnabledSelector); - const isWalletInstanceStatusFailure = useIOSelector( - itwIsWalletInstanceStatusFailureSelector - ); useFocusEffect(trackShowCredentialsList); const isItwSectionVisible = React.useMemo( // IT Wallet credential catalog should be visible if () => - !isWalletInstanceStatusFailure && isItwValid && // An eID has ben obtained and wallet is valid isItwEnabled, // Remote FF is enabled - [isItwValid, isItwEnabled, isWalletInstanceStatusFailure] + [isItwValid, isItwEnabled] ); return ( From 9dbf7a6136ae4a7fbfd46bdad3b2f9f178018383 Mon Sep 17 00:00:00 2001 From: Gianluca Spada Date: Fri, 20 Dec 2024 16:42:39 +0100 Subject: [PATCH 12/15] refactor: remove unnecessary loading state --- .../lifecycle/saga/checkWalletInstanceStateSaga.ts | 2 -- .../itwallet/walletInstance/store/reducers/index.ts | 7 ------- 2 files changed, 9 deletions(-) diff --git a/ts/features/itwallet/lifecycle/saga/checkWalletInstanceStateSaga.ts b/ts/features/itwallet/lifecycle/saga/checkWalletInstanceStateSaga.ts index 0baea52a249..51ab07dac9f 100644 --- a/ts/features/itwallet/lifecycle/saga/checkWalletInstanceStateSaga.ts +++ b/ts/features/itwallet/lifecycle/saga/checkWalletInstanceStateSaga.ts @@ -17,8 +17,6 @@ export function* getStatusOrResetWalletInstance(integrityKeyTag: string) { assert(sessionToken, "Missing session token"); try { - yield* put(itwUpdateWalletInstanceStatus.request()); - const walletInstanceStatus = yield* call( getWalletInstanceStatus, integrityKeyTag, diff --git a/ts/features/itwallet/walletInstance/store/reducers/index.ts b/ts/features/itwallet/walletInstance/store/reducers/index.ts index 1a9b5500eeb..587a95a7c50 100644 --- a/ts/features/itwallet/walletInstance/store/reducers/index.ts +++ b/ts/features/itwallet/walletInstance/store/reducers/index.ts @@ -35,13 +35,6 @@ const reducer = ( }; } - case getType(itwUpdateWalletInstanceStatus.request): { - return { - ...state, - status: pot.toLoading(state.status) - }; - } - case getType(itwUpdateWalletInstanceStatus.success): { return { ...state, From 4ae4a8e8969bc72e8d3c1a5a87328c60c097f391 Mon Sep 17 00:00:00 2001 From: Gianluca Spada Date: Fri, 20 Dec 2024 16:44:20 +0100 Subject: [PATCH 13/15] chore: update comment --- ts/features/itwallet/walletInstance/store/actions/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/features/itwallet/walletInstance/store/actions/index.ts b/ts/features/itwallet/walletInstance/store/actions/index.ts index 5d97c7da055..0001eb93e4b 100644 --- a/ts/features/itwallet/walletInstance/store/actions/index.ts +++ b/ts/features/itwallet/walletInstance/store/actions/index.ts @@ -14,7 +14,7 @@ export const itwWalletInstanceAttestationStore = createStandardAction( )(); /** - * This action update the Wallet Instance Status + * This action handles the Wallet Instance Status fetch */ export const itwUpdateWalletInstanceStatus = createAsyncAction( "ITW_WALLET_INSTANCE_STATUS_REQUEST", From 21b2445967762d2085c967fc8ff8a889621596e7 Mon Sep 17 00:00:00 2001 From: Gianluca Spada Date: Fri, 20 Dec 2024 16:55:31 +0100 Subject: [PATCH 14/15] chore: add migration --- .../walletInstance/store/reducers/index.ts | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/ts/features/itwallet/walletInstance/store/reducers/index.ts b/ts/features/itwallet/walletInstance/store/reducers/index.ts index 587a95a7c50..08b2ef646cb 100644 --- a/ts/features/itwallet/walletInstance/store/reducers/index.ts +++ b/ts/features/itwallet/walletInstance/store/reducers/index.ts @@ -1,4 +1,10 @@ -import { PersistConfig, persistReducer } from "redux-persist"; +import { + MigrationManifest, + PersistConfig, + PersistedState, + createMigrate, + persistReducer +} from "redux-persist"; import { getType } from "typesafe-actions"; import * as pot from "@pagopa/ts-commons/lib/pot"; import { Action } from "../../../../../store/actions/types"; @@ -10,6 +16,7 @@ import { } from "../actions"; import { WalletInstanceStatus } from "../../../common/utils/itwTypesUtils"; import { NetworkError } from "../../../../../utils/errors"; +import { isDevEnv } from "../../../../../utils/environment"; export type ItwWalletInstanceState = { attestation: string | undefined; @@ -21,7 +28,21 @@ export const itwWalletInstanceInitialState: ItwWalletInstanceState = { status: pot.none }; -const CURRENT_REDUX_ITW_WALLET_INSTANCE_STORE_VERSION = -1; +const CURRENT_REDUX_ITW_WALLET_INSTANCE_STORE_VERSION = 0; + +const migrations: MigrationManifest = { + // Convert status into a pot for better async handling + "0": (state): ItwWalletInstanceState & PersistedState => { + const prevState = state as PersistedState & { + attestation: string | undefined; + status: WalletInstanceStatus | undefined; + }; + return { + ...prevState, + status: prevState.status ? pot.some(prevState.status) : pot.none + }; + } +}; const reducer = ( state: ItwWalletInstanceState = itwWalletInstanceInitialState, @@ -67,7 +88,8 @@ const reducer = ( const itwWalletInstancePersistConfig: PersistConfig = { key: "itwWalletInstance", storage: itwCreateSecureStorage(), - version: CURRENT_REDUX_ITW_WALLET_INSTANCE_STORE_VERSION + version: CURRENT_REDUX_ITW_WALLET_INSTANCE_STORE_VERSION, + migrate: createMigrate(migrations, { debug: isDevEnv }) }; const persistedReducer = persistReducer( From 3cfcbcfb4c379928c12bff7cb7fcdf7cfec2783b Mon Sep 17 00:00:00 2001 From: Gianluca Spada Date: Fri, 20 Dec 2024 17:21:11 +0100 Subject: [PATCH 15/15] chore: only show ItwWalletNotAvailableBanner in ITW category filter --- .../ItwWalletNotAvailableBanner.tsx | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/ts/features/itwallet/common/components/ItwWalletNotAvailableBanner.tsx b/ts/features/itwallet/common/components/ItwWalletNotAvailableBanner.tsx index c34b3454b7c..e42628af18f 100644 --- a/ts/features/itwallet/common/components/ItwWalletNotAvailableBanner.tsx +++ b/ts/features/itwallet/common/components/ItwWalletNotAvailableBanner.tsx @@ -9,32 +9,36 @@ import { import I18n from "../../../../i18n"; import { useIOSelector } from "../../../../store/hooks"; import { itwIsWalletInstanceStatusFailureSelector } from "../../walletInstance/store/selectors"; +import { withWalletCategoryFilter } from "../../../wallet/utils"; /** * Component shown when it is not possible to retrieve the wallet instance status * from the backend, because of unexpected errors. */ -export const ItwWalletNotAvailableBanner = () => { - const isWalletInstanceStatusFailure = useIOSelector( - itwIsWalletInstanceStatusFailureSelector - ); +export const ItwWalletNotAvailableBanner = withWalletCategoryFilter( + "itw", + () => { + const isWalletInstanceStatusFailure = useIOSelector( + itwIsWalletInstanceStatusFailureSelector + ); - if (!isWalletInstanceStatusFailure) { - return null; - } + if (!isWalletInstanceStatusFailure) { + return null; + } - return ( - - - - {I18n.t("features.itWallet.generic.walletNotAvailable.message")} - - - {I18n.t("features.itWallet.generic.walletNotAvailable.cta")} - - - ); -}; + return ( + + + + {I18n.t("features.itWallet.generic.walletNotAvailable.message")} + + + {I18n.t("features.itWallet.generic.walletNotAvailable.cta")} + + + ); + } +); const styles = StyleSheet.create({ bannerContainer: {