teamdigitale/italia-app

View on GitHub
ts/features/services/details/screens/ServiceDetailsScreen.tsx

Summary

Maintainability
C
1 day
Test Coverage
import { IOVisualCostants, VSpacer } from "@pagopa/io-app-design-system";
import { useFocusEffect, useLinkTo } from "@react-navigation/native";
import * as O from "fp-ts/lib/Option";
import { pipe } from "fp-ts/lib/function";
import React, { useCallback, useEffect, useMemo } from "react";
import { StyleSheet, View } from "react-native";
import { ServiceId } from "../../../../../definitions/backend/ServiceId";
import { IOStackNavigationRouteProps } from "../../../../navigation/params/AppParamsList";
import { useIODispatch, useIOSelector } from "../../../../store/hooks";
import { useOnFirstRender } from "../../../../utils/hooks/useOnFirstRender";
import { logosForService } from "../../common/utils";
import { CTA, CTAS } from "../../../messages/types/MessageCTA";
import {
  CTAActionType,
  getServiceCTA,
  handleCtaAction
} from "../../../messages/utils/messages";
import * as analytics from "../../common/analytics";
import { CtaCategoryType } from "../../common/analytics";
import { ServicesHeaderSection } from "../../common/components/ServicesHeaderSection";
import { useFirstRender } from "../../common/hooks/useFirstRender";
import { ServicesParamsList } from "../../common/navigation/params";
import {
  CardWithMarkdownContent,
  CardWithMarkdownContentSkeleton
} from "../components/CardWithMarkdownContent";
import { ServiceDetailsFailure } from "../components/ServiceDetailsFailure";
import { ServiceDetailsMetadata } from "../components/ServiceDetailsMetadata";
import { ServiceDetailsPreferences } from "../components/ServiceDetailsPreferences";
import {
  ServiceActionsProps,
  ServiceDetailsScreenComponent
} from "../components/ServiceDetailsScreenComponent";
import { ServiceDetailsTosAndPrivacy } from "../components/ServiceDetailsTosAndPrivacy";
import { loadServiceDetail } from "../store/actions/details";
import { loadServicePreference } from "../store/actions/preference";
import {
  isErrorServiceByIdSelector,
  isLoadingServiceByIdSelector,
  serviceByIdSelector,
  serviceMetadataByIdSelector,
  serviceMetadataInfoSelector
} from "../store/reducers";
import { ServiceMetadataInfo } from "../types/ServiceMetadataInfo";
import { trackAuthenticationStart } from "../../../fims/common/analytics";

export type ServiceDetailsScreenRouteParams = {
  serviceId: ServiceId;
  // if true the service should be activated automatically
  // as soon as the screen is shown (used for custom activation
  // flows like PN)
  activate?: boolean;
};

type ServiceDetailsScreenProps = IOStackNavigationRouteProps<
  ServicesParamsList,
  "SERVICE_DETAIL"
>;

const headerPaddingBottom: number = 138;

const styles = StyleSheet.create({
  cardContainer: {
    marginTop: -headerPaddingBottom,
    minHeight: headerPaddingBottom,
    paddingHorizontal: IOVisualCostants.appMarginDefault
  }
});

