diff --git a/locales/de/index.yml b/locales/de/index.yml index ea39956d1ae..69ab1e600b8 100644 --- a/locales/de/index.yml +++ b/locales/de/index.yml @@ -594,11 +594,7 @@ authentication: hint: "Gib in diesem Feld die 8-stellige PIN deiner elektronischen Identitätskarte ein." pinCardTitle: "Gib die PIN deiner Identitätskarte ein" pinCardHeader: "Anmeldung mit CIE" - pinCardContent: "Der PIN-Code besteht aus 8 Ziffern. Vier Ziffern wurden dir beim Meldeamt ausgehändigt und weitere vier Ziffern wurden dir mit der Karte nach Hause geschickt. Setze diese in dieser Reihenfolge zusammen und trage sie bitte hier ein." - incorrectCiePinTitle: "Falsche PIN, du hast noch {{remainingCount}} Versuche" - incorrectCiePinHeaderTitle: "Falsche PIN" - incorrectCiePinContent1: "Zu deiner Sicherheit kannst du die PIN der Identitätskarte maximal dreimal eingeben. Danach musst du die Karte durch Eingabe des PUK-Codes entsperren." - incorrectCiePinContent2: "Beachte, dass der PIN-Code deiner elektronischen Identitätskarte (CIE) aus 8 Ziffern besteht. Vier Ziffern, die dir im Meldeamt ausgehändigt wurden und weitere vier Ziffern, die dir zusammen mit der Karte nach Hause geschickt wurden." + pinCardContent: "Der PIN-Code besteht aus 8 Ziffern. Vier Ziffern wurden dir beim Meldeamt ausgehändigt und weitere vier Ziffern wurden dir mit der Karte nach Hause geschickt. Setze diese in dieser Reihenfolge zusammen und trage sie bitte hier ein." alert: "Möchtest du wirklich zurückgehen?" subtitleHelp: "Die PIN ist ein 8-stelliger Code." subtitleCTA: "Wo finde ich ihn?" @@ -626,10 +622,7 @@ authentication: readingInProgress: "WIRD GELESEN \nHalte die Karte einige Sekunden lang still..." readingSuccess: "Das Auslesen war erfolgreich.\nDu kannst die Karte entfernen während wir die Datenüberprüfung abschließen." invalidCard: "Die verwendete Karte scheint keine elektronische Identitätskarte (CIE) zu sein." - tagLost: "Du hast die Karte zu früh entfernt." - cardLocked: "Identitätskarte gesperrt" - wrongPin1AttemptLeft: "Falsche PIN, du hast noch einen Versuch" - wrongPin2AttemptLeft: "Falsche PIN, du hast noch zwei Versuche" + tagLost: "Du hast die Karte zu früh entfernt." genericError: "Es ist ein unerwarteter Fehler aufgetreten. Bitte versuche es erneut." error: readerCardLostTitle: "Karte neu lesen" diff --git a/locales/en/index.yml b/locales/en/index.yml index 766ede0a67c..241a04863f1 100644 --- a/locales/en/index.yml +++ b/locales/en/index.yml @@ -627,10 +627,14 @@ authentication: pinCardTitle: Enter the PIN of your ID Card pinCardHeader: Login with CIE pinCardContent: The PIN code consists of 8 digits. Four digits have been delivered to you at the registry office and another four digits have been sent to your home along with the card. Put them together, in that order, and enter them here please. - incorrectCiePinTitle: Incorrect PIN, you still have {{remainingCount}} attempts - incorrectCiePinHeaderTitle: Wrong PIN - incorrectCiePinContent1: For your security, you can try entering the card PIN no more than three times. Then you will have to unlock the card by entering the PUK. - incorrectCiePinContent2: Keep in mind that the PIN code of your Electronic Identity Card (CIE) is composed of 8 digits. Four digits that were given to you at the registry office and another four digits that were sent to you at home, along with the card. + incorrectCiePinTitle1: Wrong PIN + incorrectCiePinTitle2: You have entered a wrong PIN twice + lockedCiePinTitle: You have entered a wrong PIN too many times + incorrectCiePinContent1: You still have 2 attempts left, check it and try again. + incorrectCiePinContent2: After the third incorrect attempt, the PIN will be blocked. To unlock it and set a new one, you will need to enter the PUK code into the CieID app. + incorrectCiePinSecondaryActionLabel2: Lost your PIN? + lockedCiePinContent: The PIN of your CIE has been blocked. To unlock it and set a new one, you will need to enter the PUK code into the CieID app. + lockedSecondaryActionLabel: Lost your PUK? alert: Are you sure you want to go back? subtitleHelp: The PIN code consists of 8 digits. subtitleCTA: Where can I find it? @@ -661,9 +665,9 @@ authentication: readingSuccess: "Successful reading.\nYou can remove the card while we complete the data verification." invalidCard: It seems that this card is not an Electronic Identity Card (CIE). tagLost: You removed the card too soon. - cardLocked: CIE card locked - wrongPin1AttemptLeft: Wrong PIN, you still have 1 attempt - wrongPin2AttemptLeft: Wrong PIN, you still have 2 attempts + cardLocked: The CIE PIN is blocked. + wrongPin1AttemptLeft: You have entered a wrong PIN twice + wrongPin2AttemptLeft: Wrong PIN genericError: Something went wrong! Please try again. error: readerCardLostTitle: Hold the card again for a few seconds diff --git a/locales/it/index.yml b/locales/it/index.yml index 7b9ba368d0b..9cd1eadc04a 100644 --- a/locales/it/index.yml +++ b/locales/it/index.yml @@ -627,10 +627,14 @@ authentication: pinCardTitle: Inserisci il PIN della CIE pinCardHeader: Entra con CIE pinCardContent: Il codice PIN è composto da 8 cifre. Quattro cifre che ti sono state consegnate all'anagrafe e altre 4 cifre ti sono state spedite a casa, insieme alla carta. Mettile insieme, in quest'ordine, e inseriscile qui per favore. - incorrectCiePinTitle: PIN errato, hai ancora {{remainingCount}} tentativi - incorrectCiePinHeaderTitle: PIN errato - incorrectCiePinContent1: Per la tua sicurezza puoi provare ad inserire il PIN della carta non più di tre volte. Poi dovrai sbloccare la carta inserendo il PUK. - incorrectCiePinContent2: Ricorda che il codice PIN della tua Carta d'Identità Elettronica (CIE) è composto da 8 cifre. Quattro cifre che ti sono state consegnate all'anagrafe e altre quattro cifre che ti sono state spedite a casa, insieme alla carta. + incorrectCiePinTitle1: Il PIN non è corretto + incorrectCiePinTitle2: Hai inserito un PIN errato per 2 volte + lockedCiePinTitle: Hai inserito un PIN errato per troppe volte + incorrectCiePinContent1: Hai ancora 2 tentativi, controllalo e riprova. + incorrectCiePinContent2: Al terzo tentativo errato, il PIN verrà bloccato. Per sbloccarlo e impostarne un nuovo, dovrai inserire il codice PUK nell’app CieID. + incorrectCiePinSecondaryActionLabel2: Hai dimenticato il PIN? + lockedCiePinContent: Il PIN della tua CIE è stato bloccato. Per sbloccarlo e impostarne un nuovo, dovrai inserire il codice PUK nell’app CieID. + lockedSecondaryActionLabel: Hai dimenticato il PUK? alert: Vuoi davvero tornare indietro? subtitleHelp: Il PIN è un codice di 8 cifre. subtitleCTA: Dove lo trovo? @@ -661,9 +665,9 @@ authentication: readingSuccess: "Lettura avvenuta con successo.\nPuoi rimuovere la carta mentre completiamo la verifica dei dati." invalidCard: La carta utilizzata non sembra essere una Carta di Identità Elettronica (CIE). tagLost: Hai rimosso la carta troppo presto. - cardLocked: Carta CIE bloccata - wrongPin1AttemptLeft: PIN errato, hai ancora 1 tentativo - wrongPin2AttemptLeft: PIN errato, hai ancora 2 tentativi + cardLocked: Il PIN della CIE è stato bloccato + wrongPin1AttemptLeft: Hai inserito un PIN errato per due volte + wrongPin2AttemptLeft: Il PIN non è corretto genericError: Si è verificato un errore imprevisto. Riprova. error: readerCardLostTitle: Avvicina nuovamente la carta diff --git a/ts/navigation/AuthenticationNavigator.tsx b/ts/navigation/AuthenticationNavigator.tsx index 1418b6e4aa8..c7504c23462 100644 --- a/ts/navigation/AuthenticationNavigator.tsx +++ b/ts/navigation/AuthenticationNavigator.tsx @@ -15,7 +15,6 @@ import CieAuthorizeDataUsageScreen from "../screens/authentication/cie/CieAuthor import CieCardReaderScreen from "../screens/authentication/cie/CieCardReaderScreen"; import CieConsentDataUsageScreen from "../screens/authentication/cie/CieConsentDataUsageScreen"; import CieExpiredOrInvalidScreen from "../screens/authentication/cie/CieExpiredOrInvalidScreen"; -import CiePinLockedTemporarilyScreen from "../screens/authentication/cie/CiePinLockedTemporarilyScreen"; import CiePinScreen from "../screens/authentication/cie/CiePinScreen"; import CieWrongCiePinScreen from "../screens/authentication/cie/CieWrongCiePinScreen"; import { AuthSessionPage } from "../screens/authentication/idpAuthSessionHandler"; @@ -115,15 +114,18 @@ const AuthenticationStackNavigator = () => ( component={CieConsentDataUsageScreen} /> - - - + + + ( + { + remainingCount: 2, + title: I18n.t("authentication.cie.pin.incorrectCiePinTitle1"), + subtitle: I18n.t("authentication.cie.pin.incorrectCiePinContent1"), + actionLabel: retryLabel, + actionTestID: `message-action-${retryLabel}`, + secondaryActionLabel: closeLabel, + secondaryActionTestID: `message-action-${closeLabel}` + }, + { + remainingCount: 1, + title: I18n.t("authentication.cie.pin.incorrectCiePinTitle2"), + subtitle: I18n.t("authentication.cie.pin.incorrectCiePinContent2"), + actionLabel: retryLabel, + actionTestID: `message-action-${retryLabel}`, + secondaryActionLabel: I18n.t( + "authentication.cie.pin.incorrectCiePinSecondaryActionLabel2" + ), + secondaryActionTestID: `message-action-${I18n.t( + "authentication.cie.pin.incorrectCiePinSecondaryActionLabel2" + )}` + }, + { + remainingCount: 0, + title: I18n.t("authentication.cie.pin.lockedCiePinTitle"), + subtitle: I18n.t("authentication.cie.pin.lockedCiePinContent"), + actionLabel: closeLabel, + actionTestID: `message-action-${closeLabel}`, + secondaryActionLabel: I18n.t( + "authentication.cie.pin.lockedSecondaryActionLabel" + ), + secondaryActionTestID: `message-action-${I18n.t( + "authentication.cie.pin.lockedSecondaryActionLabel" + )}` + } +); + +const useCaseThatShouldNotHappen: Test = { + remainingCount: -1, + title: I18n.t("global.genericError"), + subtitle: "-1", + actionLabel: retryLabel, + actionTestID: `message-action-${retryLabel}`, + secondaryActionLabel: closeLabel, + secondaryActionTestID: `message-action-${closeLabel}` +}; + +describe("CieWrongCiePinScreen", () => { + tests.forEach(test => { + it(`it should render correctly, with ${test.remainingCount} remainig attemps`, () => { + const component = renderComponent(test.remainingCount); + expect(component).toBeDefined(); + expect(component.getByText(test.title)).toBeDefined(); + expect(component.getByText(test.subtitle)).toBeDefined(); + expect(component.getByText(test.actionLabel)).toBeDefined(); + expect(component.getByText(test.secondaryActionLabel)).toBeDefined(); + expect(component.getByTestId(test.actionTestID)).toBeDefined(); + expect(component.getByTestId(test.secondaryActionTestID)).toBeDefined(); + }); + }); + it("it should render the default message, in case of unexpetect values", () => { + const component = renderComponent( + useCaseThatShouldNotHappen.remainingCount + ); + expect(component).toBeDefined(); + expect(component.queryByText(tests[0].title)).toBeNull(); + expect(component.queryByText(tests[0].subtitle)).toBeNull(); + expect(component.getByText(I18n.t("global.genericError"))).toBeDefined(); + expect( + component.getByText(useCaseThatShouldNotHappen.actionLabel) + ).toBeDefined(); + expect( + component.getByText(useCaseThatShouldNotHappen.secondaryActionLabel) + ).toBeDefined(); + expect( + component.getByText(useCaseThatShouldNotHappen.subtitle) + ).toBeDefined(); + }); +}); + +const renderComponent = (remainingCount: number) => { + const globalState = appReducer(undefined, applicationChangeState("active")); + const store = createStore(appReducer, globalState as any); + + return renderScreenWithNavigationStoreContext( + CieWrongCiePinScreen, + ROUTES.AUTHENTICATION_OPT_IN, + { remainingCount }, + store + ); +}; diff --git a/ts/screens/authentication/cie/CieCardReaderScreen.tsx b/ts/screens/authentication/cie/CieCardReaderScreen.tsx index 5cab84759d8..dc31cce6471 100644 --- a/ts/screens/authentication/cie/CieCardReaderScreen.tsx +++ b/ts/screens/authentication/cie/CieCardReaderScreen.tsx @@ -278,16 +278,6 @@ class CieCardReaderScreen extends React.PureComponent { // The card is temporarily locked. Unlock is available by CieID app case "PIN Locked": case "ON_CARD_PIN_LOCKED": - this.setError({ - eventReason: event.event, - navigation: () => - this.props.navigation.navigate(ROUTES.AUTHENTICATION, { - screen: ROUTES.CIE_PIN_TEMP_LOCKED_SCREEN - }) - }); - break; - - // The inserted pin is incorrect case "ON_PIN_ERROR": this.setError({ eventReason: event.event, @@ -295,7 +285,8 @@ class CieCardReaderScreen extends React.PureComponent { this.props.navigation.navigate(ROUTES.AUTHENTICATION, { screen: ROUTES.CIE_WRONG_PIN_SCREEN, params: { - remainingCount: event.attemptsLeft + remainingCount: + event.event === "ON_CARD_PIN_LOCKED" ? 0 : event.attemptsLeft } }) }); diff --git a/ts/screens/authentication/cie/CiePinLockedTemporarilyScreen.tsx b/ts/screens/authentication/cie/CiePinLockedTemporarilyScreen.tsx deleted file mode 100644 index 89a09156a38..00000000000 --- a/ts/screens/authentication/cie/CiePinLockedTemporarilyScreen.tsx +++ /dev/null @@ -1,108 +0,0 @@ -/** - * A screen to alert the user about the number of attempts remains - */ -import { - ContentWrapper, - FooterWithButtons -} from "@pagopa/io-app-design-system"; -import { constNull } from "fp-ts/lib/function"; -import * as React from "react"; -import { Linking, Platform, ScrollView } from "react-native"; -import { connect } from "react-redux"; -import { ScreenContentHeader } from "../../../components/screens/ScreenContentHeader"; -import TopScreenComponent from "../../../components/screens/TopScreenComponent"; -import LegacyMarkdown from "../../../components/ui/Markdown/LegacyMarkdown"; -import I18n from "../../../i18n"; -import { IOStackNavigationRouteProps } from "../../../navigation/params/AppParamsList"; -import { AuthenticationParamsList } from "../../../navigation/params/AuthenticationParamsList"; -import { resetToAuthenticationRoute } from "../../../store/actions/navigation"; -import { ReduxProps } from "../../../store/actions/types"; - -type NavigationProps = IOStackNavigationRouteProps< - AuthenticationParamsList, - "CIE_PIN_TEMP_LOCKED_SCREEN" ->; - -type Props = NavigationProps & ReduxProps; - -type State = Readonly<{ - isLoadingCompleted: boolean; -}>; - -class CiePinLockedTemporarilyScreen extends React.PureComponent { - constructor(props: Props) { - super(props); - this.state = { isLoadingCompleted: false }; - } - private goToCieID() { - Linking.openURL( - Platform.select({ - ios: "https://apps.apple.com/it/app/cieid/id1504644677", - default: "https://play.google.com/store/apps/details?id=it.ipzs.cieid" - }) - ).catch(constNull); - } - - private getContextualHelp = () => ({ - title: I18n.t("authentication.cie.pin.contextualHelpTitle"), - body: () => ( - - {I18n.t("authentication.cie.pin.contextualHelpBody")} - - ) - }); - - private renderFooterButtons = () => ( - - ); - - private handleGoBack = () => resetToAuthenticationRoute(); - - public render(): React.ReactNode { - return ( - - - - - { - this.setState({ isLoadingCompleted: true }); - }} - > - {I18n.t("authentication.cie.pinTempLocked.content")} - - - - - {this.state.isLoadingCompleted && this.renderFooterButtons()} - - ); - } -} - -export default connect()(CiePinLockedTemporarilyScreen); diff --git a/ts/screens/authentication/cie/CieWrongCiePinScreen.tsx b/ts/screens/authentication/cie/CieWrongCiePinScreen.tsx index e0786976528..5d41236fac0 100644 --- a/ts/screens/authentication/cie/CieWrongCiePinScreen.tsx +++ b/ts/screens/authentication/cie/CieWrongCiePinScreen.tsx @@ -1,106 +1,176 @@ /** * A screen to alert the user about the number of attempts remains */ -import { - BlockButtonProps, - ContentWrapper, - FooterWithButtons, - VSpacer -} from "@pagopa/io-app-design-system"; import * as React from "react"; -import { ScrollView } from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { Body } from "../../../components/core/typography/Body"; -import { ScreenContentHeader } from "../../../components/screens/ScreenContentHeader"; -import TopScreenComponent from "../../../components/screens/TopScreenComponent"; +import { Route, useRoute } from "@react-navigation/native"; +import { IOPictograms } from "@pagopa/io-app-design-system"; +import { Linking } from "react-native"; +import { constNull } from "fp-ts/lib/function"; import I18n from "../../../i18n"; -import { IOStackNavigationRouteProps } from "../../../navigation/params/AppParamsList"; -import { AuthenticationParamsList } from "../../../navigation/params/AuthenticationParamsList"; +import { useIONavigation } from "../../../navigation/params/AppParamsList"; import ROUTES from "../../../navigation/routes"; -import { resetToAuthenticationRoute } from "../../../store/actions/navigation"; +import { OperationResultScreenContent } from "../../../components/screens/OperationResultScreenContent"; +import { WithTestID } from "../../../types/WithTestID"; export type CieWrongCiePinScreenNavigationParams = { remainingCount: number; }; -type NavigationProps = IOStackNavigationRouteProps< - AuthenticationParamsList, - "CIE_WRONG_PIN_SCREEN" ->; - -type Props = NavigationProps & ReturnType; +const CieWrongCiePinScreen = () => { + const navigation = useIONavigation(); + const route = + useRoute< + Route< + typeof ROUTES.CIE_WRONG_PIN_SCREEN, + CieWrongCiePinScreenNavigationParams + > + >(); + const { remainingCount } = route.params; -class CieWrongCiePinScreen extends React.PureComponent { - // TODO: use redux to handle control? - private navigateToCiePinScreen = async () => { - this.props.navigation.navigate(ROUTES.AUTHENTICATION, { + const navigateToCiePinScreen = React.useCallback(() => { + navigation.navigate(ROUTES.AUTHENTICATION, { screen: ROUTES.CIE_PIN_SCREEN }); + }, [navigation]); + + const navigateToAuthenticationScreen = React.useCallback(() => { + navigation.reset({ + index: 0, + routes: [{ name: ROUTES.AUTHENTICATION }] + }); + }, [navigation]); + + const didYouForgetPin = React.useCallback(() => { + Linking.openURL( + "https://www.cartaidentita.interno.gov.it/info-utili/codici-di-sicurezza-pin-e-puk/" + ).catch(constNull); + }, []); + + const didYouForgetPuk = React.useCallback(() => { + Linking.openURL( + "https://www.cartaidentita.interno.gov.it/info-utili/recupero-puk/" + ).catch(constNull); + }, []); + + type MessageAction = { + label: T; + accessibilityLabel: T; + onPress: () => void; }; - get ciePinRemainingCount() { - return this.props.route.params.remainingCount; - } + type Message = { + pictogram: IOPictograms; + title: string; + subtitle: string; + action: MessageAction; + secondaryAction: MessageAction; + }; - private renderFooterButtons = () => { - const cancelButtonProps: BlockButtonProps = { - type: "Outline", - buttonProps: { - label: I18n.t("global.buttons.cancel"), - accessibilityLabel: I18n.t("global.buttons.cancel"), - onPress: resetToAuthenticationRoute - } - }; - const retryButtonProps: BlockButtonProps = { - type: "Solid", - buttonProps: { - label: I18n.t("global.buttons.retry"), - accessibilityLabel: I18n.t("global.buttons.retry"), - onPress: this.navigateToCiePinScreen - } - }; - return ( - - ); + type Messages = { + [key: number]: Message; }; - public render(): React.ReactNode { - const remainingCount = this.ciePinRemainingCount; - return ( - - - + const createMessageAction = React.useCallback( + ({ + label, + onPress + }: { + label: T; + onPress: () => void; + }): WithTestID> => ({ + label, + accessibilityLabel: label, + onPress, + testID: `message-action-${label}` + }), + [] + ); + + const messages: Messages = React.useMemo( + () => ({ + 2: { + pictogram: "attention", + title: I18n.t("authentication.cie.pin.incorrectCiePinTitle1"), + subtitle: I18n.t("authentication.cie.pin.incorrectCiePinContent1"), + action: createMessageAction({ + label: I18n.t("global.buttons.retry"), + onPress: navigateToCiePinScreen + }), + secondaryAction: createMessageAction({ + label: I18n.t("global.buttons.close"), + onPress: navigateToAuthenticationScreen + }) + }, + 1: { + pictogram: "attention", + title: I18n.t("authentication.cie.pin.incorrectCiePinTitle2"), + subtitle: I18n.t("authentication.cie.pin.incorrectCiePinContent2"), + action: createMessageAction({ + label: I18n.t("global.buttons.retry"), + onPress: navigateToCiePinScreen + }), + secondaryAction: createMessageAction({ + label: I18n.t( + "authentication.cie.pin.incorrectCiePinSecondaryActionLabel2" + ), + onPress: didYouForgetPin + }) + }, + 0: { + pictogram: "fatalError", + title: I18n.t("authentication.cie.pin.lockedCiePinTitle"), + subtitle: I18n.t("authentication.cie.pin.lockedCiePinContent"), + action: createMessageAction({ + label: I18n.t("global.buttons.close"), + onPress: navigateToAuthenticationScreen + }), + secondaryAction: createMessageAction({ + label: I18n.t("authentication.cie.pin.lockedSecondaryActionLabel"), + onPress: didYouForgetPuk + }) + } + }), + [ + createMessageAction, + didYouForgetPin, + didYouForgetPuk, + navigateToAuthenticationScreen, + navigateToCiePinScreen + ] + ); + + // This should never happen, + // but it's a good practice to have a default message + // in case of unexpected values of `remainingCount`. + const defaultMessageThatShouldNeverHappen: Message = React.useMemo( + () => ({ + pictogram: "attention", + title: I18n.t("global.genericError"), + subtitle: `${remainingCount}`, + action: createMessageAction({ + label: I18n.t("global.buttons.retry"), + onPress: navigateToCiePinScreen + }), + secondaryAction: createMessageAction({ + label: I18n.t("global.buttons.close"), + onPress: navigateToAuthenticationScreen + }) + }), + [ + createMessageAction, + navigateToAuthenticationScreen, + navigateToCiePinScreen, + remainingCount + ] + ); - - - {I18n.t("authentication.cie.pin.incorrectCiePinContent1")} - - - - {I18n.t("authentication.cie.pin.incorrectCiePinContent2")} - - - + const getMessage = React.useCallback( + (key: number) => + key in messages ? messages[key] : defaultMessageThatShouldNeverHappen, + [defaultMessageThatShouldNeverHappen, messages] + ); - {this.renderFooterButtons()} - - ); - } -} -const mapDispatchToProps = (_: Dispatch) => ({}); + return ; +}; -export default connect(undefined, mapDispatchToProps)(CieWrongCiePinScreen); +export default CieWrongCiePinScreen;