From 3431429fa9d6f7ee5d92fd814af4e9d365c71647 Mon Sep 17 00:00:00 2001 From: Alessandro Izzo Date: Mon, 22 Jan 2024 22:10:14 +0100 Subject: [PATCH 1/3] fix: [IOBP-515] Expiry date into new wallet details screen (#5418) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## ⚠️ This PR depends on #5412 ⚠️ ## Short description This PR fixes the expiry date inside the new wallet details screen showing the correct date. ## List of changes proposed in this pull request - Added an additional util function `getDateFromExpiryDate` which returns a Date object from a string in format "YYYYMM" - Fixed the `isExpiredDate` utility function with the right string slicing; ## How to test Complete a new onboarding process from the playground and at the end you should be able to see the correct expiry date ## Preview |Before|After| |-|-| | | | --------- Co-authored-by: Federico Mastrini --- .../details/screens/WalletDetailsScreen.tsx | 3 ++- ts/utils/dates.ts | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/ts/features/walletV3/details/screens/WalletDetailsScreen.tsx b/ts/features/walletV3/details/screens/WalletDetailsScreen.tsx index 64c923cf7b8..2eec49f09b4 100644 --- a/ts/features/walletV3/details/screens/WalletDetailsScreen.tsx +++ b/ts/features/walletV3/details/screens/WalletDetailsScreen.tsx @@ -21,6 +21,7 @@ import { } from "../store"; import { walletDetailsGetInstrument } from "../store/actions"; import { UIWalletInfoDetails } from "../types/UIWalletInfoDetails"; +import { getDateFromExpiryDate } from "../../../../utils/dates"; export type WalletDetailsScreenNavigationParams = Readonly<{ walletId: string; @@ -46,7 +47,7 @@ const generateCardComponent = (details: UIWalletInfoDetails) => { { - const year = +expiryDate.slice(3, 5); - const month = +expiryDate.slice(0, 3); + const year = +expiryDate.slice(0, 4); + const month = +expiryDate.slice(4, 6); const now = new Date(); const nowYearMonth = new Date(now.getFullYear(), now.getMonth() + 1); const cardExpirationDate = new Date(year, month); @@ -320,3 +320,13 @@ export const toAndroidCacheTimestamp = () => new Date(), I18n.t("global.dateFormats.shortFormat").replace(/\//g, "") ); + +/** + * This function returns a Date object from a string in format "YYYYMM" + * @param expiryDate + */ +export const getDateFromExpiryDate = (expiryDate: string): Date => { + const year = +expiryDate.slice(0, 4); + const month = +expiryDate.slice(4, 6); + return new Date(year, month - 1); +}; From 46af86615b881d57a9f785092eca3baf0cc89ada Mon Sep 17 00:00:00 2001 From: Fabio Bombardi <16268789+shadowsheep1@users.noreply.github.com> Date: Tue, 23 Jan 2024 09:47:54 +0100 Subject: [PATCH 2/3] chore: [IOPID-1301] Bump ToS version from 4.7 to 4.8"" (#5402) Reverts pagopa/io-app#5391 --- ts/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/config.ts b/ts/config.ts index a5496e6722c..92ede32ee6c 100644 --- a/ts/config.ts +++ b/ts/config.ts @@ -122,7 +122,7 @@ export const remindersOptInEnabled = Config.REMINDERS_OPT_IN_ENABLED === "YES"; export const isNewCduFlow = Config.CDU_NEW_FLOW === "YES"; // version of ToS -export const tosVersion: NonNegativeNumber = 4.7 as NonNegativeNumber; +export const tosVersion: NonNegativeNumber = 4.8 as NonNegativeNumber; export const fetchTimeout = pipe( parseInt(Config.FETCH_TIMEOUT_MS, 10), From 9fc529f943ab7854f84980655e0064c77c699f33 Mon Sep 17 00:00:00 2001 From: Fabio Bombardi <16268789+shadowsheep1@users.noreply.github.com> Date: Tue, 23 Jan 2024 10:47:59 +0100 Subject: [PATCH 3/3] feat: [IOPID-1391] Fix tablet compatibility alert displaying (#5417) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Short description This PR fixes the continuously displaying of the Tablet compatibility alert during the login flow. It took the chance to refactor the `LandingScreen` from `PureComponent` to `FunctionComponent`.
Details

| ❌ | ✅ | | - | - | |

## How to test Launch the app on an Android Tablet and check that the Alert is shown only at every first render of `LandingScreen`. --- ts/navigation/AuthenticationNavigator.tsx | 2 +- ts/screens/authentication/LandingScreen.tsx | 320 ++++++++++---------- 2 files changed, 158 insertions(+), 164 deletions(-) diff --git a/ts/navigation/AuthenticationNavigator.tsx b/ts/navigation/AuthenticationNavigator.tsx index c7a392f824e..af41d43c234 100644 --- a/ts/navigation/AuthenticationNavigator.tsx +++ b/ts/navigation/AuthenticationNavigator.tsx @@ -10,7 +10,7 @@ import CiePinScreen from "../screens/authentication/cie/CiePinScreen"; import CieWrongCiePinScreen from "../screens/authentication/cie/CieWrongCiePinScreen"; import IdpLoginScreen from "../screens/authentication/IdpLoginScreen"; import IdpSelectionScreen from "../screens/authentication/IdpSelectionScreen"; -import LandingScreen from "../screens/authentication/LandingScreen"; +import { LandingScreen } from "../screens/authentication/LandingScreen"; import TestAuthenticationScreen from "../screens/authentication/TestAuthenticationScreen"; import MarkdownScreen from "../screens/development/MarkdownScreen"; import { AuthSessionPage } from "../screens/authentication/idpAuthSessionHandler"; diff --git a/ts/screens/authentication/LandingScreen.tsx b/ts/screens/authentication/LandingScreen.tsx index 4f138840e7f..3053331215e 100644 --- a/ts/screens/authentication/LandingScreen.tsx +++ b/ts/screens/authentication/LandingScreen.tsx @@ -10,8 +10,9 @@ import { Content, Text as NBButtonText } from "native-base"; import * as React from "react"; import { View, Alert, StyleSheet } from "react-native"; import DeviceInfo from "react-native-device-info"; -import { connect } from "react-redux"; +import { useDispatch, useStore } from "react-redux"; import { IOColors, Icon, HSpacer, VSpacer } from "@pagopa/io-app-design-system"; +import { useNavigation } from "@react-navigation/native"; import sessionExpiredImg from "../../../img/landing/session_expired.png"; import ButtonDefaultOpacity from "../../components/ButtonDefaultOpacity"; import CieNotSupported from "../../components/cie/CieNotSupported"; @@ -19,7 +20,6 @@ import ContextualInfo from "../../components/ContextualInfo"; import { Link } from "../../components/core/typography/Link"; import { IOStyles } from "../../components/core/variables/IOStyles"; import { DevScreenButton } from "../../components/DevScreenButton"; -import { withLightModalContext } from "../../components/helpers/withLightModalContext"; import { HorizontalScroll } from "../../components/HorizontalScroll"; import { renderInfoRasterImage } from "../../components/infoScreen/imageRendering"; import { InfoScreenComponent } from "../../components/infoScreen/InfoScreenComponent"; @@ -29,28 +29,19 @@ import BaseScreenComponent, { ContextualHelpPropsMarkdown } from "../../components/screens/BaseScreenComponent"; import SectionStatusComponent from "../../components/SectionStatus"; -import { LightModalContextInterface } from "../../components/ui/LightModal"; import I18n from "../../i18n"; import { mixpanelTrack } from "../../mixpanel"; -import { - AppParamsList, - IOStackNavigationRouteProps -} from "../../navigation/params/AppParamsList"; import ROUTES from "../../navigation/routes"; import { idpSelected, resetAuthenticationState } from "../../store/actions/authentication"; -import { continueWithRootOrJailbreak } from "../../store/actions/persistedPreferences"; -import { Dispatch } from "../../store/actions/types"; -import { isSessionExpiredSelector } from "../../store/reducers/authentication"; import { hasApiLevelSupportSelector, hasNFCFeatureSelector, isCieSupportedSelector } from "../../store/reducers/cie"; import { continueWithRootOrJailbreakSelector } from "../../store/reducers/persistedPreferences"; -import { GlobalState } from "../../store/reducers/types"; import variables from "../../theme/variables"; import { ComponentProps } from "../../types/react"; import { isDevEnv } from "../../utils/environment"; @@ -64,24 +55,17 @@ import { } from "../../features/fastLogin/store/selectors"; import { isCieLoginUatEnabledSelector } from "../../features/cieLogin/store/selectors"; import { cieFlowForDevServerEnabled } from "../../features/cieLogin/utils"; +import { useOnFirstRender } from "../../utils/hooks/useOnFirstRender"; +import { useIOSelector } from "../../store/hooks"; +import { isSessionExpiredSelector } from "../../store/reducers/authentication"; +import { LightModalContext } from "../../components/ui/LightModal"; +import { continueWithRootOrJailbreak } from "../../store/actions/persistedPreferences"; import { trackCieLoginSelected, trackMethodInfo, trackSpidLoginSelected } from "./analytics"; -type NavigationProps = IOStackNavigationRouteProps; - -type Props = NavigationProps & - LightModalContextInterface & - ReturnType & - ReturnType; - -type State = { - isRootedOrJailbroken: O.Option; - isSessionExpired: boolean; -}; - const getCards = ( isCIEAvailable: boolean ): ReadonlyArray> => [ @@ -179,124 +163,166 @@ export const IdpCIE: SpidIdp = { profileUrl: "" }; -class LandingScreen extends React.PureComponent { - constructor(props: Props) { - super(props); - this.state = { isRootedOrJailbroken: O.none, isSessionExpired: false }; - } +export const LandingScreen = () => { + const [isRootedOrJailbroken, setIsRootedOrJailbroken] = React.useState< + O.Option + >(O.none); + const [ + hasTabletCompatibilityAlertAlreadyShown, + setHasTabletCompatibilityAlertAlreadyShown + ] = React.useState(false); + + const store = useStore(); + + const dispatch = useDispatch(); + const navigation = useNavigation(); - private isCieSupported = () => - cieFlowForDevServerEnabled || this.props.isCieSupported; - private isCieUatEnabled = () => this.props.isCieUatEnabled; + const isSessionExpired = useIOSelector(isSessionExpiredSelector); - public async componentDidMount() { + const isContinueWithRootOrJailbreak = useIOSelector( + continueWithRootOrJailbreakSelector + ); + + const isFastLoginEnabled = useIOSelector(isFastLoginEnabledSelector); + const isFastLoginOptInFFEnabled = useIOSelector(fastLoginOptInFFEnabled); + + const isCIEAuthenticationSupported = useIOSelector(isCieSupportedSelector); + const hasApiLevelSupport = useIOSelector(hasApiLevelSupportSelector); + const hasCieApiLevelSupport = pot.getOrElse(hasApiLevelSupport, false); + const hasNFCFeature = useIOSelector(hasNFCFeatureSelector); + const hasCieNFCFeature = pot.getOrElse(hasNFCFeature, false); + + const isCieSupported = React.useCallback( + () => + cieFlowForDevServerEnabled || + pot.getOrElse(isCIEAuthenticationSupported, false), + [isCIEAuthenticationSupported] + ); + const isCieUatEnabled = useIOSelector(isCieLoginUatEnabledSelector); + + useOnFirstRender(async () => { const isRootedOrJailbroken = await JailMonkey.isJailBroken(); - this.setState({ isRootedOrJailbroken: O.some(isRootedOrJailbroken) }); - if (this.props.isSessionExpired) { - this.setState({ isSessionExpired: true }); - this.props.resetState(); + setIsRootedOrJailbroken(O.some(isRootedOrJailbroken)); + if (isSessionExpired) { + dispatch(resetAuthenticationState()); } - } + }); - private displayTabletAlert() { - Alert.alert( - "", - I18n.t("tablet.message"), - [ - { - text: I18n.t("global.buttons.continue"), - style: "cancel" - } - ], - { cancelable: true } - ); - } + const { hideModal, showAnimatedModal } = React.useContext(LightModalContext); - private openUnsupportedCIEModal = () => { - this.props.showAnimatedModal( - ( - - )} - /> - ); + const displayTabletAlert = () => { + if (!hasTabletCompatibilityAlertAlreadyShown) { + setHasTabletCompatibilityAlertAlreadyShown(true); + Alert.alert( + "", + I18n.t("tablet.message"), + [ + { + text: I18n.t("global.buttons.continue"), + style: "cancel" + } + ], + { cancelable: true } + ); + } }; - private navigateToMarkdown = () => - this.props.navigation.navigate(ROUTES.AUTHENTICATION, { - screen: ROUTES.MARKDOWN - }); + const navigateToMarkdown = React.useCallback( + () => + navigation.navigate(ROUTES.AUTHENTICATION, { + screen: ROUTES.MARKDOWN + }), + [navigation] + ); - private navigateToIdpSelection = () => { + const navigateToIdpSelection = React.useCallback(() => { trackSpidLoginSelected(); - if (this.props.isFastLoginOptInFFEnabled) { - this.props.navigation.navigate(ROUTES.AUTHENTICATION, { + if (isFastLoginOptInFFEnabled) { + navigation.navigate(ROUTES.AUTHENTICATION, { screen: ROUTES.AUTHENTICATION_OPT_IN, params: { identifier: "SPID" } }); } else { - this.props.navigation.navigate(ROUTES.AUTHENTICATION, { + navigation.navigate(ROUTES.AUTHENTICATION, { screen: ROUTES.AUTHENTICATION_IDP_SELECTION }); } - }; + }, [isFastLoginOptInFFEnabled, navigation]); + + const navigateToCiePinScreen = React.useCallback(() => { + const openUnsupportedCIEModal = () => { + showAnimatedModal( + ( + + )} + /> + ); + }; - private navigateToCiePinScreen = () => { - if (this.isCieSupported()) { - void trackCieLoginSelected(this.props.state); - this.props.dispatchIdpCieSelected(); - if (this.props.isFastLoginOptInFFEnabled) { - this.props.navigation.navigate(ROUTES.AUTHENTICATION, { + if (isCieSupported()) { + void trackCieLoginSelected(store.getState()); + dispatch(idpSelected(IdpCIE)); + if (isFastLoginOptInFFEnabled) { + navigation.navigate(ROUTES.AUTHENTICATION, { screen: ROUTES.AUTHENTICATION_OPT_IN, params: { identifier: "CIE" } }); } else { - this.props.navigation.navigate(ROUTES.AUTHENTICATION, { + navigation.navigate(ROUTES.AUTHENTICATION, { screen: ROUTES.CIE_PIN_SCREEN }); } } else { - this.openUnsupportedCIEModal(); + openUnsupportedCIEModal(); } - }; + }, [ + dispatch, + hasCieApiLevelSupport, + hasCieNFCFeature, + hideModal, + isCieSupported, + isFastLoginOptInFFEnabled, + navigation, + showAnimatedModal, + store + ]); - private navigateToSpidCieInformationRequest = () => { + const navigateToSpidCieInformationRequest = () => { trackMethodInfo(); openWebUrl(cieSpidMoreInfoUrl); }; - private navigateToCieUatSelectionScreen = () => { - if (this.isCieSupported()) { - this.props.navigation.navigate(ROUTES.AUTHENTICATION, { + const navigateToCieUatSelectionScreen = React.useCallback(() => { + if (isCieSupported()) { + navigation.navigate(ROUTES.AUTHENTICATION, { screen: ROUTES.CIE_LOGIN_CONFIG_SCREEN }); } - }; + }, [isCieSupported, navigation]); - private renderCardComponents = () => { - const cardProps = getCards(this.isCieSupported()); + const renderCardComponents = () => { + const cardProps = getCards(isCieSupported()); return cardProps.map(p => ( )); }; - private handleContinueWithRootOrJailbreak = (continueWith: boolean) => { - this.props.dispatchContinueWithRootOrJailbreak(continueWith); + const handleContinueWithRootOrJailbreak = (continueWith: boolean) => { + dispatch(continueWithRootOrJailbreak(continueWith)); }; // eslint-disable-next-line sonarjs/cognitive-complexity - private renderLandingScreen = () => { - const isCieSupported = this.isCieSupported(); - const isCieUatEnabled = this.isCieUatEnabled(); + const renderLandingScreen = () => { const firstButtonStyle = isCieUatEnabled ? styles.uatCie : styles.fullOpacity; - const secondButtonStyle = isCieSupported + const secondButtonStyle = isCieSupported() ? styles.fullOpacity : styles.noCie; return ( @@ -304,22 +330,22 @@ class LandingScreen extends React.PureComponent { appLogo contextualHelpMarkdown={contextualHelpMarkdown} faqCategories={ - isCieSupported ? ["landing_SPID", "landing_CIE"] : ["landing_SPID"] + isCieSupported() ? ["landing_SPID", "landing_CIE"] : ["landing_SPID"] } > - {isDevEnv && } + {isDevEnv && } - {this.state.isSessionExpired ? ( + {isSessionExpired ? ( ) : ( - + )} @@ -330,31 +356,32 @@ class LandingScreen extends React.PureComponent { primary={true} iconLeft={true} onPress={ - isCieSupported - ? this.navigateToCiePinScreen - : this.navigateToIdpSelection + isCieSupported() ? navigateToCiePinScreen : navigateToIdpSelection } onLongPress={() => - isCieSupported ? this.navigateToCieUatSelectionScreen() : "" + isCieSupported() ? navigateToCieUatSelectionScreen() : "" } accessibilityRole="button" accessible={true} style={firstButtonStyle} accessibilityLabel={ - isCieSupported + isCieSupported() ? I18n.t("authentication.landing.loginCie") : I18n.t("authentication.landing.loginSpid") } testID={ - isCieSupported + isCieSupported() ? "landing-button-login-cie" : "landing-button-login-spid" } > - + - {isCieSupported + {isCieSupported() ? I18n.t("authentication.landing.loginCie") : I18n.t("authentication.landing.loginSpid")} @@ -362,7 +389,7 @@ class LandingScreen extends React.PureComponent { { primary={true} iconLeft={true} onPress={ - this.isCieSupported() - ? this.navigateToIdpSelection - : this.navigateToCiePinScreen + isCieSupported() ? navigateToIdpSelection : navigateToCiePinScreen } testID={ - this.isCieSupported() + isCieSupported() ? "landing-button-login-spid" : "landing-button-login-cie" } > - {this.isCieSupported() + {isCieSupported() ? I18n.t("authentication.landing.loginSpid") : I18n.t("authentication.landing.loginCie")} @@ -397,9 +422,9 @@ class LandingScreen extends React.PureComponent { - {this.isCieSupported() + {isCieSupported() ? I18n.t("authentication.landing.nospid-nocie") : I18n.t("authentication.landing.nospid")} @@ -409,70 +434,39 @@ class LandingScreen extends React.PureComponent { }; // Screen displayed during the async loading of the JailMonkey.isJailBroken() - private renderLoadingScreen = () => ( + const renderLoadingScreen = () => ( ); - private chooseScreenToRender = (isRootedOrJailbroken: boolean) => { + const chooseScreenToRender = (isRootedOrJailbroken: boolean) => { // if the device is compromised and the user didn't allow to continue // show a blocking modal - if (isRootedOrJailbroken && !this.props.continueWithRootOrJailbreak) { + if (isRootedOrJailbroken && !isContinueWithRootOrJailbreak) { void mixpanelTrack("SHOW_ROOTED_OR_JAILBROKEN_MODAL"); return ( this.handleContinueWithRootOrJailbreak(true)} - onCancel={() => this.handleContinueWithRootOrJailbreak(false)} + onContinue={() => handleContinueWithRootOrJailbreak(true)} + onCancel={() => handleContinueWithRootOrJailbreak(false)} /> ); } // In case of Tablet, display an alert to inform the user if (DeviceInfo.isTablet()) { - this.displayTabletAlert(); + displayTabletAlert(); } // standard rendering of the landing screen - return this.renderLandingScreen(); + return renderLandingScreen(); }; - public render() { - // If the async loading of the isRootedOrJailbroken is not ready, display a loading - return pipe( - this.state.isRootedOrJailbroken, - O.fold( - () => this.renderLoadingScreen(), - // when the value isRootedOrJailbroken is ready, display the right screen based on a set of rule - rootedOrJailbroken => this.chooseScreenToRender(rootedOrJailbroken) - ) - ); - } -} - -const mapStateToProps = (state: GlobalState) => { - const isCIEAuthenticationSupported = isCieSupportedSelector(state); - const hasApiLevelSupport = hasApiLevelSupportSelector(state); - const hasNFCFeature = hasNFCFeatureSelector(state); - return { - isFastLoginEnabled: isFastLoginEnabledSelector(state), - isSessionExpired: isSessionExpiredSelector(state), - isFastLoginOptInFFEnabled: fastLoginOptInFFEnabled(state), - continueWithRootOrJailbreak: continueWithRootOrJailbreakSelector(state), - isCieSupported: pot.getOrElse(isCIEAuthenticationSupported, false), - hasCieApiLevelSupport: pot.getOrElse(hasApiLevelSupport, false), - hasCieNFCFeature: pot.getOrElse(hasNFCFeature, false), - isCieUatEnabled: isCieLoginUatEnabledSelector(state), - state - }; + // If the async loading of the isRootedOrJailbroken is not ready, display a loading + return pipe( + isRootedOrJailbroken, + O.fold( + () => renderLoadingScreen(), + // when the value isRootedOrJailbroken is ready, display the right screen based on a set of rule + rootedOrJailbroken => chooseScreenToRender(rootedOrJailbroken) + ) + ); }; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - resetState: () => dispatch(resetAuthenticationState()), - dispatchIdpCieSelected: () => dispatch(idpSelected(IdpCIE)), - dispatchContinueWithRootOrJailbreak: (continueWith: boolean) => - dispatch(continueWithRootOrJailbreak(continueWith)) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(withLightModalContext(LandingScreen));