Skip to content

Commit

Permalink
feat(IT Wallet): [SIW-1584] Add IPZS privacy screen (#6270)
Browse files Browse the repository at this point in the history
## Short description
This PR adds a new screen regarding the IPZS privacy policy that must be
reviewed before proceeding and logging in via SPID/CIE.

## List of changes proposed in this pull request
- Added a new component used to display a `WebView` inside the
`ItwIpzsPrivacyScreen`
- Added a new machine state that handles the new screen inside the
discovery flow
- Removed the privacy policy acceptance text inside the
`ItwIdentificationModeSelectionScreen`
- Added the mixpanel tracking event inside the new screen

## How to test
- Start the new documents flow from the payment section
- Navigate to this screen after the onboarding/discovery screen (The
first screen inside the WIA flow)
- ~~Read the policy and scroll to the bottom to enable the continue
button~~
- Scroll down the policy (not mandatory now) and press the continue
button to navigate to the next screen

**NOTE**: I cannot test this PR on a real Android device since I don't
have one.
Can someone test on Android (physical device)? Thanks!

DEMO:



https://github.com/user-attachments/assets/ba9781d3-dffa-4132-9274-cc43c6c7a991

---------

Co-authored-by: ITW Development <[email protected]>
Co-authored-by: Mario Perrotta <[email protected]>
Co-authored-by: LazyAfternoons <[email protected]>
  • Loading branch information
4 people authored Oct 17, 2024
1 parent 7492e94 commit d15d93f
Show file tree
Hide file tree
Showing 16 changed files with 241 additions and 17 deletions.
2 changes: 2 additions & 0 deletions .env.local
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,5 @@ ITW_ISSUANCE_REDIRECT_URI_CIE="iowalletcie://cb"
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'
2 changes: 2 additions & 0 deletions .env.production
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,5 @@ ITW_ISSUANCE_REDIRECT_URI_CIE="iowalletcie://cb"
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'
6 changes: 5 additions & 1 deletion locales/en/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3215,6 +3215,11 @@ features:
mdl: Patente di Guida
dc: Carta Europea della Disabilità
ts: Tessera Sanitaria - Tessera europea di assicurazione malattia
ipzsPrivacy:
title: I tuoi Documenti su IO sono al sicuro
warning: Premendo **Continua** dichiari di aver letto e compreso l’**Informativa Privacy**.
button:
label: Continua
wallet:
active: Attivo
inactive: Non attivo
Expand Down Expand Up @@ -3287,7 +3292,6 @@ features:
cieId:
title: CieID
subtitle: Usa credenziali e app CieID
privacy: Identificandoti dichiari di aver letto e compreso l’[Informativa Privacy](https://io.italia.it/informativa-ipzs-sperimentazione) di **Istituto Poligrafico e Zecca dello Stato**.
nfc:
title: Attiva l'NFC per continuare
description: Per consentire a IO di leggere la tua CIE, attiva l'NFC dalle Impostazioni del tuo dispositivo.
Expand Down
6 changes: 5 additions & 1 deletion locales/it/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3215,6 +3215,11 @@ features:
mdl: Patente di Guida
dc: Carta Europea della Disabilità
ts: Tessera Sanitaria - Tessera europea di assicurazione malattia
ipzsPrivacy:
title: I tuoi Documenti su IO sono al sicuro
warning: Premendo **Continua** dichiari di aver letto e compreso l’**Informativa Privacy**.
button:
label: Continua
wallet:
active: Attivo
inactive: Non attivo
Expand Down Expand Up @@ -3287,7 +3292,6 @@ features:
cieId:
title: CieID
subtitle: Usa credenziali e app CieID
privacy: Identificandoti dichiari di aver letto e compreso l’[Informativa Privacy](https://io.italia.it/informativa-ipzs-sperimentazione) di **Istituto Poligrafico e Zecca dello Stato**.
nfc:
title: Attiva l'NFC per continuare
description: Per consentire a IO di leggere la tua CIE, attiva l'NFC dalle Impostazioni del tuo dispositivo.
Expand Down
5 changes: 5 additions & 0 deletions ts/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,3 +255,8 @@ export const itwEaaVerifierBaseUrl = Config.ITW_EAA_VERIFIER_BASE_URL;
export const itwBypassIdentityMatch =
Config.ITW_BYPASS_IDENTITY_MATCH === "YES";
export const itwIdpHintTest = Config.ITW_IDP_HINT_TEST === "YES";
export const itwIpzsPrivacyUrl: string = pipe(
Config.ITW_IPZS_PRIVACY_URL,
t.string.decode,
E.getOrElse(() => "https://io.italia.it/informativa-ipzs")
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React from "react";
import {
ContentWrapper,
IOStyles,
VSpacer
} from "@pagopa/io-app-design-system";
import WebView from "react-native-webview";
import { View } from "react-native";
import { WebViewSource } from "react-native-webview/lib/WebViewTypes";
import { AVOID_ZOOM_JS, closeInjectedScript } from "../../../../utils/webview";
import I18n from "../../../../i18n";
import ItwMarkdown from "../../common/components/ItwMarkdown";
import { FooterActions } from "../../../../components/ui/FooterActions";

type Props = {
source: WebViewSource;
onAcceptTos: () => void;
onLoadEnd: () => void;
onError: () => void;
};

const ItwPrivacyWebViewComponent = ({
source,
onAcceptTos,
onLoadEnd,
onError
}: Props) => (
<View style={IOStyles.flex}>
<WebView
androidCameraAccessDisabled={true}
androidMicrophoneAccessDisabled={true}
onLoadEnd={onLoadEnd}
onError={onError}
textZoom={100}
style={IOStyles.flex}
source={source}
injectedJavaScript={closeInjectedScript(AVOID_ZOOM_JS)}
/>

<ContentWrapper>
<VSpacer size={4} />
<ItwMarkdown>
{I18n.t("features.itWallet.ipzsPrivacy.warning")}
</ItwMarkdown>
</ContentWrapper>
<FooterActions
fixed={false}
actions={{
type: "SingleButton",
primary: {
label: I18n.t("features.itWallet.ipzsPrivacy.button.label"),
accessibilityLabel: I18n.t(
"features.itWallet.ipzsPrivacy.button.label"
),
onPress: onAcceptTos
}
}}
/>
</View>
);

export default ItwPrivacyWebViewComponent;
60 changes: 60 additions & 0 deletions ts/features/itwallet/discovery/screens/ItwIpzsPrivacyScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React, { useState } from "react";
import { View } from "react-native";
import { H2, IOStyles, VSpacer } from "@pagopa/io-app-design-system";
import { useHeaderSecondLevel } from "../../../../hooks/useHeaderSecondLevel";
import I18n from "../../../../i18n";
import LoadingSpinnerOverlay from "../../../../components/LoadingSpinnerOverlay";
import ItwPrivacyWebViewComponent from "../components/ItwPrivacyWebViewComponent";
import { ItwEidIssuanceMachineContext } from "../../machine/provider";
import { trackOpenItwTosAccepted } from "../../analytics";
import { itwIpzsPrivacyUrl } from "../../../../config";

const ItwIpzsPrivacyScreen = () => {
const [isLoading, setIsLoading] = useState(true);
const machineRef = ItwEidIssuanceMachineContext.useActorRef();

const handleContinuePress = () => {
trackOpenItwTosAccepted();
machineRef.send({ type: "accept-ipzs-privacy" });
};

const onLoadEnd = () => {
setIsLoading(false);
};

const onError = () => {
onLoadEnd();
machineRef.send({ type: "error", scope: "ipzs-privacy" });
};

useHeaderSecondLevel({
title: "",
canGoBack: true,
supportRequest: true
});

return (
<LoadingSpinnerOverlay isLoading={isLoading}>
<View style={IOStyles.horizontalContentPadding}>
<H2
accessible={true}
accessibilityRole="header"
testID="screen-content-header-title"
>
{I18n.t("features.itWallet.ipzsPrivacy.title")}
</H2>
<VSpacer size={24} />
</View>
<ItwPrivacyWebViewComponent
source={{
uri: itwIpzsPrivacyUrl
}}
onAcceptTos={handleContinuePress}
onLoadEnd={onLoadEnd}
onError={onError}
/>
</LoadingSpinnerOverlay>
);
};

export default ItwIpzsPrivacyScreen;
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import * as React from "react";
import { createStore } from "redux";
import { applicationChangeState } from "../../../../../store/actions/application";
import { appReducer } from "../../../../../store/reducers";
import { GlobalState } from "../../../../../store/reducers/types";
import { renderScreenWithNavigationStoreContext } from "../../../../../utils/testWrapper";
import { itwEidIssuanceMachine } from "../../../machine/eid/machine";
import { ItwEidIssuanceMachineContext } from "../../../machine/provider";
import { ITW_ROUTES } from "../../../navigation/routes";
import ItwIpzsPrivacyScreen from "../ItwIpzsPrivacyScreen";

describe("Test ItwIpzsPrivacy screen", () => {
it("it should render the screen correctly", () => {
const component = renderComponent();
expect(component).toBeTruthy();
});
});

const renderComponent = () => {
const globalState = appReducer(undefined, applicationChangeState("active"));

const logic = itwEidIssuanceMachine.provide({
actions: {
onInit: jest.fn(),
navigateToTosScreen: () => undefined
}
});

return renderScreenWithNavigationStoreContext<GlobalState>(
() => (
<ItwEidIssuanceMachineContext.Provider logic={logic}>
<ItwIpzsPrivacyScreen />
</ItwEidIssuanceMachineContext.Provider>
),
ITW_ROUTES.DISCOVERY.IPZS_PRIVACY,
{},
createStore(appReducer, globalState as any)
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {
ContentWrapper,
ListItemHeader,
ModuleNavigation,
VSpacer,
VStack
} from "@pagopa/io-app-design-system";
import * as pot from "@pagopa/ts-commons/lib/pot";
Expand All @@ -13,7 +12,6 @@ import I18n from "../../../../i18n";
import { useIOSelector } from "../../../../store/hooks";
import { cieFlowForDevServerEnabled } from "../../../cieLogin/utils";
import { ItwEidIssuanceMachineContext } from "../../machine/provider";
import ItwMarkdown from "../../common/components/ItwMarkdown";
import { itwIsCieSupportedSelector } from "../store/selectors";
import {
trackItWalletIDMethod,
Expand Down Expand Up @@ -91,10 +89,6 @@ export const ItwIdentificationModeSelectionScreen = () => {
onPress={handleCieIdPress}
/>
</VStack>
<VSpacer size={24} />
<ItwMarkdown>
{I18n.t("features.itWallet.identification.mode.privacy")}
</ItwMarkdown>
</ContentWrapper>
</IOScrollViewWithLargeHeader>
);
Expand Down
20 changes: 16 additions & 4 deletions ts/features/itwallet/machine/eid/__tests__/machine.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const T_WIA: string = "abcdefg";

describe("itwEidIssuanceMachine", () => {
const navigateToTosScreen = jest.fn();
const navigateToIpzsPrivacyScreen = jest.fn();
const navigateToIdentificationModeScreen = jest.fn();
const navigateToIdpSelectionScreen = jest.fn();
const navigateToEidPreviewScreen = jest.fn();
Expand Down Expand Up @@ -54,6 +55,7 @@ describe("itwEidIssuanceMachine", () => {
const mockedMachine = itwEidIssuanceMachine.provide({
actions: {
navigateToTosScreen,
navigateToIpzsPrivacyScreen,
navigateToIdentificationModeScreen,
navigateToIdpSelectionScreen,
navigateToEidPreviewScreen,
Expand Down Expand Up @@ -167,10 +169,16 @@ describe("itwEidIssuanceMachine", () => {

// Wallet instance creation and attestation obtainment success

// Navigate to ipzs privacy screen
expect(actor.getSnapshot().value).toStrictEqual("IpzsPrivacyAcceptance");
expect(actor.getSnapshot().tags).toStrictEqual(new Set());

// Accept IPZS privacy
actor.send({ type: "accept-ipzs-privacy" });
// Navigate to identification mode selection
expect(actor.getSnapshot().value).toStrictEqual({
UserIdentification: "ModeSelection"
});
expect(actor.getSnapshot().tags).toStrictEqual(new Set());

/**
* Choose SPID as identification mode
Expand Down Expand Up @@ -551,12 +559,16 @@ describe("itwEidIssuanceMachine", () => {

actor.send({ type: "accept-tos" });

expect(actor.getSnapshot().value).toStrictEqual({
UserIdentification: "ModeSelection"
});
expect(actor.getSnapshot().value).toStrictEqual("IpzsPrivacyAcceptance");
expect(actor.getSnapshot().tags).toStrictEqual(new Set([]));
expect(createWalletInstance).toHaveBeenCalledTimes(0);
expect(getWalletAttestation).toHaveBeenCalledTimes(0);

// Accept IPZS privacy
actor.send({ type: "accept-ipzs-privacy" });
expect(actor.getSnapshot().value).toStrictEqual({
UserIdentification: "ModeSelection"
});
});

it("Should allow the user to add a new credential once eID issuance is complete", () => {
Expand Down
6 changes: 6 additions & 0 deletions ts/features/itwallet/machine/eid/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ export const createEidIssuanceActionsImplementation = (
});
},

navigateToIpzsPrivacyScreen: () => {
navigation.navigate(ITW_ROUTES.MAIN, {
screen: ITW_ROUTES.DISCOVERY.IPZS_PRIVACY
});
},

navigateToIdentificationModeScreen: () => {
navigation.navigate(ITW_ROUTES.MAIN, {
screen: ITW_ROUTES.IDENTIFICATION.MODE_SELECTION
Expand Down
14 changes: 13 additions & 1 deletion ts/features/itwallet/machine/eid/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ export type AcceptTos = {
type: "accept-tos";
};

export type AcceptIpzsPrivacy = {
type: "accept-ipzs-privacy";
};

export type AddToWallet = {
type: "add-to-wallet";
};
Expand Down Expand Up @@ -72,10 +76,17 @@ export type RevokeWalletInstance = {
type: "revoke-wallet-instance";
};

export type Error = {
type: "error";
// Add a custom error code to the error event to distinguish between different errors. Add a new error code for each different error if needed.
scope: "ipzs-privacy";
};

export type EidIssuanceEvents =
| Reset
| Start
| AcceptTos
| AcceptIpzsPrivacy
| SelectIdentificationMode
| SelectSpidIdp
| CiePinEntered
Expand All @@ -89,4 +100,5 @@ export type EidIssuanceEvents =
| NfcEnabled
| Abort
| RevokeWalletInstance
| ErrorActorEvent;
| ErrorActorEvent
| Error;
Loading

0 comments on commit d15d93f

Please sign in to comment.