export const ServiceDetailsScreen = ({ route }: ServiceDetailsScreenProps) => {
  const { serviceId, activate } = route.params;

  const linkTo = useLinkTo();
  const dispatch = useIODispatch();
  const isFirstRender = useFirstRender();

  const service = useIOSelector(state => serviceByIdSelector(state, serviceId));

  const isLoadingService = useIOSelector(state =>
    isLoadingServiceByIdSelector(state, serviceId)
  );

  const isErrorService = useIOSelector(state =>
    isErrorServiceByIdSelector(state, serviceId)
  );

  const serviceMetadata = useIOSelector(state =>
    serviceMetadataByIdSelector(state, serviceId)
  );

  const serviceMetadataInfo = useIOSelector(state =>
    serviceMetadataInfoSelector(state, serviceId)
  );

  const serviceCtas = useMemo(
    () => pipe(serviceMetadata, getServiceCTA, O.toUndefined),
    [serviceMetadata]
  );

  useOnFirstRender(
    () => {
      analytics.trackServiceDetails({
        bottom_cta_available: !!serviceMetadata?.cta,
        organization_fiscal_code: service?.organization_fiscal_code ?? "",
        organization_name: service?.organization_name ?? "",
        service_category: serviceMetadataInfo.isSpecialService
          ? "special"
          : "standard",
        service_id: serviceId,
        service_name: service?.service_name ?? ""
      });
    },
    () => !!service
  );

  useEffect(() => {
    dispatch(loadServiceDetail.request(serviceId));
  }, [dispatch, serviceId]);

  useFocusEffect(
    useCallback(() => {
      dispatch(loadServicePreference.request(serviceId));
    }, [serviceId, dispatch])
  );

  if (isErrorService) {
    return <ServiceDetailsFailure serviceId={serviceId} />;
  }

  if (isFirstRender || isLoadingService) {
    return (
      <ServiceDetailsScreenComponent>
        <ServicesHeaderSection
          extraBottomPadding={headerPaddingBottom}
          isLoading={true}
        />
        <View style={styles.cardContainer}>
          <CardWithMarkdownContentSkeleton />
        </View>
      </ServiceDetailsScreenComponent>
    );
  }

  if (!service) {
    return null;
  }

  const handlePressCta = (cta: CTA, ctaType: CtaCategoryType) => {
    analytics.trackServiceDetailsCtaTapped({
      cta_category: ctaType,
      service_id: serviceId
    });
    handleCtaAction(cta, linkTo, (type: CTAActionType) => {
      if (type === "fims") {
        trackAuthenticationStart(
          service.service_id,
          service.service_name,
          service.organization_name,
          service.organization_fiscal_code,
          cta.text,
          "service_detail"
        );
      }
    });
  };

  const getActionsProps = (
    serviceMetadataInfo: ServiceMetadataInfo,
    ctas?: CTAS
  ): ServiceActionsProps | undefined => {
    const customSpecialFlow = serviceMetadataInfo?.customSpecialFlow;
    const isSpecialService = serviceMetadataInfo.isSpecialService;

    if (isSpecialService && ctas?.cta_1 && ctas.cta_2) {
      const { cta_1, cta_2 } = ctas;

      return {
        type: "TwoCtasWithCustomFlow",
        primaryActionProps: {
          serviceId,
          activate,
          customSpecialFlowOpt: customSpecialFlow
        },
        secondaryActionProps: {
          label: cta_1.text,
          accessibilityLabel: cta_1.text,
          onPress: () => handlePressCta(cta_1, "custom_1")
        },
        tertiaryActionProps: {
          label: cta_2.text,
          accessibilityLabel: cta_2.text,
          onPress: () => handlePressCta(cta_2, "custom_2")
        }
      };
    }

    if (isSpecialService && ctas?.cta_1) {
      const { cta_1 } = ctas;

      return {
        type: "SingleCtaWithCustomFlow",
        primaryActionProps: {
          serviceId,
          activate,
          customSpecialFlowOpt: customSpecialFlow
        },
        secondaryActionProps: {
          label: cta_1.text,
          accessibilityLabel: cta_1.text,
          onPress: () => handlePressCta(cta_1, "custom_1")
        }
      };
    }

    if (ctas?.cta_1 && ctas?.cta_2) {
      const { cta_1, cta_2 } = ctas;

      return {
        type: "TwoCtas",
        primaryActionProps: {
          label: cta_1.text,
          accessibilityLabel: cta_1.text,
          onPress: () => handlePressCta(cta_1, "custom_1")
        },
        secondaryActionProps: {
          label: cta_2.text,
          accessibilityLabel: cta_2.text,
          onPress: () => handlePressCta(cta_2, "custom_2")
        }
      };
    }

    if (ctas?.cta_1) {
      return {
        type: "SingleCta",
        primaryActionProps: {
          label: ctas.cta_1.text,
          accessibilityLabel: ctas.cta_1.text,
          onPress: () => handlePressCta(ctas.cta_1, "custom_1")
        }
      };
    }

    if (isSpecialService) {
      return {
        type: "SingleCtaCustomFlow",
        primaryActionProps: {
          serviceId,
          activate,
          customSpecialFlowOpt: customSpecialFlow
        }
      };
    }

    return undefined;
  };

  const {
    organization_name,
    organization_fiscal_code,
    service_id,
    service_name,
    available_notification_channels,
    service_metadata
  } = service;

  return (
    <ServiceDetailsScreenComponent
      actionsProps={getActionsProps(serviceMetadataInfo, serviceCtas)}
      title={service_name}
    >
      <ServicesHeaderSection
        extraBottomPadding={headerPaddingBottom}
        logoUri={logosForService(service)}
        title={service_name}
        subTitle={organization_name}
      />
      {service_metadata?.description && (
        <View style={styles.cardContainer}>
          <CardWithMarkdownContent content={service_metadata.description} />
        </View>
      )}

      <ServiceDetailsTosAndPrivacy serviceId={service_id} />

      <VSpacer size={40} />
      <ServiceDetailsPreferences
        serviceId={service_id}
        availableChannels={available_notification_channels}
      />

      <VSpacer size={40} />
      <ServiceDetailsMetadata
        organizationFiscalCode={organization_fiscal_code}
        serviceId={service_id}
      />
    </ServiceDetailsScreenComponent>
  );
};