teamdigitale/italia-app

View on GitHub
ts/screens/authentication/cie/CiePinScreen.tsx

Summary

Maintainability
D
1 day
Test Coverage
import {
  Banner,
  ContentWrapper,
  H2,
  IOStyles,
  LabelLink,
  OTPInput,
  VSpacer
} from "@pagopa/io-app-design-system";
import { Millisecond } from "@pagopa/ts-commons/lib/units";
import { useHeaderHeight } from "@react-navigation/elements";
import {
  useFocusEffect,
  useIsFocused,
  useNavigation
} from "@react-navigation/native";
import React, {
  useCallback,
  useContext,
  useEffect,
  useRef,
  useState
} from "react";
import {
  Keyboard,
  KeyboardAvoidingView,
  Platform,
  ScrollView,
  View
} from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import * as pot from "@pagopa/ts-commons/lib/pot";
import { IdpData } from "../../../../definitions/content/IdpData";
import {
  CieEntityIds,
  CieRequestAuthenticationOverlay
} from "../../../components/cie/CieRequestAuthenticationOverlay";
import { ContextualHelpPropsMarkdown } from "../../../components/screens/BaseScreenComponent";
import {
  BottomTopAnimation,
  LightModalContext
} from "../../../components/ui/LightModal";
import LegacyMarkdown from "../../../components/ui/Markdown/LegacyMarkdown";
import { pinPukHelpUrl } from "../../../config";
import { isCieLoginUatEnabledSelector } from "../../../features/cieLogin/store/selectors";
import { cieFlowForDevServerEnabled } from "../../../features/cieLogin/utils";
import { isFastLoginEnabledSelector } from "../../../features/fastLogin/store/selectors";
import { useHeaderSecondLevel } from "../../../hooks/useHeaderSecondLevel";
import I18n from "../../../i18n";
import { IOStackNavigationProp } from "../../../navigation/params/AppParamsList";
import { AuthenticationParamsList } from "../../../navigation/params/AuthenticationParamsList";
import ROUTES from "../../../navigation/routes";
import { loginSuccess } from "../../../store/actions/authentication";
import { nfcIsEnabled } from "../../../store/actions/cie";
import { useIODispatch, useIOSelector } from "../../../store/hooks";
import { SessionToken } from "../../../types/SessionToken";
import { setAccessibilityFocus } from "../../../utils/accessibility";
import { useIOBottomSheetAutoresizableModal } from "../../../utils/hooks/bottomSheet";
import { useOnFirstRender } from "../../../utils/hooks/useOnFirstRender";
import { withTrailingPoliceCarLightEmojii } from "../../../utils/strings";
import { openWebUrl } from "../../../utils/url";
import {
  trackLoginCiePinInfo,
  trackLoginCiePinScreen
} from "../analytics/cieAnalytics";
import { isNfcEnabledSelector } from "../../../store/reducers/cie";
import { getIdpLoginUri } from "../../../utils/login";

const CIE_PIN_LENGTH = 8;

const getContextualHelp = (): ContextualHelpPropsMarkdown => ({
  title: "authentication.cie.pin.contextualHelpTitle",
  body: "authentication.cie.pin.contextualHelpBody"
});
const onOpenForgotPinPage = () => openWebUrl(pinPukHelpUrl);

