teamdigitale/italia-app

View on GitHub
ts/features/idpay/configuration/screens/InstrumentsEnrollmentScreen.tsx

Summary

Maintainability
D
2 days
Test Coverage
import {
  FooterWithButtons,
  IOStyles,
  VSpacer
} from "@pagopa/io-app-design-system";
import { RouteProp, useFocusEffect, useRoute } from "@react-navigation/native";
import * as O from "fp-ts/lib/Option";
import { pipe } from "fp-ts/lib/function";
import React from "react";
import { ScrollView } from "react-native";
import AdviceComponent from "../../../../components/AdviceComponent";
import LoadingSpinnerOverlay from "../../../../components/LoadingSpinnerOverlay";
import { Body } from "../../../../components/core/typography/Body";
import { H1 } from "../../../../components/core/typography/H1";
import BaseScreenComponent from "../../../../components/screens/BaseScreenComponent";
import I18n from "../../../../i18n";
import { useIONavigation } from "../../../../navigation/params/AppParamsList";
import ROUTES from "../../../../navigation/routes";
import { Wallet } from "../../../../types/pagopa";
import { emptyContextualHelp } from "../../../../utils/emptyContextualHelp";
import { useIOBottomSheetAutoresizableModal } from "../../../../utils/hooks/bottomSheet";
import { InstrumentEnrollmentSwitch } from "../components/InstrumentEnrollmentSwitch";
import { IdPayConfigurationMachineContext } from "../machine/provider";
import {
  failureSelector,
  initiativeInstrumentsByIdWalletSelector,
  isUpsertingInstrumentSelector,
  selectInitiativeDetails,
  selectIsInstrumentsOnlyMode,
  selectWalletInstruments
} from "../machine/selectors";
import { IdPayConfigurationParamsList } from "../navigation/params";
import { ConfigurationMode } from "../types";
import { InitiativeFailureType } from "../types/failure";
import { isLoadingSelector } from "../../common/machine/selectors";

export type IdPayInstrumentsEnrollmentScreenParams = {
  initiativeId?: string;
};

type RouteProps = RouteProp<
  IdPayConfigurationParamsList,
  "IDPAY_CONFIGURATION_INSTRUMENTS_ENROLLMENT"
>;

