diff --git a/.env.local b/.env.local index 15b6ff0d3a8..ead00523583 100644 --- a/.env.local +++ b/.env.local @@ -97,6 +97,4 @@ ITW_BYPASS_IDENTITY_MATCH=YES # Use the test environment for the IDP hint for both CIE and SPID ITW_IDP_HINT_TEST=YES # IPZS Privacy Policy URL -ITW_IPZS_PRIVACY_URL='https://io.italia.it/informativa-ipzs' -# ITW Documents on IO URL -ITW_DOCUMENTS_ON_IO_URL='https://io.italia.it/documenti-su-io' +ITW_IPZS_PRIVACY_URL='https://io.italia.it/informativa-ipzs' \ No newline at end of file diff --git a/.env.production b/.env.production index 0d09f9f56db..6d01eebd819 100644 --- a/.env.production +++ b/.env.production @@ -97,6 +97,4 @@ ITW_BYPASS_IDENTITY_MATCH=NO # Use the test environment for the IDP hint for both CIE and SPID ITW_IDP_HINT_TEST=NO # IPZS Privacy Policy URL -ITW_IPZS_PRIVACY_URL='https://io.italia.it/informativa-ipzs' -# ITW Documents on IO URL -ITW_DOCUMENTS_ON_IO_URL='https://io.italia.it/documenti-su-io' +ITW_IPZS_PRIVACY_URL='https://io.italia.it/informativa-ipzs' \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b90ff95da7..56f31e639a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,30 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [2.80.0-rc.6](https://github.com/pagopa/io-app/compare/2.80.0-rc.5...2.80.0-rc.6) (2024-12-19) + + +### Features + +* **IT Wallet:** [[SIW-1824](https://pagopa.atlassian.net/browse/SIW-1824)] Show alert if wallet instance is revoked ([#6547](https://github.com/pagopa/io-app/issues/6547)) ([fbe9e87](https://github.com/pagopa/io-app/commit/fbe9e87eb00f576a09867991353b1b82a5f5fea3)) + + +### Bug Fixes + +* [[IOPID-2547](https://pagopa.atlassian.net/browse/IOPID-2547)] Fix unhandled error on `Linking.openUrl` ([#6554](https://github.com/pagopa/io-app/issues/6554)) ([1ddf587](https://github.com/pagopa/io-app/commit/1ddf587f5c63c6bf5a236da5a96e31764b86955f)) +* [[IOPLT-814](https://pagopa.atlassian.net/browse/IOPLT-814)] Fixes workflow concatenation after changes due to test improvements ([#6572](https://github.com/pagopa/io-app/issues/6572)) ([d8a8e66](https://github.com/pagopa/io-app/commit/d8a8e660bfc5bf9358e2050763ebeeff5b6b9c4c)) + + +### Chores + +* **Cross:** [[IOAPPX-432](https://pagopa.atlassian.net/browse/IOAPPX-432)] Development Push notifications for Android ([#6416](https://github.com/pagopa/io-app/issues/6416)) ([8cf64a9](https://github.com/pagopa/io-app/commit/8cf64a942a0ac538c0f6b493dccd1d8695bc29d5)) +* **Cross:** [[IOAPPX-448](https://pagopa.atlassian.net/browse/IOAPPX-448)] Add missing components to the Design System section, remove legacy ones + Change `ListItemMessage` component API ([#6541](https://github.com/pagopa/io-app/issues/6541)) ([323996f](https://github.com/pagopa/io-app/commit/323996f8e0207da8a60766ff38042e9a85d9a857)) +* [[IOBP-1076](https://pagopa.atlassian.net/browse/IOBP-1076)] FAQ without CTA with sticky buttons ([#6555](https://github.com/pagopa/io-app/issues/6555)) ([a0d562f](https://github.com/pagopa/io-app/commit/a0d562fc949e40ca7b24b714e42e41f53db56f32)) +* [[IOPLT-798](https://pagopa.atlassian.net/browse/IOPLT-798)] Split test execution using shards ([#6500](https://github.com/pagopa/io-app/issues/6500)) ([7d9df54](https://github.com/pagopa/io-app/commit/7d9df5479cd9784e2679e5a5188d085cb68dbef5)) +* [[IOPLT-813](https://pagopa.atlassian.net/browse/IOPLT-813)] Fix android run script and align Gemfile ([#6571](https://github.com/pagopa/io-app/issues/6571)) ([bbe6980](https://github.com/pagopa/io-app/commit/bbe69800a17a8e0b6b88629da9b58fa7f23f95c4)) +* **IT Wallet:** [[SIW-1793](https://pagopa.atlassian.net/browse/SIW-1793)] Update non-matching identity screen ([#6559](https://github.com/pagopa/io-app/issues/6559)) ([5c04708](https://github.com/pagopa/io-app/commit/5c04708efdf2aa82263c947073eb6c9e489f15f7)) +* [[IOPLT-801](https://pagopa.atlassian.net/browse/IOPLT-801)] Improvements to canary release workflow ([#6468](https://github.com/pagopa/io-app/issues/6468)) ([dc66860](https://github.com/pagopa/io-app/commit/dc668601582efbb029b36c58186e7fb9e098b576)) + ## [2.80.0-rc.5](https://github.com/pagopa/io-app/compare/2.80.0-rc.4...2.80.0-rc.5) (2024-12-18) diff --git a/android/app/build.gradle b/android/app/build.gradle index 320024561f8..8e759e032cf 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -119,8 +119,8 @@ android { applicationId "it.pagopa.io.app" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 100154900 - versionName "2.80.0.5" + versionCode 100154901 + versionName "2.80.0.6" multiDexEnabled true // The resConfigs attribute will remove all not required localized resources while building the application, // including the localized resources from libraries. diff --git a/ios/ItaliaApp.xcodeproj/project.pbxproj b/ios/ItaliaApp.xcodeproj/project.pbxproj index 7e4990bdbc0..70e39b1f27b 100644 --- a/ios/ItaliaApp.xcodeproj/project.pbxproj +++ b/ios/ItaliaApp.xcodeproj/project.pbxproj @@ -798,7 +798,7 @@ CODE_SIGN_ENTITLEMENTS = ItaliaApp/ItaliaApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 5; + CURRENT_PROJECT_VERSION = 6; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = M2X5YQ4BJ7; ENABLE_BITCODE = NO; @@ -835,7 +835,7 @@ CODE_SIGN_ENTITLEMENTS = ItaliaApp/ItaliaApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 5; + CURRENT_PROJECT_VERSION = 6; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = M2X5YQ4BJ7; ENABLE_BITCODE = NO; diff --git a/ios/ItaliaApp/Info.plist b/ios/ItaliaApp/Info.plist index fffb2ffee8b..fcb81f12f10 100644 --- a/ios/ItaliaApp/Info.plist +++ b/ios/ItaliaApp/Info.plist @@ -36,7 +36,7 @@ CFBundleVersion - 5 + 6 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/ItaliaAppTests/Info.plist b/ios/ItaliaAppTests/Info.plist index 8256df486f4..54df2577125 100644 --- a/ios/ItaliaAppTests/Info.plist +++ b/ios/ItaliaAppTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 5 + 6 \ No newline at end of file diff --git a/locales/en/index.yml b/locales/en/index.yml index 8c2b33fac22..a10530a05a0 100644 --- a/locales/en/index.yml +++ b/locales/en/index.yml @@ -3555,6 +3555,20 @@ features: title: Dicci cosa ne pensi content: Raccontaci la tua esperienza con la funzionalità Documenti su IO. action: Inizia + walletInstanceRevoked: + alert: + cta: Scopri di più + closeButton: Chiudi + closeButtonAlt: Ho capito + revokedByWalletProvider: + title: Documenti su IO è stata disattivata + content: Per verificare i requisiti richiesti per continuare a usare la funzionalità sul tuo dispositivo, premi "Scopri di più". + newWalletInstanceCreated: + title: Documenti su IO è stata disattivata su questo dispositivo + content: Puoi usare i tuoi documenti su IO su un solo dispositivo alla volta per ragioni di sicurezza. + revokedByUser: + title: Hai disattivato Documenti su IO + content: Se cambi idea, potrai riattivare Documenti su IO in futuro. support: ticketList: noTicket: diff --git a/locales/it/index.yml b/locales/it/index.yml index 640a6ab0a72..3a5cbe020ce 100644 --- a/locales/it/index.yml +++ b/locales/it/index.yml @@ -3555,6 +3555,20 @@ features: title: Dicci cosa ne pensi content: Raccontaci la tua esperienza con la funzionalità Documenti su IO. action: Inizia + walletInstanceRevoked: + alert: + cta: Scopri di più + closeButton: Chiudi + closeButtonAlt: Ho capito + revokedByWalletProvider: + title: Documenti su IO è stata disattivata + content: Per verificare i requisiti richiesti per continuare a usare la funzionalità sul tuo dispositivo, premi "Scopri di più". + newWalletInstanceCreated: + title: Documenti su IO è stata disattivata su questo dispositivo + content: Puoi usare i tuoi documenti su IO su un solo dispositivo alla volta per ragioni di sicurezza. + revokedByUser: + title: Hai disattivato Documenti su IO + content: Se cambi idea, potrai riattivare Documenti su IO in futuro. support: ticketList: noTicket: diff --git a/package.json b/package.json index bf310534b75..eb641b03094 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "italia-app", - "version": "2.80.0-rc.5", + "version": "2.80.0-rc.6", "private": true, "scripts": { "start": "react-native start", diff --git a/publiccode.yml b/publiccode.yml index e9c51e4b7cf..9fb9e0d7e57 100644 --- a/publiccode.yml +++ b/publiccode.yml @@ -9,7 +9,7 @@ releaseDate: "2024-11-21" url: "https://github.com/pagopa/io-app" applicationSuite: IO landingURL: "https://io.italia.it/" -softwareVersion: 2.80.0-rc.5 +softwareVersion: 2.80.0-rc.6 developmentStatus: beta softwareType: standalone/mobile roadmap: "https://io.italia.it/" diff --git a/ts/config.ts b/ts/config.ts index fa847bbb3ad..505771f19eb 100644 --- a/ts/config.ts +++ b/ts/config.ts @@ -252,8 +252,3 @@ export const itwIpzsPrivacyUrl: string = pipe( t.string.decode, E.getOrElse(() => "https://io.italia.it/informativa-ipzs") ); -export const itwDocumentsOnIOUrl: string = pipe( - Config.ITW_DOCUMENTS_ON_IO_URL, - t.string.decode, - E.getOrElse(() => "https://io.italia.it/documenti-su-io") -); diff --git a/ts/features/common/store/reducers/__tests__/__snapshots__/index.test.ts.snap b/ts/features/common/store/reducers/__tests__/__snapshots__/index.test.ts.snap index 527cdcd64ce..81dd7b4bcb0 100644 --- a/ts/features/common/store/reducers/__tests__/__snapshots__/index.test.ts.snap +++ b/ts/features/common/store/reducers/__tests__/__snapshots__/index.test.ts.snap @@ -132,6 +132,7 @@ exports[`featuresPersistor should match snapshot 1`] = ` }, "walletInstance": { "attestation": undefined, + "status": undefined, }, }, "landingBanners": { diff --git a/ts/features/itwallet/common/store/reducers/__tests__/__snapshots__/index.test.ts.snap b/ts/features/itwallet/common/store/reducers/__tests__/__snapshots__/index.test.ts.snap index 7fb4ec75a32..e6f11fb8d17 100644 --- a/ts/features/itwallet/common/store/reducers/__tests__/__snapshots__/index.test.ts.snap +++ b/ts/features/itwallet/common/store/reducers/__tests__/__snapshots__/index.test.ts.snap @@ -19,6 +19,7 @@ exports[`itWalletReducer should match snapshot [if this test fails, remember to }, "walletInstance": { "attestation": undefined, + "status": undefined, }, } `; diff --git a/ts/features/itwallet/common/utils/itwTypesUtils.ts b/ts/features/itwallet/common/utils/itwTypesUtils.ts index fe04304f663..3fab058e345 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,19 @@ export type ParsedStatusAttestation = Awaited< ReturnType >["parsedStatusAttestation"]["payload"]; +/** + * Alias for the WalletInstanceStatus type + */ +export type WalletInstanceStatus = Awaited< + ReturnType +>; + +/** + * Alias for the WalletInstanceRevocationReason type + */ +export type WalletInstanceRevocationReason = + WalletInstanceStatus["revocation_reason"]; + export type StoredStatusAttestation = | { credentialStatus: "valid"; diff --git a/ts/features/itwallet/lifecycle/saga/__tests__/checkWalletInstanceStateSaga.test.ts b/ts/features/itwallet/lifecycle/saga/__tests__/checkWalletInstanceStateSaga.test.ts index fdf253d364c..574090dea19 100644 --- a/ts/features/itwallet/lifecycle/saga/__tests__/checkWalletInstanceStateSaga.test.ts +++ b/ts/features/itwallet/lifecycle/saga/__tests__/checkWalletInstanceStateSaga.test.ts @@ -12,9 +12,9 @@ import { getWalletInstanceStatus } from "../../../common/utils/itwAttestationUti import { StoredCredential } from "../../../common/utils/itwTypesUtils"; import { sessionTokenSelector } from "../../../../../store/reducers/authentication"; import { handleWalletInstanceResetSaga } from "../handleWalletInstanceResetSaga"; -import { itwIsWalletInstanceAttestationValidSelector } from "../../../walletInstance/store/reducers"; import { ensureIntegrityServiceIsReady } from "../../../common/utils/itwIntegrityUtils"; import { itwIntegrityServiceReadySelector } from "../../../issuance/store/selectors"; +import { itwIsWalletInstanceAttestationValidSelector } from "../../../walletInstance/store/selectors"; jest.mock("@pagopa/io-react-native-crypto", () => ({ deleteKey: jest.fn diff --git a/ts/features/itwallet/lifecycle/saga/checkWalletInstanceStateSaga.ts b/ts/features/itwallet/lifecycle/saga/checkWalletInstanceStateSaga.ts index 779a11a929e..07d6dc12719 100644 --- a/ts/features/itwallet/lifecycle/saga/checkWalletInstanceStateSaga.ts +++ b/ts/features/itwallet/lifecycle/saga/checkWalletInstanceStateSaga.ts @@ -8,6 +8,7 @@ import { ensureIntegrityServiceIsReady } from "../../common/utils/itwIntegrityUt import { itwIntegrityKeyTagSelector } from "../../issuance/store/selectors"; import { itwLifecycleIsOperationalOrValid } from "../store/selectors"; import { itwIntegritySetServiceIsReady } from "../../issuance/store/actions"; +import { itwUpdateWalletInstanceStatus } from "../../walletInstance/store/actions"; import { handleWalletInstanceResetSaga } from "./handleWalletInstanceResetSaga"; export function* getStatusOrResetWalletInstance(integrityKeyTag: string) { @@ -23,6 +24,9 @@ export function* getStatusOrResetWalletInstance(integrityKeyTag: string) { if (walletInstanceStatus.is_revoked) { yield* call(handleWalletInstanceResetSaga); } + + // Update wallet instance status + yield* put(itwUpdateWalletInstanceStatus(walletInstanceStatus)); } /** diff --git a/ts/features/itwallet/machine/credential/actions.ts b/ts/features/itwallet/machine/credential/actions.ts index f5f1eaf9990..ee43befc3b9 100644 --- a/ts/features/itwallet/machine/credential/actions.ts +++ b/ts/features/itwallet/machine/credential/actions.ts @@ -19,7 +19,7 @@ import { import { itwCredentialsStore } from "../../credentials/store/actions"; import { ITW_ROUTES } from "../../navigation/routes"; import { itwWalletInstanceAttestationStore } from "../../walletInstance/store/actions"; -import { itwWalletInstanceAttestationSelector } from "../../walletInstance/store/reducers"; +import { itwWalletInstanceAttestationSelector } from "../../walletInstance/store/selectors"; import { Context } from "./context"; import { CredentialIssuanceEvents } from "./events"; diff --git a/ts/features/itwallet/machine/eid/actions.ts b/ts/features/itwallet/machine/eid/actions.ts index d7581cc2b7a..9b84d7a1e23 100644 --- a/ts/features/itwallet/machine/eid/actions.ts +++ b/ts/features/itwallet/machine/eid/actions.ts @@ -22,8 +22,8 @@ import { trackSaveCredentialSuccess, updateITWStatusAndIDProperties } from "../../analytics"; -import { itwWalletInstanceAttestationSelector } from "../../walletInstance/store/reducers"; import { itwIntegrityKeyTagSelector } from "../../issuance/store/selectors"; +import { itwWalletInstanceAttestationSelector } from "../../walletInstance/store/selectors"; import { Context } from "./context"; import { EidIssuanceEvents } from "./events"; diff --git a/ts/features/itwallet/trustmark/machine/actions.ts b/ts/features/itwallet/trustmark/machine/actions.ts index 929d2d32104..145a4ba00fd 100644 --- a/ts/features/itwallet/trustmark/machine/actions.ts +++ b/ts/features/itwallet/trustmark/machine/actions.ts @@ -4,7 +4,7 @@ import { useIONavigation } from "../../../../navigation/params/AppParamsList"; import { checkCurrentSession } from "../../../../store/actions/authentication"; import { useIOStore } from "../../../../store/hooks"; import { itwCredentialByTypeSelector } from "../../credentials/store/selectors"; -import { itwWalletInstanceAttestationSelector } from "../../walletInstance/store/reducers"; +import { itwWalletInstanceAttestationSelector } from "../../walletInstance/store/selectors"; import { Context } from "./context"; export const createItwTrustmarkActionsImplementation = ( diff --git a/ts/features/itwallet/walletInstance/hook/useItwWalletInstanceRevocationAlert.ts b/ts/features/itwallet/walletInstance/hook/useItwWalletInstanceRevocationAlert.ts new file mode 100644 index 00000000000..15698a1baf2 --- /dev/null +++ b/ts/features/itwallet/walletInstance/hook/useItwWalletInstanceRevocationAlert.ts @@ -0,0 +1,101 @@ +import { Alert } from "react-native"; +import { useCallback } from "react"; +import I18n from "../../../../i18n"; +import { WalletInstanceRevocationReason } from "../../common/utils/itwTypesUtils"; +import { useIODispatch, useIOSelector } from "../../../../store/hooks"; +import { itwWalletInstanceStatusSelector } from "../store/selectors"; +import { useOnFirstRender } from "../../../../utils/hooks/useOnFirstRender"; +import { itwUpdateWalletInstanceStatus } from "../store/actions"; +import { openWebUrl } from "../../../../utils/url"; + +const closeButtonText = I18n.t( + "features.itWallet.walletInstanceRevoked.alert.closeButton" +); +const alertCtaText = I18n.t( + "features.itWallet.walletInstanceRevoked.alert.cta" +); + +const itwMinIntegrityReqUrl = "https://io.italia.it/documenti-su-io/faq/#n1_12"; +const itwDocsOnIOMultipleDevicesUrl = + "https://io.italia.it/documenti-su-io/faq/#n1_14"; + +/** + * Hook to monitor wallet instance status and display alerts if revoked. + */ +export const useItwWalletInstanceRevocationAlert = () => { + const walletInstanceStatus = useIOSelector(itwWalletInstanceStatusSelector); + const dispatch = useIODispatch(); + + useOnFirstRender( + useCallback(() => { + if (walletInstanceStatus?.is_revoked) { + showWalletRevocationAlert(walletInstanceStatus.revocation_reason); + dispatch(itwUpdateWalletInstanceStatus(undefined)); + } + }, [walletInstanceStatus, dispatch]) + ); +}; + +/** + * Displays an alert based on the revocation reason. + */ +const showWalletRevocationAlert = ( + revocationReason?: WalletInstanceRevocationReason +) => { + switch (revocationReason) { + case "CERTIFICATE_REVOKED_BY_ISSUER": + Alert.alert( + I18n.t( + "features.itWallet.walletInstanceRevoked.alert.revokedByWalletProvider.title" + ), + I18n.t( + "features.itWallet.walletInstanceRevoked.alert.revokedByWalletProvider.content" + ), + [ + { text: closeButtonText }, + { + text: alertCtaText, + onPress: () => openWebUrl(itwMinIntegrityReqUrl) + } + ] + ); + break; + + case "NEW_WALLET_INSTANCE_CREATED": + Alert.alert( + I18n.t( + "features.itWallet.walletInstanceRevoked.alert.newWalletInstanceCreated.title" + ), + I18n.t( + "features.itWallet.walletInstanceRevoked.alert.newWalletInstanceCreated.content" + ), + [ + { text: closeButtonText }, + { + text: alertCtaText, + onPress: () => openWebUrl(itwDocsOnIOMultipleDevicesUrl) + } + ] + ); + break; + case "REVOKED_BY_USER": + Alert.alert( + I18n.t( + "features.itWallet.walletInstanceRevoked.alert.revokedByUser.title" + ), + I18n.t( + "features.itWallet.walletInstanceRevoked.alert.revokedByUser.content" + ), + [ + { + text: I18n.t( + "features.itWallet.walletInstanceRevoked.alert.closeButtonAlt" + ) + } + ] + ); + break; + default: + break; + } +}; diff --git a/ts/features/itwallet/walletInstance/store/actions/index.ts b/ts/features/itwallet/walletInstance/store/actions/index.ts index 3d0c05b05c2..cbf6db80535 100644 --- a/ts/features/itwallet/walletInstance/store/actions/index.ts +++ b/ts/features/itwallet/walletInstance/store/actions/index.ts @@ -1,4 +1,5 @@ import { ActionType, createStandardAction } from "typesafe-actions"; +import { WalletInstanceStatus } from "../../../common/utils/itwTypesUtils"; /** * This action stores the Wallet Instance Attestation @@ -7,6 +8,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..778d134d4fb 100644 --- a/ts/features/itwallet/walletInstance/store/reducers/index.ts +++ b/ts/features/itwallet/walletInstance/store/reducers/index.ts @@ -1,21 +1,22 @@ -import * as O from "fp-ts/lib/Option"; -import { flow } from "fp-ts/lib/function"; import { PersistConfig, persistReducer } from "redux-persist"; -import { createSelector } from "reselect"; import { getType } from "typesafe-actions"; import { Action } from "../../../../../store/actions/types"; -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 { + itwWalletInstanceAttestationStore, + itwUpdateWalletInstanceStatus +} from "../actions"; +import { WalletInstanceStatus } from "../../../common/utils/itwTypesUtils"; export type ItwWalletInstanceState = { attestation: string | undefined; + status: WalletInstanceStatus | undefined; }; export const itwWalletInstanceInitialState: ItwWalletInstanceState = { - attestation: undefined + attestation: undefined, + status: undefined }; const CURRENT_REDUX_ITW_WALLET_INSTANCE_STORE_VERSION = -1; @@ -27,10 +28,18 @@ const reducer = ( switch (action.type) { case getType(itwWalletInstanceAttestationStore): { return { + status: undefined, attestation: action.payload }; } + case getType(itwUpdateWalletInstanceStatus): { + return { + ...state, + status: action.payload + }; + } + case getType(itwLifecycleStoresReset): return { ...itwWalletInstanceInitialState }; @@ -50,16 +59,4 @@ const persistedReducer = persistReducer( reducer ); -export const itwWalletInstanceAttestationSelector = (state: GlobalState) => - state.features.itWallet.walletInstance.attestation; - -export const itwIsWalletInstanceAttestationValidSelector = createSelector( - itwWalletInstanceAttestationSelector, - flow( - O.fromNullable, - O.map(isWalletInstanceAttestationValid), - O.getOrElse(() => false) - ) -); - export default persistedReducer; diff --git a/ts/features/itwallet/walletInstance/store/selectors/index.ts b/ts/features/itwallet/walletInstance/store/selectors/index.ts new file mode 100644 index 00000000000..85a98084d61 --- /dev/null +++ b/ts/features/itwallet/walletInstance/store/selectors/index.ts @@ -0,0 +1,23 @@ +import * as O from "fp-ts/lib/Option"; +import { flow } from "fp-ts/lib/function"; +import { createSelector } from "reselect"; +import { GlobalState } from "../../../../../store/reducers/types"; +import { isWalletInstanceAttestationValid } from "../../../common/utils/itwAttestationUtils"; + +/* Selector to get the wallet instance attestation */ +export const itwWalletInstanceAttestationSelector = (state: GlobalState) => + state.features.itWallet.walletInstance.attestation; + +/* Selector to check if the attestation is valid */ +export const itwIsWalletInstanceAttestationValidSelector = createSelector( + itwWalletInstanceAttestationSelector, + flow( + O.fromNullable, + O.map(isWalletInstanceAttestationValid), + O.getOrElse(() => false) + ) +); + +/* Selector to get the wallet instance status */ +export const itwWalletInstanceStatusSelector = (state: GlobalState) => + state.features.itWallet.walletInstance.status; diff --git a/ts/features/wallet/components/WalletCardsContainer.tsx b/ts/features/wallet/components/WalletCardsContainer.tsx index 898245b9a9b..8b45cb2b996 100644 --- a/ts/features/wallet/components/WalletCardsContainer.tsx +++ b/ts/features/wallet/components/WalletCardsContainer.tsx @@ -28,6 +28,7 @@ import { shouldRenderWalletEmptyStateSelector } from "../store/selectors"; import { WalletCardCategoryFilter } from "../types"; +import { useItwWalletInstanceRevocationAlert } from "../../itwallet/walletInstance/hook/useItwWalletInstanceRevocationAlert"; import { WalletCardsCategoryContainer } from "./WalletCardsCategoryContainer"; import { WalletCardsCategoryRetryErrorBanner } from "./WalletCardsCategoryRetryErrorBanner"; import { WalletCardSkeleton } from "./WalletCardSkeleton"; @@ -48,6 +49,8 @@ const WalletCardsContainer = () => { shouldRenderWalletEmptyStateSelector ); + useItwWalletInstanceRevocationAlert(); + // Loading state is only displayed if there is the initial loading and there are no cards or // placeholders in the wallet const shouldRenderLoadingState = isLoading && isWalletEmpty; diff --git a/ts/features/wallet/components/__tests__/WalletCardsContainer.test.tsx b/ts/features/wallet/components/__tests__/WalletCardsContainer.test.tsx index 9c0b2bd0a05..c78c5bc00e9 100644 --- a/ts/features/wallet/components/__tests__/WalletCardsContainer.test.tsx +++ b/ts/features/wallet/components/__tests__/WalletCardsContainer.test.tsx @@ -3,6 +3,7 @@ import _ from "lodash"; import { ComponentType } from "react"; import configureMockStore from "redux-mock-store"; +import { Alert } from "react-native"; import ROUTES from "../../../../navigation/routes"; import { applicationChangeState } from "../../../../store/actions/application"; import { appReducer } from "../../../../store/reducers"; @@ -17,6 +18,7 @@ import { import { ItwJwtCredentialStatus } from "../../../itwallet/common/utils/itwTypesUtils"; import * as itwCredentialsSelectors from "../../../itwallet/credentials/store/selectors"; import * as itwLifecycleSelectors from "../../../itwallet/lifecycle/store/selectors"; +import * as itwWalletInstanceSelectors from "../../../itwallet/walletInstance/store/selectors"; import { WalletCardsState } from "../../store/reducers/cards"; import * as walletSelectors from "../../store/selectors"; import { WalletCard } from "../../types"; @@ -25,7 +27,9 @@ import { OtherWalletCardsContainer, WalletCardsContainer } from "../WalletCardsContainer"; +import I18n from "../../../../i18n"; +jest.spyOn(Alert, "alert"); jest.mock("react-native-reanimated", () => ({ ...require("react-native-reanimated/mock"), useReducedMotion: jest.fn, @@ -358,6 +362,112 @@ describe("OtherWalletCardsContainer", () => { expect(queryByTestId(`walletCardTestID_cgn_cgn_3`)).not.toBeNull(); expect(queryByTestId(`walletCardTestID_itw_placeholder_4`)).not.toBeNull(); }); + + it("should not show alert if not revoked", () => { + jest + .spyOn(itwWalletInstanceSelectors, "itwWalletInstanceStatusSelector") + .mockImplementation(() => ({ + id: "39cc62ab-1df0-4a9d-974d-4c58173a1750", + is_revoked: false, + revocation_reason: undefined + })); + + renderComponent(WalletCardsContainer); + + expect(Alert.alert).not.toHaveBeenCalled(); + }); + + it("should show alert for NEW_WALLET_INSTANCE_CREATED", () => { + jest + .spyOn(itwWalletInstanceSelectors, "itwWalletInstanceStatusSelector") + .mockImplementation(() => ({ + id: "39cc62ab-1df0-4a9d-974d-4c58173a1750", + is_revoked: true, + revocation_reason: "NEW_WALLET_INSTANCE_CREATED" + })); + + renderComponent(WalletCardsContainer); + + expect(Alert.alert).toHaveBeenCalledWith( + I18n.t( + "features.itWallet.walletInstanceRevoked.alert.newWalletInstanceCreated.title" + ), + I18n.t( + "features.itWallet.walletInstanceRevoked.alert.newWalletInstanceCreated.content" + ), + [ + { + text: I18n.t( + "features.itWallet.walletInstanceRevoked.alert.closeButton" + ) + }, + { + text: I18n.t("features.itWallet.walletInstanceRevoked.alert.cta"), + onPress: expect.any(Function) + } + ] + ); + }); + + it("should show alert for CERTIFICATE_REVOKED_BY_ISSUER", () => { + jest + .spyOn(itwWalletInstanceSelectors, "itwWalletInstanceStatusSelector") + .mockImplementation(() => ({ + id: "39cc62ab-1df0-4a9d-974d-4c58173a1750", + is_revoked: true, + revocation_reason: "CERTIFICATE_REVOKED_BY_ISSUER" + })); + + renderComponent(WalletCardsContainer); + + expect(Alert.alert).toHaveBeenCalledWith( + I18n.t( + "features.itWallet.walletInstanceRevoked.alert.revokedByWalletProvider.title" + ), + I18n.t( + "features.itWallet.walletInstanceRevoked.alert.revokedByWalletProvider.content" + ), + [ + { + text: I18n.t( + "features.itWallet.walletInstanceRevoked.alert.closeButton" + ) + }, + { + text: I18n.t("features.itWallet.walletInstanceRevoked.alert.cta"), + onPress: expect.any(Function) + } + ] + ); + }); + + it("should show alert for REVOKED_BY_USER", () => { + jest + .spyOn(itwWalletInstanceSelectors, "itwWalletInstanceStatusSelector") + .mockImplementation(() => ({ + id: "39cc62ab-1df0-4a9d-974d-4c58173a1750", + is_revoked: true, + revocation_reason: "REVOKED_BY_USER" + })); + + renderComponent(WalletCardsContainer); + + expect(Alert.alert).toHaveBeenCalledWith( + I18n.t( + "features.itWallet.walletInstanceRevoked.alert.revokedByUser.title" + ), + I18n.t( + "features.itWallet.walletInstanceRevoked.alert.revokedByUser.content" + ), + [ + { + text: I18n.t( + "features.itWallet.walletInstanceRevoked.alert.closeButtonAlt" + ) + } + ] + ); + }); }); const renderComponent = (component: ComponentType) => {