const CiePinScreen = () => {
  useOnFirstRender(() => {
    trackLoginCiePinScreen();
  });

  const dispatch = useIODispatch();

  const requestNfcEnabledCheck = useCallback(
    () => dispatch(nfcIsEnabled.request()),
    [dispatch]
  );

  const doLoginSuccess = useCallback(
    (token: SessionToken, idp: keyof IdpData) =>
      dispatch(loginSuccess({ token, idp })),
    [dispatch]
  );

  const { showAnimatedModal, hideModal } = useContext(LightModalContext);
  const navigation =
    useNavigation<
      IOStackNavigationProp<
        AuthenticationParamsList,
        typeof ROUTES.CIE_PIN_SCREEN
      >
    >();
  const [pin, setPin] = useState("");
  const bannerRef = useRef<View>(null);
  const pinPadViewRef = useRef<View>(null);
  const [authUrlGenerated, setAuthUrlGenerated] = useState<string | undefined>(
    undefined
  );
  const isEnabled = useIOSelector(isNfcEnabledSelector);
  const isNfcEnabled = pot.getOrElse(isEnabled, false);
  const { present, bottomSheet } = useIOBottomSheetAutoresizableModal({
    component: (
      <View>
        <LegacyMarkdown avoidTextSelection>
          {I18n.t("bottomSheets.ciePin.content")}
        </LegacyMarkdown>
        <VSpacer size={24} />
        <LabelLink onPress={onOpenForgotPinPage}>
          {I18n.t("authentication.cie.pin.bottomSheetCTA")}
        </LabelLink>
        <VSpacer size={24} />
      </View>
    ),
    title: I18n.t("bottomSheets.ciePin.title")
  });

  const handleAuthenticationOverlayOnClose = useCallback(() => {
    setPin("");
    setAuthUrlGenerated(undefined);
    hideModal();
  }, [hideModal]);

  useEffect(() => {
    if (authUrlGenerated !== undefined) {
      if (cieFlowForDevServerEnabled) {
        const loginUri = getIdpLoginUri(CieEntityIds.PROD, 3);
        navigation.navigate(ROUTES.CIE_CONSENT_DATA_USAGE, {
          cieConsentUri: loginUri
        });
      } else {
        if (isNfcEnabled) {
          navigation.navigate(ROUTES.CIE_CARD_READER_SCREEN, {
            ciePin: pin,
            authorizationUri: authUrlGenerated
          });
        } else {
          navigation.navigate(ROUTES.CIE_ACTIVATE_NFC_SCREEN, {
            ciePin: pin,
            authorizationUri: authUrlGenerated
          });
        }
      }
      handleAuthenticationOverlayOnClose();
    }
  }, [
    authUrlGenerated,
    doLoginSuccess,
    handleAuthenticationOverlayOnClose,
    isNfcEnabled,
    navigation,
    pin
  ]);

  const showModal = useCallback(() => {
    requestNfcEnabledCheck();
    Keyboard.dismiss();
    showAnimatedModal(
      <CieRequestAuthenticationOverlay
        onClose={handleAuthenticationOverlayOnClose}
        onSuccess={setAuthUrlGenerated}
      />,
      BottomTopAnimation
    );
  }, [
    handleAuthenticationOverlayOnClose,
    requestNfcEnabledCheck,
    showAnimatedModal
  ]);

  useEffect(() => {
    if (pin.length === CIE_PIN_LENGTH) {
      showModal();
    }
  }, [pin, showModal]);

  useFocusEffect(
    React.useCallback(() => {
      setAccessibilityFocus(pinPadViewRef, 300 as Millisecond);
    }, [])
  );

  const isFastLoginFeatureFlagEnabled = useIOSelector(
    isFastLoginEnabledSelector
  );
  const useCieUat = useIOSelector(isCieLoginUatEnabledSelector);

  useHeaderSecondLevel({
    title: withTrailingPoliceCarLightEmojii("", useCieUat),
    supportRequest: true,
    contextualHelpMarkdown: getContextualHelp()
  });

  const headerHeight = useHeaderHeight();
  const isFocused = useIsFocused();

  return (
    <SafeAreaView edges={["bottom"]} style={{ flex: 1 }}>
      <KeyboardAvoidingView
        behavior={Platform.select({
          ios: "padding",
          android: undefined
        })}
        contentContainerStyle={IOStyles.flex}
        style={IOStyles.flex}
        keyboardVerticalOffset={headerHeight}
      >
        <ScrollView contentContainerStyle={{ flexGrow: 1 }}>
          <ContentWrapper>
            <H2>{I18n.t("authentication.cie.pin.pinCardTitle")}</H2>
            <VSpacer size={8} />
            <LabelLink
              onPress={() => {
                trackLoginCiePinInfo();
                present();
              }}
            >
              {I18n.t("authentication.cie.pin.subtitleCTA")}
            </LabelLink>
            <VSpacer size={24} />
            <View style={IOStyles.flex}>
              <OTPInput
                ref={pinPadViewRef}
                secret
                value={pin}
                accessibilityLabel={I18n.t(
                  "authentication.cie.pin.accessibility.label"
                )}
                accessibilityHint={I18n.t(
                  "authentication.cie.pin.accessibility.hint"
                )}
                onValueChange={setPin}
                length={CIE_PIN_LENGTH}
                autoFocus={isFocused}
                deleteButtonAccessibilityLabel={I18n.t(
                  "authentication.cie.pin.accessibility.deleteLabel",
                  { number: pin.slice(-1) }
                )}
                key={isFocused ? "focused" : "unfocused"}
              />
              <VSpacer size={24} />
              <Banner
                viewRef={bannerRef}
                color="neutral"
                size="small"
                content={
                  isFastLoginFeatureFlagEnabled
                    ? I18n.t("login.expiration_info_FL")
                    : I18n.t("login.expiration_info")
                }
                pictogramName="passcode"
              />
            </View>
          </ContentWrapper>
        </ScrollView>
      </KeyboardAvoidingView>
      {bottomSheet}
    </SafeAreaView>
  );
};

export default CiePinScreen;