export const InstrumentsEnrollmentScreen = () => {
  const navigation = useIONavigation();
  const { params } = useRoute<RouteProps>();
  const { initiativeId } = params;

  const { useActorRef, useSelector } = IdPayConfigurationMachineContext;
  const machine = useActorRef();

  const [stagedWalletId, setStagedWalletId] = React.useState<number>();

  const isLoading = useSelector(isLoadingSelector);
  const failure = useSelector(failureSelector);

  const initiativeDetails = useSelector(selectInitiativeDetails);
  const isInstrumentsOnlyMode = useSelector(selectIsInstrumentsOnlyMode);
  const walletInstruments = useSelector(selectWalletInstruments);

  const isUpserting = useSelector(isUpsertingInstrumentSelector);

  const initiativeInstrumentsByIdWallet = useSelector(
    initiativeInstrumentsByIdWalletSelector
  );

  const hasSelectedInstruments =
    Object.keys(initiativeInstrumentsByIdWallet).length > 0;

  useFocusEffect(
    React.useCallback(() => {
      if (initiativeId) {
        machine.send({
          type: "start-configuration",
          initiativeId,
          mode: ConfigurationMode.INSTRUMENTS
        });
      }
    }, [machine, initiativeId])
  );

  React.useEffect(() => {
    pipe(
      failure,
      O.filter(
        failure =>
          failure === InitiativeFailureType.INSTRUMENT_ENROLL_FAILURE ||
          failure === InitiativeFailureType.INSTRUMENT_DELETE_FAILURE
      ),
      O.map(() => setStagedWalletId(undefined))
    );
  }, [failure]);

  const handleBackPress = () => machine.send({ type: "back" });

  const handleSkipButton = () => machine.send({ type: "skip-instruments" });

  const handleContinueButton = () => machine.send({ type: "next" });

  const handleAddPaymentMethodButton = () =>
    navigation.replace(ROUTES.WALLET_NAVIGATOR, {
      screen: ROUTES.WALLET_ADD_PAYMENT_METHOD,
      params: { inPayment: O.none }
    });

  const handleEnrollConfirm = () => {
    if (stagedWalletId) {
      machine.send({
        type: "enroll-instrument",
        walletId: stagedWalletId.toString()
      });
      setStagedWalletId(undefined);
    }
  };

  const enrollmentBottomSheetModal = useIOBottomSheetAutoresizableModal(
    {
      component: (
        <Body>
          {I18n.t("idpay.configuration.instruments.enrollmentSheet.bodyFirst")}
          <Body weight="Semibold">
            {I18n.t(
              "idpay.configuration.instruments.enrollmentSheet.bodyBold"
            ) + "\n"}
          </Body>
          {I18n.t("idpay.configuration.instruments.enrollmentSheet.bodyLast")}
        </Body>
      ),
      title: I18n.t("idpay.configuration.instruments.enrollmentSheet.header"),
      footer: (
        <FooterWithButtons
          type="TwoButtonsInlineThird"
          primary={{
            type: "Solid",
            buttonProps: {
              label: I18n.t(
                "idpay.configuration.instruments.enrollmentSheet.buttons.activate"
              ),
              accessibilityLabel: I18n.t(
                "idpay.configuration.instruments.enrollmentSheet.buttons.activate"
              ),
              onPress: handleEnrollConfirm
            }
          }}
          secondary={{
            type: "Outline",
            buttonProps: {
              label: I18n.t(
                "idpay.configuration.instruments.enrollmentSheet.buttons.cancel"
              ),
              accessibilityLabel: I18n.t(
                "idpay.configuration.instruments.enrollmentSheet.buttons.cancel"
              ),
              onPress: () => {
                enrollmentBottomSheetModal.dismiss();
              }
            }
          }}
        />
      ),
      onDismiss: () => {
        setStagedWalletId(undefined);
      }
    },
    175
  );

  React.useEffect(() => {
    if (stagedWalletId) {
      enrollmentBottomSheetModal.present();
    } else {
      enrollmentBottomSheetModal.dismiss();
    }
  }, [enrollmentBottomSheetModal, stagedWalletId]);

  const renderFooterButtons = () => {
    if (isInstrumentsOnlyMode) {
      return (
        <FooterWithButtons
          type="SingleButton"
          primary={{
            type: "Solid",
            buttonProps: {
              label: I18n.t(
                "idpay.configuration.instruments.buttons.addMethod"
              ),
              accessibilityLabel: I18n.t(
                "idpay.configuration.instruments.buttons.addMethod"
              ),
              onPress: handleAddPaymentMethodButton,
              disabled: isUpserting
            }
          }}
        />
      );
    }

    return (
      <FooterWithButtons
        type="TwoButtonsInlineHalf"
        primary={{
          type: "Outline",
          buttonProps: {
            label: I18n.t("idpay.configuration.instruments.buttons.skip"),
            accessibilityLabel: I18n.t(
              "idpay.configuration.instruments.buttons.skip"
            ),
            onPress: handleSkipButton,
            disabled: isUpserting
          }
        }}
        secondary={{
          type: "Solid",
          buttonProps: {
            label: !isUpserting
              ? I18n.t("idpay.configuration.instruments.buttons.continue")
              : "",
            accessibilityLabel: !isUpserting
              ? I18n.t("idpay.configuration.instruments.buttons.continue")
              : "",
            disabled: isUpserting || !hasSelectedInstruments,
            onPress: handleContinueButton,
            loading: isUpserting
          }
        }}
      />
    );
  };

  const handleInstrumentValueChange = (wallet: Wallet) => (value: boolean) => {
    if (value) {
      setStagedWalletId(wallet.idWallet);
    } else {
      const instrument = initiativeInstrumentsByIdWallet[wallet.idWallet];
      machine.send({
        type: "delete-instrument",
        instrumentId: instrument.instrumentId,
        walletId: wallet.idWallet.toString()
      });
    }
  };

  const initiativeName = pipe(
    initiativeDetails,
    O.map(i => i.initiativeName),
    O.toUndefined
  );

  return (
    <>
      <BaseScreenComponent
        goBack={handleBackPress}
        headerTitle={I18n.t(
          isInstrumentsOnlyMode
            ? "idpay.configuration.instruments.title"
            : "idpay.configuration.headerTitle"
        )}
        contextualHelp={emptyContextualHelp}
      >
        <LoadingSpinnerOverlay isLoading={isLoading} loadingOpacity={1}>
          <ScrollView
            style={[IOStyles.flex, IOStyles.horizontalContentPadding]}
          >
            <VSpacer size={16} />
            <H1>{I18n.t("idpay.configuration.instruments.header")}</H1>
            <VSpacer size={8} />
            <Body>
              {I18n.t("idpay.configuration.instruments.body", {
                initiativeName
              })}
            </Body>
            <VSpacer size={24} />
            {walletInstruments.map(walletInstrument => (
              <InstrumentEnrollmentSwitch
                key={walletInstrument.idWallet}
                wallet={walletInstrument}
                isStaged={stagedWalletId === walletInstrument.idWallet}
                onValueChange={handleInstrumentValueChange(walletInstrument)}
              />
            ))}
            <VSpacer size={16} />
            <AdviceComponent
              iconName="navWallet"
              iconColor="bluegrey"
              text={I18n.t("idpay.configuration.instruments.footer")}
            />
            <VSpacer size={16} />
          </ScrollView>
          {renderFooterButtons()}
        </LoadingSpinnerOverlay>
      </BaseScreenComponent>
      {enrollmentBottomSheetModal.bottomSheet}
    </>
  );
};