diff --git a/locales/en/index.yml b/locales/en/index.yml index f6f9424d41c..7d05de37121 100644 --- a/locales/en/index.yml +++ b/locales/en/index.yml @@ -3494,6 +3494,7 @@ features: valid: L'ultima verifica è del {{date}}. expiring: Verifica la tua identità entro il {{date}}. expired: È necessario un rapido passaggio di verifica per continuare a usare Documenti su IO. + action: Inizia MDL: expiring: title: "Patente su IO: documento in scadenza" @@ -3549,6 +3550,13 @@ features: openPdf: Show document shareButton: Save or share fiscalCode: Your Fiscal Code + eid: + verificationExpired: + title: Verifica la tua identità + contentStart: "È un passaggio di sicurezza necessario per continuare ad usare " + contentBold: Documenti su IO + contentEnd: "." + primaryAction: Inizia statusAttestationUnknown: title: Non siamo riusciti a caricare {{credentialName}} content: Chiudi e riapri l'app per riprovare. diff --git a/locales/it/index.yml b/locales/it/index.yml index 1525b138425..326db19474a 100644 --- a/locales/it/index.yml +++ b/locales/it/index.yml @@ -3494,6 +3494,7 @@ features: valid: L'ultima verifica è del {{date}}. expiring: Verifica la tua identità entro il {{date}}. expired: È necessario un rapido passaggio di verifica per continuare a usare Documenti su IO. + action: Inizia MDL: expiring: title: "Patente su IO: documento in scadenza" @@ -3549,6 +3550,13 @@ features: openPdf: "Mostra documento" shareButton: Salva o condividi fiscalCode: Il tuo Codice Fiscale + eid: + verificationExpired: + title: Verifica la tua identità + contentStart: "È un passaggio di sicurezza necessario per continuare ad usare " + contentBold: Documenti su IO + contentEnd: "." + primaryAction: Inizia statusAttestationUnknown: title: Non siamo riusciti a caricare {{credentialName}} content: Chiudi e riapri l'app per riprovare. diff --git a/ts/features/itwallet/common/components/ItwEidInfoBottomSheetContent.tsx b/ts/features/itwallet/common/components/ItwEidInfoBottomSheetContent.tsx index 16e950f72d4..a74e4839f52 100644 --- a/ts/features/itwallet/common/components/ItwEidInfoBottomSheetContent.tsx +++ b/ts/features/itwallet/common/components/ItwEidInfoBottomSheetContent.tsx @@ -96,7 +96,7 @@ const ItwEidInfoBottomSheetContent = ({ ))} - + = [ "valid", @@ -27,17 +29,28 @@ type Props = { * The eID statuses that will render the alert. */ lifecycleStatus?: Array; + navigation: ReturnType; }; /** * This component renders an alert that displays information on the eID status. */ export const ItwEidLifecycleAlert = ({ - lifecycleStatus = defaultLifecycleStatus + lifecycleStatus = defaultLifecycleStatus, + navigation }: Props) => { const eidOption = useIOSelector(itwCredentialsEidSelector); const maybeEidStatus = useIOSelector(itwCredentialsEidStatusSelector); + const startEidReissuing = () => { + navigation.navigate(ITW_ROUTES.MAIN, { + screen: ITW_ROUTES.IDENTIFICATION.MODE_SELECTION, + params: { + eidReissuing: true + } + }); + }; + const Content = ({ eid, eidStatus @@ -73,14 +86,22 @@ export const ItwEidLifecycleAlert = ({ { date: format(eid.jwt.expiration, "DD-MM-YYYY") } - ) + ), + action: I18n.t( + "features.itWallet.presentation.bottomSheets.eidInfo.alert.action" + ), + onPress: startEidReissuing }, jwtExpired: { testID: "itwEidLifecycleAlertTestID_jwtExpired", variant: "error", content: I18n.t( "features.itWallet.presentation.bottomSheets.eidInfo.alert.expired" - ) + ), + action: I18n.t( + "features.itWallet.presentation.bottomSheets.eidInfo.alert.action" + ), + onPress: startEidReissuing } }; diff --git a/ts/features/itwallet/identification/screens/ItwIdentificationModeSelectionScreen.tsx b/ts/features/itwallet/identification/screens/ItwIdentificationModeSelectionScreen.tsx index 153775d6aa9..646d1932fc7 100644 --- a/ts/features/itwallet/identification/screens/ItwIdentificationModeSelectionScreen.tsx +++ b/ts/features/itwallet/identification/screens/ItwIdentificationModeSelectionScreen.tsx @@ -16,9 +16,20 @@ import { } from "../../analytics"; import { IOScrollViewWithLargeHeader } from "../../../../components/ui/IOScrollViewWithLargeHeader"; import { isCIEAuthenticationSupportedSelector } from "../../machine/eid/selectors"; +import { IOStackNavigationRouteProps } from "../../../../navigation/params/AppParamsList"; +import { ItwParamsList } from "../../navigation/ItwParamsList"; import { itwDisabledIdentificationMethodsSelector } from "../../common/store/selectors/remoteConfig"; -export const ItwIdentificationModeSelectionScreen = () => { +export type ItwIdentificationModeSelectionScreenNavigationParams = { + eidReissuing?: boolean; +}; + +type ScreenProps = IOStackNavigationRouteProps< + ItwParamsList, + "ITW_IDENTIFICATION_MODE_SELECTION" +>; + +export const ItwIdentificationModeSelectionScreen = (params: ScreenProps) => { const machineRef = ItwEidIssuanceMachineContext.useActorRef(); const isCieAuthenticationSupported = ItwEidIssuanceMachineContext.useSelector( isCIEAuthenticationSupportedSelector @@ -45,6 +56,16 @@ export const ItwIdentificationModeSelectionScreen = () => { [isCieAuthenticationSupported] ); + const { eidReissuing } = params.route.params; + + useFocusEffect( + useCallback(() => { + if (eidReissuing) { + machineRef.send({ type: "start-reissuing" }); + } + }, [eidReissuing, machineRef]) + ); + useFocusEffect(trackItWalletIDMethod); const handleSpidPress = useCallback(() => { diff --git a/ts/features/itwallet/identification/screens/__tests__/ItwIdentificationModeSelectionScreen.test.tsx b/ts/features/itwallet/identification/screens/__tests__/ItwIdentificationModeSelectionScreen.test.tsx index 6ed8b90582c..83d026db21d 100644 --- a/ts/features/itwallet/identification/screens/__tests__/ItwIdentificationModeSelectionScreen.test.tsx +++ b/ts/features/itwallet/identification/screens/__tests__/ItwIdentificationModeSelectionScreen.test.tsx @@ -15,6 +15,8 @@ import { ItwEidIssuanceMachineContext } from "../../../machine/provider"; import { itwEidIssuanceMachine } from "../../../machine/eid/machine"; import { ItwLifecycleState } from "../../../lifecycle/store/reducers"; import { ToolEnum } from "../../../../../../definitions/content/AssistanceToolConfig"; +import { IOStackNavigationProp } from "../../../../../navigation/params/AppParamsList"; +import { ItwParamsList } from "../../../navigation/ItwParamsList"; jest.mock("../../../../../config", () => ({ itwEnabled: true @@ -128,10 +130,29 @@ describe("ItwIdentificationModeSelectionScreen", () => { } }); + const mockNavigation = new Proxy( + {}, + { + get: _ => jest.fn() + } + ) as unknown as IOStackNavigationProp< + ItwParamsList, + "ITW_IDENTIFICATION_MODE_SELECTION" + >; + + const route = { + key: "ITW_IDENTIFICATION_MODE_SELECTION", + name: ITW_ROUTES.IDENTIFICATION.MODE_SELECTION, + params: {} + }; + return renderScreenWithNavigationStoreContext( () => ( - + ), ITW_ROUTES.IDENTIFICATION.MODE_SELECTION, diff --git a/ts/features/itwallet/machine/credential/__tests__/machine.test.ts b/ts/features/itwallet/machine/credential/__tests__/machine.test.ts index 3b02a20a98f..ff6f4fc3a44 100644 --- a/ts/features/itwallet/machine/credential/__tests__/machine.test.ts +++ b/ts/features/itwallet/machine/credential/__tests__/machine.test.ts @@ -126,6 +126,8 @@ describe("itwCredentialIssuanceMachine", () => { const isDeferredIssuance = jest.fn(); const hasValidWalletInstanceAttestation = jest.fn(); const isStatusError = jest.fn(); + const isSkipNavigation = jest.fn(); + const isWalletValid = jest.fn(); const mockedMachine = itwCredentialIssuanceMachine.provide({ actions: { @@ -163,13 +165,17 @@ describe("itwCredentialIssuanceMachine", () => { isSessionExpired, isDeferredIssuance, hasValidWalletInstanceAttestation, - isStatusError + isStatusError, + isSkipNavigation, + isWalletValid } }); beforeEach(() => { onInit.mockImplementation(() => ({ walletInstanceAttestation: undefined })); hasValidWalletInstanceAttestation.mockImplementation(() => false); + isWalletValid.mockImplementation(() => true); + isSkipNavigation.mockImplementation(() => true); }); afterEach(() => { @@ -595,6 +601,7 @@ describe("itwCredentialIssuanceMachine", () => { actor.start(); await waitFor(() => expect(onInit).toHaveBeenCalledTimes(1)); + isSkipNavigation.mockImplementation(() => false); requestCredential.mockImplementation(() => Promise.resolve({ diff --git a/ts/features/itwallet/machine/credential/actions.ts b/ts/features/itwallet/machine/credential/actions.ts index ee43befc3b9..6ac21142d04 100644 --- a/ts/features/itwallet/machine/credential/actions.ts +++ b/ts/features/itwallet/machine/credential/actions.ts @@ -73,6 +73,12 @@ export default ( }); }, + navigateToEidVerificationExpiredScreen: () => { + navigation.navigate(ITW_ROUTES.MAIN, { + screen: ITW_ROUTES.PRESENTATION.EID_VERIFICATION_EXPIRED + }); + }, + closeIssuance: () => { navigation.popToTop(); }, diff --git a/ts/features/itwallet/machine/credential/guards.ts b/ts/features/itwallet/machine/credential/guards.ts index 3104fbc5d6f..0ff06d45522 100644 --- a/ts/features/itwallet/machine/credential/guards.ts +++ b/ts/features/itwallet/machine/credential/guards.ts @@ -2,24 +2,36 @@ import { pipe } from "fp-ts/lib/function"; import * as O from "fp-ts/lib/Option"; import { ItwSessionExpiredError } from "../../api/client"; import { isWalletInstanceAttestationValid } from "../../common/utils/itwAttestationUtils"; +import { useIOStore } from "../../../../store/hooks"; +import { itwLifecycleIsValidSelector } from "../../lifecycle/store/selectors"; import { Context } from "./context"; import { CredentialIssuanceEvents } from "./events"; import { CredentialIssuanceFailureType } from "./failure"; -export const createCredentialIssuanceGuardsImplementation = () => ({ - isSessionExpired: ({ event }: { event: CredentialIssuanceEvents }) => - "error" in event && event.error instanceof ItwSessionExpiredError, +export const createCredentialIssuanceGuardsImplementation = () => { + // eslint-disable-next-line react-hooks/rules-of-hooks + const store = useIOStore(); - isDeferredIssuance: ({ context }: { context: Context }) => - context.failure?.type === CredentialIssuanceFailureType.ASYNC_ISSUANCE, + return { + isSessionExpired: ({ event }: { event: CredentialIssuanceEvents }) => + "error" in event && event.error instanceof ItwSessionExpiredError, - hasValidWalletInstanceAttestation: ({ context }: { context: Context }) => - pipe( - O.fromNullable(context.walletInstanceAttestation), - O.map(isWalletInstanceAttestationValid), - O.getOrElse(() => false) - ), + isDeferredIssuance: ({ context }: { context: Context }) => + context.failure?.type === CredentialIssuanceFailureType.ASYNC_ISSUANCE, - isStatusError: ({ context }: { context: Context }) => - context.failure?.type === CredentialIssuanceFailureType.INVALID_STATUS -}); + hasValidWalletInstanceAttestation: ({ context }: { context: Context }) => + pipe( + O.fromNullable(context.walletInstanceAttestation), + O.map(isWalletInstanceAttestationValid), + O.getOrElse(() => false) + ), + + isStatusError: ({ context }: { context: Context }) => + context.failure?.type === CredentialIssuanceFailureType.INVALID_STATUS, + + isSkipNavigation: ({ event }: { event: CredentialIssuanceEvents }) => + event.type === "select-credential" && event.skipNavigation === true, + + isWalletValid: () => itwLifecycleIsValidSelector(store.getState()) + }; +}; diff --git a/ts/features/itwallet/machine/credential/machine.ts b/ts/features/itwallet/machine/credential/machine.ts index beacfbffc9b..ee1facbf38d 100644 --- a/ts/features/itwallet/machine/credential/machine.ts +++ b/ts/features/itwallet/machine/credential/machine.ts @@ -1,4 +1,4 @@ -import { assign, fromPromise, not, setup } from "xstate"; +import { and, assign, fromPromise, not, setup } from "xstate"; import { StoredCredential } from "../../common/utils/itwTypesUtils"; import { ItwTags } from "../tags"; import { @@ -28,6 +28,7 @@ export const itwCredentialIssuanceMachine = setup({ navigateToCredentialPreviewScreen: notImplemented, navigateToFailureScreen: notImplemented, navigateToWallet: notImplemented, + navigateToEidVerificationExpiredScreen: notImplemented, closeIssuance: notImplemented, storeWalletInstanceAttestation: notImplemented, storeCredential: notImplemented, @@ -58,7 +59,9 @@ export const itwCredentialIssuanceMachine = setup({ isSessionExpired: notImplemented, isDeferredIssuance: notImplemented, hasValidWalletInstanceAttestation: notImplemented, - isStatusError: notImplemented + isStatusError: notImplemented, + isWalletValid: notImplemented, + isSkipNavigation: notImplemented } }).createMachine({ id: "itwCredentialIssuanceMachine", @@ -72,7 +75,12 @@ export const itwCredentialIssuanceMachine = setup({ on: { "select-credential": [ { - guard: ({ event }) => event.skipNavigation === true, + guard: and([not("isWalletValid"), "isSkipNavigation"]), + actions: "navigateToEidVerificationExpiredScreen", + target: "Idle" + }, + { + guard: "isSkipNavigation", target: "CheckingWalletInstanceAttestation", actions: [ assign(({ event }) => ({ diff --git a/ts/features/itwallet/machine/eid/__tests__/machine.test.ts b/ts/features/itwallet/machine/eid/__tests__/machine.test.ts index 751df57822b..563cc138e69 100644 --- a/ts/features/itwallet/machine/eid/__tests__/machine.test.ts +++ b/ts/features/itwallet/machine/eid/__tests__/machine.test.ts @@ -94,7 +94,10 @@ describe("itwEidIssuanceMachine", () => { resetWalletInstance, trackWalletInstanceCreation, trackWalletInstanceRevocation, - onInit: assign(onInit) + onInit: assign(onInit), + setIsReissuing: assign({ + isReissuing: true + }) }, actors: { createWalletInstance: fromPromise(createWalletInstance), @@ -985,6 +988,185 @@ describe("itwEidIssuanceMachine", () => { expect(actor.getSnapshot().value).toStrictEqual("Idle"); }); + it("Should obtain an eID (SPID), reissuing mode", async () => { + // The wallet instance and attestation already exist + const initialContext = { + ...InitialContext, + integrityKeyTag: T_INTEGRITY_KEY, + walletInstanceAttestation: T_WIA + }; + + const actor = createActor(mockedMachine); + actor.start(); + + // eslint-disable-next-line functional/immutable-data + actor.getSnapshot().context = initialContext; + + hasValidWalletInstanceAttestation.mockImplementation(() => true); + + await waitFor(() => expect(onInit).toHaveBeenCalledTimes(1)); + + expect(actor.getSnapshot().value).toStrictEqual("Idle"); + expect(actor.getSnapshot().tags).toStrictEqual(new Set()); + + actor.send({ type: "start-reissuing" }); + + expect(actor.getSnapshot().value).toStrictEqual({ + UserIdentification: "ModeSelection" + }); + + expect(actor.getSnapshot().context).toStrictEqual({ + ...initialContext, + isReissuing: true + }); + + /** + * Choose SPID as identification mode + */ + + actor.send({ type: "select-identification-mode", mode: "spid" }); + + expect(actor.getSnapshot().value).toStrictEqual({ + UserIdentification: { + Spid: "IdpSelection" + } + }); + expect(actor.getSnapshot().tags).toStrictEqual(new Set()); + expect(navigateToIdpSelectionScreen).toHaveBeenCalledTimes(1); + + /** + * Choose first IDP in list for SPID identification + */ + + startAuthFlow.mockImplementation(() => Promise.resolve({})); + + requestEid.mockImplementation(() => + Promise.resolve(ItwStoredCredentialsMocks.eid) + ); + + issuedEidMatchesAuthenticatedUser.mockImplementation(() => true); + + actor.send({ type: "select-spid-idp", idp: idps[0] }); + + expect(actor.getSnapshot().value).toStrictEqual({ + UserIdentification: { + Spid: "StartingSpidAuthFlow" + } + }); + + expect(actor.getSnapshot().context).toStrictEqual({ + ...initialContext, + integrityKeyTag: T_INTEGRITY_KEY, + walletInstanceAttestation: T_WIA, + isReissuing: true, + identification: { + mode: "spid", + idpId: idps[0].id + } + }); + + expect(actor.getSnapshot().tags).toStrictEqual(new Set([ItwTags.Loading])); + + await waitFor(() => expect(startAuthFlow).toHaveBeenCalledTimes(1)); + + expect(actor.getSnapshot().value).toStrictEqual({ + UserIdentification: { + Spid: "CompletingSpidAuthFlow" + } + }); + + actor.send({ + type: "user-identification-completed", + authRedirectUrl: "http://test.it" + }); + + expect(actor.getSnapshot().value).toStrictEqual({ + Issuance: "RequestingEid" + }); + + expect(actor.getSnapshot().tags).toStrictEqual(new Set([ItwTags.Loading])); + expect(actor.getSnapshot().context).toMatchObject({ + authenticationContext: { + callbackUrl: "http://test.it" + } + }); + expect(navigateToEidPreviewScreen).toHaveBeenCalledTimes(1); + + // EID obtained + + // eslint-disable-next-line sonarjs/no-identical-functions + await waitFor(() => + expect(actor.getSnapshot().value).toStrictEqual({ + Issuance: "DisplayingPreview" + }) + ); + + actor.send({ type: "add-to-wallet" }); + + expect(storeEidCredential).toHaveBeenCalledTimes(1); + expect(setWalletInstanceToValid).toHaveBeenCalledTimes(1); + expect(navigateToWallet).toHaveBeenCalledTimes(1); + + expect(actor.getSnapshot().context).toStrictEqual({ + ...initialContext, + integrityKeyTag: T_INTEGRITY_KEY, + walletInstanceAttestation: T_WIA, + isReissuing: true, + identification: { + mode: "spid", + idpId: idps[0].id + }, + authenticationContext: expect.objectContaining({ + callbackUrl: "http://test.it" + }), + eid: ItwStoredCredentialsMocks.eid + }); + }); + + it("Should go back to Idle state if isReissuing is true", async () => { + const initialSnapshot: MachineSnapshot = createActor( + itwEidIssuanceMachine + ).getSnapshot(); + + const snapshot: MachineSnapshot = _.merge(undefined, initialSnapshot, { + value: { UserIdentification: "ModeSelection" }, + context: { + isReissuing: true + } + } as MachineSnapshot); + + const actor = createActor(mockedMachine, { + snapshot + }); + actor.start(); + + actor.send({ type: "back" }); + + expect(actor.getSnapshot().value).toStrictEqual("Idle"); + }); + + it("Should go back to IpzsPrivacyAcceptance state if isReissuing is false", async () => { + const initialSnapshot: MachineSnapshot = createActor( + itwEidIssuanceMachine + ).getSnapshot(); + + const snapshot: MachineSnapshot = _.merge(undefined, initialSnapshot, { + value: { UserIdentification: "ModeSelection" }, + context: { + isReissuing: false + } + } as MachineSnapshot); + + const actor = createActor(mockedMachine, { + snapshot + }); + actor.start(); + + actor.send({ type: "back" }); + + expect(actor.getSnapshot().value).toStrictEqual("IpzsPrivacyAcceptance"); + }); + it("should cleanup integrity key tag and fail when obtaining Wallet Instance Attestation fails", async () => { const actor = createActor(mockedMachine); actor.start(); diff --git a/ts/features/itwallet/machine/eid/actions.ts b/ts/features/itwallet/machine/eid/actions.ts index 2572a537d9f..f25376362a7 100644 --- a/ts/features/itwallet/machine/eid/actions.ts +++ b/ts/features/itwallet/machine/eid/actions.ts @@ -49,7 +49,8 @@ export const createEidIssuanceActionsImplementation = ( navigateToIdentificationModeScreen: () => { navigation.navigate(ITW_ROUTES.MAIN, { - screen: ITW_ROUTES.IDENTIFICATION.MODE_SELECTION + screen: ITW_ROUTES.IDENTIFICATION.MODE_SELECTION, + params: { eidReissuing: false } }); }, diff --git a/ts/features/itwallet/machine/eid/context.ts b/ts/features/itwallet/machine/eid/context.ts index be98c299ac2..b6e707c1ed2 100644 --- a/ts/features/itwallet/machine/eid/context.ts +++ b/ts/features/itwallet/machine/eid/context.ts @@ -41,6 +41,7 @@ export type Context = { authenticationContext: AuthenticationContext | undefined; eid: StoredCredential | undefined; failure: IssuanceFailure | undefined; + isReissuing: boolean; }; export const InitialContext: Context = { @@ -50,5 +51,6 @@ export const InitialContext: Context = { identification: undefined, authenticationContext: undefined, eid: undefined, - failure: undefined + failure: undefined, + isReissuing: false }; diff --git a/ts/features/itwallet/machine/eid/events.ts b/ts/features/itwallet/machine/eid/events.ts index 4868c1ac44b..e76b285e0af 100644 --- a/ts/features/itwallet/machine/eid/events.ts +++ b/ts/features/itwallet/machine/eid/events.ts @@ -82,6 +82,10 @@ export type ExternalErrorEvent = { error?: Error; }; +export type StartReissuing = { + type: "start-reissuing"; +}; + export type EidIssuanceEvents = | Reset | Start @@ -101,4 +105,5 @@ export type EidIssuanceEvents = | Abort | RevokeWalletInstance | ErrorActorEvent - | ExternalErrorEvent; + | ExternalErrorEvent + | StartReissuing; diff --git a/ts/features/itwallet/machine/eid/machine.ts b/ts/features/itwallet/machine/eid/machine.ts index 1d381db258f..cb4c8a62619 100644 --- a/ts/features/itwallet/machine/eid/machine.ts +++ b/ts/features/itwallet/machine/eid/machine.ts @@ -1,5 +1,5 @@ import _ from "lodash"; -import { assertEvent, assign, fromPromise, not, setup } from "xstate"; +import { assertEvent, assign, fromPromise, not, setup, and } from "xstate"; import { assert } from "../../../../utils/assert"; import { StoredCredential } from "../../common/utils/itwTypesUtils"; import { ItwTags } from "../tags"; @@ -72,7 +72,11 @@ export const itwEidIssuanceMachine = setup({ callbackUrl: event.authRedirectUrl } }; - }) + }), + setIsReissuing: assign(({ context }) => ({ + ...context, + isReissuing: true + })) }, actors: { createWalletInstance: fromPromise(notImplemented), @@ -93,7 +97,8 @@ export const itwEidIssuanceMachine = setup({ isSessionExpired: notImplemented, isOperationAborted: notImplemented, hasValidWalletInstanceAttestation: notImplemented, - isNFCEnabled: ({ context }) => context.cieContext?.isNFCEnabled || false + isNFCEnabled: ({ context }) => context.cieContext?.isNFCEnabled || false, + isReissuing: ({ context }) => context.isReissuing === true } }).createMachine({ id: "itwEidIssuanceMachine", @@ -122,7 +127,18 @@ export const itwEidIssuanceMachine = setup({ }, "revoke-wallet-instance": { target: "WalletInstanceRevocation" - } + }, + "start-reissuing": [ + { + guard: not("hasValidWalletInstanceAttestation"), + actions: "setIsReissuing", + target: "WalletInstanceAttestationObtainment" + }, + { + actions: "setIsReissuing", + target: "UserIdentification" + } + ] } }, TosAcceptance: { @@ -210,16 +226,33 @@ export const itwEidIssuanceMachine = setup({ invoke: { src: "getWalletAttestation", input: ({ context }) => ({ integrityKeyTag: context.integrityKeyTag }), - onDone: { - actions: [ - assign(({ event }) => ({ - walletInstanceAttestation: event.output - })), - { type: "storeWalletInstanceAttestation" } - ], - target: "IpzsPrivacyAcceptance" - }, + onDone: [ + { + guard: "isReissuing", + actions: [ + assign(({ event }) => ({ + walletInstanceAttestation: event.output + })), + { type: "storeWalletInstanceAttestation" } + ], + target: "UserIdentification" + }, + { + actions: [ + assign(({ event }) => ({ + walletInstanceAttestation: event.output + })), + { type: "storeWalletInstanceAttestation" } + ], + target: "IpzsPrivacyAcceptance" + } + ], onError: [ + { + guard: and(["isReissuing", "isSessionExpired"]), + actions: ["handleSessionExpired", "closeIssuance"], + target: "#itwEidIssuanceMachine.Idle" + }, { guard: "isSessionExpired", actions: "handleSessionExpired", @@ -275,7 +308,15 @@ export const itwEidIssuanceMachine = setup({ target: "CieID" } ], - back: "#itwEidIssuanceMachine.IpzsPrivacyAcceptance" + back: [ + { + guard: "isReissuing", + target: "#itwEidIssuanceMachine.Idle" + }, + { + target: "#itwEidIssuanceMachine.IpzsPrivacyAcceptance" + } + ] } }, CieID: { @@ -555,14 +596,25 @@ export const itwEidIssuanceMachine = setup({ }, DisplayingPreview: { on: { - "add-to-wallet": { - actions: [ - "storeEidCredential", - "setWalletInstanceToValid", - "trackWalletInstanceCreation" - ], - target: "#itwEidIssuanceMachine.Success" - }, + "add-to-wallet": [ + { + guard: "isReissuing", + actions: [ + "storeEidCredential", + "setWalletInstanceToValid", + "trackWalletInstanceCreation", + "navigateToWallet" + ] + }, + { + actions: [ + "storeEidCredential", + "setWalletInstanceToValid", + "trackWalletInstanceCreation" + ], + target: "#itwEidIssuanceMachine.Success" + } + ], close: { actions: ["closeIssuance"] } diff --git a/ts/features/itwallet/navigation/ItwParamsList.ts b/ts/features/itwallet/navigation/ItwParamsList.ts index 3654b6fa7bd..ec1722a7e4c 100644 --- a/ts/features/itwallet/navigation/ItwParamsList.ts +++ b/ts/features/itwallet/navigation/ItwParamsList.ts @@ -1,3 +1,4 @@ +import { ItwIdentificationModeSelectionScreenNavigationParams } from "../identification/screens/ItwIdentificationModeSelectionScreen"; import { ItwCieWrongCiePinScreenNavigationParams } from "../identification/screens/cie/ItwCieWrongCiePinScreen"; import { ItwIssuanceCredentialAsyncContinuationNavigationParams } from "../issuance/screens/ItwIssuanceCredentialAsyncContinuationScreen"; import { ItwPresentationCredentialAttachmentNavigationParams } from "../presentation/screens/ItwPresentationCredentialAttachmentScreen"; @@ -13,7 +14,8 @@ export type ItwParamsList = { [ITW_ROUTES.DISCOVERY.IPZS_PRIVACY]: undefined; [ITW_ROUTES.DISCOVERY.ALREADY_ACTIVE_SCREEN]: undefined; // IDENTIFICATION - [ITW_ROUTES.IDENTIFICATION.MODE_SELECTION]: undefined; + [ITW_ROUTES.IDENTIFICATION + .MODE_SELECTION]: ItwIdentificationModeSelectionScreenNavigationParams; // IDENTIFICATION SPID [ITW_ROUTES.IDENTIFICATION.IDP_SELECTION]: undefined; [ITW_ROUTES.IDENTIFICATION.SPID.LOGIN]: undefined; @@ -46,6 +48,7 @@ export type ItwParamsList = { [ITW_ROUTES.PRESENTATION .CREDENTIAL_CARD_MODAL]: ItwPresentationCredentialCardModalNavigationParams; [ITW_ROUTES.PRESENTATION.CREDENTIAL_FISCAL_CODE_MODAL]: undefined; + [ITW_ROUTES.PRESENTATION.EID_VERIFICATION_EXPIRED]: undefined; // PLAYGROUNDS [ITW_ROUTES.PLAYGROUNDS]: undefined; [ITW_ROUTES.IDENTITY_NOT_MATCHING_SCREEN]: undefined; diff --git a/ts/features/itwallet/navigation/ItwStackNavigator.tsx b/ts/features/itwallet/navigation/ItwStackNavigator.tsx index ebbf4174a5b..8bcc5cf073f 100644 --- a/ts/features/itwallet/navigation/ItwStackNavigator.tsx +++ b/ts/features/itwallet/navigation/ItwStackNavigator.tsx @@ -40,6 +40,7 @@ import ItwCieIdLoginScreen from "../identification/screens/cieId/ItwCieIdLoginSc import { ItwPresentationCredentialFiscalCodeModal } from "../presentation/screens/ItwPresentationCredentialFiscalCodeModal"; import { ItwCredentialTrustmarkScreen } from "../trustmark/screens/ItwCredentialTrustmarkScreen"; import { ItwAlreadyActiveScreen } from "../discovery/screens/ItwAlreadyActiveScreen"; +import { ItwPresentationEidVerificationExpiredScreen } from "../presentation/screens/ItwPresentationEidVerificationExpiredScreen"; import { ItwParamsList } from "./ItwParamsList"; import { ITW_ROUTES } from "./routes"; @@ -225,6 +226,11 @@ const InnerNavigator = () => { component={ItwLifecycleWalletRevocationScreen} options={{ headerShown: false, gestureEnabled: false }} /> + ); }; diff --git a/ts/features/itwallet/navigation/routes.ts b/ts/features/itwallet/navigation/routes.ts index 293a04cbba1..6315e98c88b 100644 --- a/ts/features/itwallet/navigation/routes.ts +++ b/ts/features/itwallet/navigation/routes.ts @@ -42,7 +42,8 @@ export const ITW_ROUTES = { CREDENTIAL_TRUSTMARK: "ITW_PRESENTATION_CREDENTIAL_TRUSTMARK", CREDENTIAL_CARD_MODAL: "ITW_PRESENTATION_CREDENTIAL_CARD_MODAL", CREDENTIAL_FISCAL_CODE_MODAL: - "ITW_PRESENTATION_CREDENTIAL_FISCAL_CODE_MODAL" + "ITW_PRESENTATION_CREDENTIAL_FISCAL_CODE_MODAL", + EID_VERIFICATION_EXPIRED: "ITW_PRESENTATION_EID_VERIFICATION_EXPIRED" } as const, PLAYGROUNDS: "ITW_PLAYGROUNDS" as const, IDENTITY_NOT_MATCHING_SCREEN: "ITW_IDENTITY_NOT_MATCHING_SCREEN" as const, diff --git a/ts/features/itwallet/presentation/screens/ItwPresentationEidVerificationExpiredScreen.tsx b/ts/features/itwallet/presentation/screens/ItwPresentationEidVerificationExpiredScreen.tsx new file mode 100644 index 00000000000..f4d4b072186 --- /dev/null +++ b/ts/features/itwallet/presentation/screens/ItwPresentationEidVerificationExpiredScreen.tsx @@ -0,0 +1,70 @@ +import React, { useMemo } from "react"; +import { BodyProps } from "@pagopa/io-app-design-system"; +import { OperationResultScreenContent } from "../../../../components/screens/OperationResultScreenContent"; +import I18n from "../../../../i18n"; +import { useIONavigation } from "../../../../navigation/params/AppParamsList"; +import { ITW_ROUTES } from "../../navigation/routes"; + +export const ItwPresentationEidVerificationExpiredScreen = () => { + const navigation = useIONavigation(); + + const startEidReissuing = () => { + navigation.navigate(ITW_ROUTES.MAIN, { + screen: ITW_ROUTES.IDENTIFICATION.MODE_SELECTION, + params: { + eidReissuing: true + } + }); + }; + + const bodyPropsArray: Array = useMemo( + () => [ + { + text: I18n.t( + "features.itWallet.presentation.eid.verificationExpired.contentStart" + ), + style: { + textAlign: "center" + } + }, + { + text: I18n.t( + "features.itWallet.presentation.eid.verificationExpired.contentBold" + ), + style: { + textAlign: "center", + fontWeight: "bold" + } + }, + { + text: I18n.t( + "features.itWallet.presentation.eid.verificationExpired.contentEnd" + ), + style: { + textAlign: "center" + } + } + ], + [] + ); + + return ( + navigation.goBack() + }} + /> + ); +}; diff --git a/ts/features/wallet/components/WalletCardsContainer.tsx b/ts/features/wallet/components/WalletCardsContainer.tsx index e045750bf7d..d3b0b1faa5b 100644 --- a/ts/features/wallet/components/WalletCardsContainer.tsx +++ b/ts/features/wallet/components/WalletCardsContainer.tsx @@ -176,6 +176,7 @@ const ItwWalletCardsContainer = withWalletCategoryFilter("itw", () => { }