diff --git a/ts/components/cta/CTAsBar.tsx b/ts/components/cta/CTAsBar.tsx deleted file mode 100644 index 2ae53aba35b..00000000000 --- a/ts/components/cta/CTAsBar.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import React, { useCallback } from "react"; -import { View } from "react-native"; -import { useLinkTo } from "@react-navigation/native"; -import { - ButtonOutline, - ButtonSolid, - HSpacer, - IOStyles -} from "@pagopa/io-app-design-system"; -import { CTA, CTAS } from "../../features/messages/types/MessageCTA"; -import { ServiceId } from "../../../definitions/backend/ServiceId"; -import { trackPNOptInMessageAccepted } from "../../features/pn/analytics"; -import { handleCtaAction } from "../../features/messages/utils/messages"; -import { usePNOptInMessage } from "../../features/pn/hooks/usePNOptInMessage"; - -type CTAsBarProps = { - ctas: CTAS; - serviceId: ServiceId; -}; - -/** - * render cta_1 and cta_2 if they are defined in the message content as front-matter - * or if they are defined on cta attribute in ServiceMetadata in the ServiceDetailScreen - */ -export const CTAsBar = ({ ctas, serviceId }: CTAsBarProps) => { - const { cta_1, cta_2 } = ctas; - const { - cta1HasServiceNavigationLink, - cta2HasServiceNavigationLink, - isPNOptInMessage - } = usePNOptInMessage(ctas, serviceId); - - const linkTo = useLinkTo(); - - const handleOnPress = useCallback( - (cta: CTA, isServiceNavigationLink: boolean) => { - if (isPNOptInMessage && isServiceNavigationLink) { - trackPNOptInMessageAccepted(); - } - handleCtaAction(cta, linkTo, serviceId); - }, - [isPNOptInMessage, linkTo, serviceId] - ); - - return ( - - {cta_2 && ( - <> - - handleOnPress(cta_2, cta2HasServiceNavigationLink)} - /> - - - - )} - - handleOnPress(cta_1, cta1HasServiceNavigationLink)} - /> - - - ); -}; diff --git a/ts/components/cta/ExtractedCTABar.tsx b/ts/components/cta/ExtractedCTABar.tsx index 52853f482eb..1e9bb07f129 100644 --- a/ts/components/cta/ExtractedCTABar.tsx +++ b/ts/components/cta/ExtractedCTABar.tsx @@ -68,7 +68,7 @@ const ExtractedCTABar: React.FunctionComponent = ( props, linkTo, false, - props.isPNOptInMessage?.cta2HasServiceNavigationLink ?? false, + props.isPNOptInMessage?.cta2LinksToPNService ?? false, ctas.cta_2 ), [ctas.cta_2, linkTo, props] @@ -79,7 +79,7 @@ const ExtractedCTABar: React.FunctionComponent = ( props, linkTo, true, - props.isPNOptInMessage?.cta1HasServiceNavigationLink ?? false, + props.isPNOptInMessage?.cta1LinksToPNService ?? false, ctas.cta_1 ), [ctas.cta_1, linkTo, props] diff --git a/ts/components/cta/__test__/CTAsBar.test.tsx b/ts/components/cta/__test__/CTAsBar.test.tsx deleted file mode 100644 index d495bc76c4b..00000000000 --- a/ts/components/cta/__test__/CTAsBar.test.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import * as React from "react"; -import { createStore } from "redux"; -import { ServiceId } from "../../../../definitions/backend/ServiceId"; -import { CTAS } from "../../../features/messages/types/MessageCTA"; -import { appReducer } from "../../../store/reducers"; -import { applicationChangeState } from "../../../store/actions/application"; -import { renderScreenWithNavigationStoreContext } from "../../../utils/testWrapper"; -import { CTAsBar } from "../CTAsBar"; - -describe("CTAsBar", () => { - it("should match snapshot with one CTA", () => { - const serviceId = "01HRW50F08QYXNP518JYCKVSHP" as ServiceId; - const ctas = { cta_1: { text: "My CTA 1", action: "" } } as CTAS; - const component = renderComponent(serviceId, ctas); - expect(component.toJSON()).toMatchSnapshot(); - }); - it("should match snapshot with both CTAs", () => { - const serviceId = "01HRW50F08QYXNP518JYCKVSHP" as ServiceId; - const ctas = { - cta_1: { text: "My CTA 1", action: "" }, - cta_2: { text: "My CTA 2", action: "" } - } as CTAS; - const component = renderComponent(serviceId, ctas); - expect(component.toJSON()).toMatchSnapshot(); - }); -}); - -const renderComponent = (serviceId: ServiceId, ctas: CTAS) => { - const globalState = appReducer(undefined, applicationChangeState("active")); - const store = createStore(appReducer, globalState as any); - - return renderScreenWithNavigationStoreContext( - () => , - "DUMMY", - {}, - store - ); -}; diff --git a/ts/features/messages/components/MessageDetail/MessageDetailsPaymentButton.tsx b/ts/features/messages/components/MessageDetail/MessageDetailsPaymentButton.tsx new file mode 100644 index 00000000000..09098d972ce --- /dev/null +++ b/ts/features/messages/components/MessageDetail/MessageDetailsPaymentButton.tsx @@ -0,0 +1,46 @@ +import React from "react"; +import { ButtonSolid, useIOToast } from "@pagopa/io-app-design-system"; +import { PaymentData, UIMessageId } from "../../types"; +import { useIODispatch } from "../../../../store/hooks"; +import I18n from "../../../../i18n"; +import { + getRptIdStringFromPaymentData, + initializeAndNavigateToWalletForPayment +} from "../../utils"; + +type MessageDetailsPaymentButtonProps = { + messageId: UIMessageId; + paymentData: PaymentData; + canNavigateToPayment: boolean; + isLoading: boolean; +}; + +export const MessageDetailsPaymentButton = ({ + messageId, + paymentData, + canNavigateToPayment, + isLoading +}: MessageDetailsPaymentButtonProps) => { + const dispatch = useIODispatch(); + const toast = useIOToast(); + return ( + + initializeAndNavigateToWalletForPayment( + messageId, + getRptIdStringFromPaymentData(paymentData), + false, + paymentData.amount, + canNavigateToPayment, + dispatch, + false, + () => toast.error(I18n.t("genericError")) + ) + } + fullWidth + loading={isLoading} + /> + ); +}; diff --git a/ts/features/messages/components/MessageDetail/MessageDetailsScrollViewAdditionalSpace.tsx b/ts/features/messages/components/MessageDetail/MessageDetailsScrollViewAdditionalSpace.tsx index faf56918188..dba6e46b300 100644 --- a/ts/features/messages/components/MessageDetail/MessageDetailsScrollViewAdditionalSpace.tsx +++ b/ts/features/messages/components/MessageDetail/MessageDetailsScrollViewAdditionalSpace.tsx @@ -9,25 +9,29 @@ import { gapBetweenItemsInAGrid } from "../../utils"; type ScrollViewAdditionalSpaceProps = { messageId: UIMessageId; - hasCTAS: boolean; + hasCTA1: boolean; + hasCTA2: boolean; }; export const MessageDetailsScrollViewAdditionalSpace = ({ messageId, - hasCTAS + hasCTA1, + hasCTA2 }: ScrollViewAdditionalSpaceProps) => { const safeAreaInsets = useSafeAreaInsets(); const isShowingPaymentButton = useIOSelector(state => isPaymentsButtonVisibleSelector(state, messageId) ); - const stickyFooterRowHeight = - IOStyles.footer.paddingBottom + buttonSolidHeight + gapBetweenItemsInAGrid; + const hasAtLeastAButton = isShowingPaymentButton || hasCTA1 || hasCTA2; const height = - safeAreaInsets.bottom + + (hasAtLeastAButton ? IOStyles.footer.paddingBottom : 0) + + (isShowingPaymentButton ? buttonSolidHeight + gapBetweenItemsInAGrid : 0) + + (hasCTA1 ? buttonSolidHeight + gapBetweenItemsInAGrid : 0) + + (hasCTA2 ? buttonSolidHeight + gapBetweenItemsInAGrid : 0) + + gapBetweenItemsInAGrid + IOStyles.footer.paddingBottom + - (isShowingPaymentButton ? stickyFooterRowHeight : 0) + - (hasCTAS ? stickyFooterRowHeight : 0); + safeAreaInsets.bottom; return ( + footerData.tag === "None"; + +const foldFooterData = ( + footerData: FooterData, + onPaymentWithDoubleCTA: ( + paymentWithDoubleCTA: FooterPaymentWithDoubleCTA + ) => JSX.Element, + onPaymentWithCTA: (paymentWithCTA: FooterPaymentWithCTA) => JSX.Element, + onDoubleCTA: (doubleCTA: FooterDoubleCTA) => JSX.Element, + onPayment: (paymentCTA: FooterPayment) => JSX.Element, + onCTA: (cta: FooterCTA) => JSX.Element, + onNone: () => JSX.Element | null +) => { + switch (footerData.tag) { + case "PaymentWithDoubleCTA": + return onPaymentWithDoubleCTA(footerData); + case "PaymentWithCTA": + return onPaymentWithCTA(footerData); + case "DoubleCTA": + return onDoubleCTA(footerData); + case "Payment": + return onPayment(footerData); + case "CTA": + return onCTA(footerData); + } + return onNone(); +}; + +const computeFooterData = ( + paymentData: PaymentData | undefined, + paymentButtonStatus: "hidden" | "loading" | "enabled", + ctas: CTAS | undefined +): FooterData => { + const isPaymentButtonVisible = + paymentData && paymentButtonStatus !== "hidden"; + const isCTA1Visible = !!ctas?.cta_1; + const cta2 = ctas?.cta_2; + const isCTA2Visible = !!cta2; + if (isPaymentButtonVisible && isCTA1Visible && isCTA2Visible) { + return { + tag: "PaymentWithDoubleCTA", + cta1: ctas.cta_1, + cta2, + paymentData + }; + } else if (isPaymentButtonVisible && isCTA1Visible) { + return { + tag: "PaymentWithCTA", + cta1: ctas.cta_1, + paymentData + }; + } else if (isCTA1Visible && isCTA2Visible) { + return { + tag: "DoubleCTA", + cta1: ctas.cta_1, + cta2 + }; + } else if (isPaymentButtonVisible) { + return { + tag: "Payment", + paymentData + }; + } else if (isCTA1Visible) { + return { + tag: "CTA", + cta1: ctas.cta_1 + }; + } + return { tag: "None" }; +}; + +const renderPaymentWithDoubleCTA = ( + messageId: UIMessageId, + paymentData: PaymentData, + canNavigateToPayment: boolean, + isLoadingPayment: boolean, + cta1: CTA, + cta1IsPNOptInMessage: boolean, + cta2: CTA, + cta2IsPNOptInMessage: boolean, + onCTAPress: (cta: CTA, isPNOptInMessage: boolean) => void +) => ( + <> + + + onCTAPress(cta1, cta1IsPNOptInMessage)} + /> + + + onCTAPress(cta2, cta2IsPNOptInMessage)} + /> + + +); +const renderPaymentWithCTA = ( + messageId: UIMessageId, + paymentData: PaymentData, + canNavigateToPayment: boolean, + isLoadingPayment: boolean, + cta1: CTA, + cta1IsPNOptInMessage: boolean, + onCTAPress: (cta: CTA, isPNOptInMessage: boolean) => void +) => ( + <> + + + + onCTAPress(cta1, cta1IsPNOptInMessage)} + /> + + +); +const renderDoubleCTA = ( + cta1: CTA, + cta1IsPNOptInMessage: boolean, + cta2: CTA, + cta2IsPNOptInMessage: boolean, + onCTAPress: (cta: CTA, isPNOptInMessage: boolean) => void +) => ( + <> + onCTAPress(cta1, cta1IsPNOptInMessage)} + /> + + + onCTAPress(cta2, cta2IsPNOptInMessage)} + /> + + +); +const renderPayment = ( + messageId: UIMessageId, + paymentData: PaymentData, + canNavigateToPayment: boolean, + isLoadingPayment: boolean +) => ( + +); +const renderCTA = ( + cta: CTA, + isPNOptInMessage: boolean, + onCTAPress: (cta: CTA, isPNOptInMessage: boolean) => void +) => ( + onCTAPress(cta, isPNOptInMessage)} + /> +); + export const MessageDetailsStickyFooter = ({ ctas, + firstCTAIsPNOptInMessage, messageId, + secondCTAIsPNOptInMessage, serviceId }: MessageDetailsPaymentButtonProps) => { - const dispatch = useIODispatch(); - const toast = useIOToast(); const safeAreaInsets = useSafeAreaInsets(); const paymentData = useIOSelector(state => messagePaymentDataSelector(state, messageId) ); - const componentVisibility = useIOSelector(state => + const paymentButtonStatus = useIOSelector(state => paymentsButtonStateSelector(state, messageId) ); const canNavigateToPayment = useIOSelector(state => canNavigateToPaymentFromMessageSelector(state) ); - const hidePaymentButton = !paymentData || componentVisibility === "hidden"; - if (!ctas && hidePaymentButton) { + + const linkTo = useLinkTo(); + const handleOnPress = React.useCallback( + (cta: CTA, isPNOptInMessage: boolean) => { + if (isPNOptInMessage) { + trackPNOptInMessageAccepted(); + } + handleCtaAction(cta, linkTo, serviceId); + }, + [linkTo, serviceId] + ); + + const footerData = computeFooterData(paymentData, paymentButtonStatus, ctas); + if (isNone(footerData)) { return null; } + + const isPaymentLoading = paymentButtonStatus === "loading"; return ( - {ctas && ( - <> - - {!hidePaymentButton && } - - )} - {!hidePaymentButton && ( - - initializeAndNavigateToWalletForPayment( - messageId, - getRptIdStringFromPaymentData(paymentData), - false, - paymentData.amount, - canNavigateToPayment, - dispatch, - false, - () => toast.error(I18n.t("genericError")) - ) - } - fullWidth - loading={componentVisibility === "loading"} - /> + {foldFooterData( + footerData, + paymentWithDoubleCTA => + renderPaymentWithDoubleCTA( + messageId, + paymentWithDoubleCTA.paymentData, + canNavigateToPayment, + isPaymentLoading, + paymentWithDoubleCTA.cta1, + firstCTAIsPNOptInMessage, + paymentWithDoubleCTA.cta2, + secondCTAIsPNOptInMessage, + handleOnPress + ), + paymentWithCTA => + renderPaymentWithCTA( + messageId, + paymentWithCTA.paymentData, + canNavigateToPayment, + isPaymentLoading, + paymentWithCTA.cta1, + firstCTAIsPNOptInMessage, + handleOnPress + ), + doubleCTA => + renderDoubleCTA( + doubleCTA.cta1, + firstCTAIsPNOptInMessage, + doubleCTA.cta2, + secondCTAIsPNOptInMessage, + handleOnPress + ), + payment => + renderPayment( + messageId, + payment.paymentData, + canNavigateToPayment, + isPaymentLoading + ), + cta => renderCTA(cta.cta1, firstCTAIsPNOptInMessage, handleOnPress), + () => null )} ); diff --git a/ts/features/messages/components/MessageDetail/__tests__/MessageDetailsPaymentButton.test.tsx b/ts/features/messages/components/MessageDetail/__tests__/MessageDetailsPaymentButton.test.tsx new file mode 100644 index 00000000000..7a1c32f65c9 --- /dev/null +++ b/ts/features/messages/components/MessageDetail/__tests__/MessageDetailsPaymentButton.test.tsx @@ -0,0 +1,50 @@ +import * as React from "react"; +import { createStore } from "redux"; +import { applicationChangeState } from "../../../../../store/actions/application"; +import { preferencesDesignSystemSetEnabled } from "../../../../../store/actions/persistedPreferences"; +import { appReducer } from "../../../../../store/reducers"; +import { MessageDetailsPaymentButton } from "../MessageDetailsPaymentButton"; +import { renderScreenWithNavigationStoreContext } from "../../../../../utils/testWrapper"; +import { PaymentData, UIMessageId } from "../../../types"; + +describe("MessageDetailsPaymentButton", () => { + it("should match snapshot when not loading", () => { + const screen = renderScreen(false); + expect(screen.toJSON()).toMatchSnapshot(); + }); + it("should match snapshot when loading", () => { + const screen = renderScreen(true); + expect(screen.toJSON()).toMatchSnapshot(); + }); +}); + +const renderScreen = (isLoading: boolean) => { + const initialState = appReducer(undefined, applicationChangeState("active")); + const designSystemState = appReducer( + initialState, + preferencesDesignSystemSetEnabled({ isDesignSystemEnabled: true }) + ); + const store = createStore(appReducer, designSystemState as any); + + return renderScreenWithNavigationStoreContext( + () => ( + + ), + "DUMMY", + {}, + store + ); +}; diff --git a/ts/features/messages/components/MessageDetail/__tests__/MessageDetailsScrollViewAdditionalSpace.test.tsx b/ts/features/messages/components/MessageDetail/__tests__/MessageDetailsScrollViewAdditionalSpace.test.tsx index c5c66592afc..bc1d8571115 100644 --- a/ts/features/messages/components/MessageDetail/__tests__/MessageDetailsScrollViewAdditionalSpace.test.tsx +++ b/ts/features/messages/components/MessageDetail/__tests__/MessageDetailsScrollViewAdditionalSpace.test.tsx @@ -16,34 +16,49 @@ describe("MessageDetailsScrollViewAdditionalSpace", () => { jest .spyOn(payments, "isPaymentsButtonVisibleSelector") .mockReturnValue(false); - const component = renderComponent(false); + const component = renderComponent(false, false); expect(component.toJSON()).toMatchSnapshot(); }); - it("Should match snapshot with hidden button and CTAs", () => { + it("Should match snapshot with hidden button and both CTAs", () => { jest .spyOn(payments, "isPaymentsButtonVisibleSelector") .mockReturnValue(false); - const component = renderComponent(true); + const component = renderComponent(true, true); + expect(component.toJSON()).toMatchSnapshot(); + }); + it("Should match snapshot with hidden button and a single CTA", () => { + jest + .spyOn(payments, "isPaymentsButtonVisibleSelector") + .mockReturnValue(false); + const component = renderComponent(true, false); expect(component.toJSON()).toMatchSnapshot(); }); it("Should match snapshot with button and no CTAs", () => { jest .spyOn(payments, "isPaymentsButtonVisibleSelector") .mockReturnValue(true); - const component = renderComponent(false); + const component = renderComponent(false, false); + expect(component.toJSON()).toMatchSnapshot(); + }); + it("Should match snapshot with button and both CTA", () => { + jest + .spyOn(payments, "isPaymentsButtonVisibleSelector") + .mockReturnValue(true); + const component = renderComponent(true, true); expect(component.toJSON()).toMatchSnapshot(); }); - it("Should match snapshot with button and CTAs", () => { + it("Should match snapshot with button and a single CTA", () => { jest .spyOn(payments, "isPaymentsButtonVisibleSelector") .mockReturnValue(true); - const component = renderComponent(true); + const component = renderComponent(true, false); expect(component.toJSON()).toMatchSnapshot(); }); }); const renderComponent = ( - hasCTAS: boolean, + hasCTA1: boolean, + hasCTA2: boolean, messageId: UIMessageId = "01HRW5J2QYMH3FWAA5CYGXSC84" as UIMessageId ) => { const globalState = appReducer(undefined, applicationChangeState("active")); @@ -52,7 +67,8 @@ const renderComponent = ( () => ( ), "DUMMY", diff --git a/ts/features/messages/components/MessageDetail/__tests__/MessageDetailsStickyFooter.test.tsx b/ts/features/messages/components/MessageDetail/__tests__/MessageDetailsStickyFooter.test.tsx index 7e076a8e5c8..0e8103073f8 100644 --- a/ts/features/messages/components/MessageDetail/__tests__/MessageDetailsStickyFooter.test.tsx +++ b/ts/features/messages/components/MessageDetail/__tests__/MessageDetailsStickyFooter.test.tsx @@ -128,6 +128,8 @@ const renderComponent = (ctas?: CTAS) => { messageId={"01HRW6GJBD594Z0K9B4D6KAERC" as UIMessageId} serviceId={"01HRW6GS171WY97ZBJ5BPFJ625" as ServiceId} ctas={ctas} + firstCTAIsPNOptInMessage={false} + secondCTAIsPNOptInMessage={false} /> ), "DUMMY", diff --git a/ts/components/cta/__test__/__snapshots__/CTAsBar.test.tsx.snap b/ts/features/messages/components/MessageDetail/__tests__/__snapshots__/MessageDetailsPaymentButton.test.tsx.snap similarity index 59% rename from ts/components/cta/__test__/__snapshots__/CTAsBar.test.tsx.snap rename to ts/features/messages/components/MessageDetail/__tests__/__snapshots__/MessageDetailsPaymentButton.test.tsx.snap index e5823eeaabd..b66722bdcd5 100644 --- a/ts/components/cta/__test__/__snapshots__/CTAsBar.test.tsx.snap +++ b/ts/features/messages/components/MessageDetail/__tests__/__snapshots__/MessageDetailsPaymentButton.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`CTAsBar should match snapshot with both CTAs 1`] = ` +exports[`MessageDetailsPaymentButton should match snapshot when loading 1`] = ` - - - - My CTA 2 - - - - - - + - - - My CTA 1 - + + + + + + + + + + + + @@ -560,7 +578,7 @@ exports[`CTAsBar should match snapshot with both CTAs 1`] = ` `; -exports[`CTAsBar should match snapshot with one CTA 1`] = ` +exports[`MessageDetailsPaymentButton should match snapshot when not loading 1`] = ` - - - - My CTA 1 - - - + Pay + diff --git a/ts/features/messages/components/MessageDetail/__tests__/__snapshots__/MessageDetailsScrollViewAdditionalSpace.test.tsx.snap b/ts/features/messages/components/MessageDetail/__tests__/__snapshots__/MessageDetailsScrollViewAdditionalSpace.test.tsx.snap index 38203e85a2b..c9138d1c9bf 100644 --- a/ts/features/messages/components/MessageDetail/__tests__/__snapshots__/MessageDetailsScrollViewAdditionalSpace.test.tsx.snap +++ b/ts/features/messages/components/MessageDetail/__tests__/__snapshots__/MessageDetailsScrollViewAdditionalSpace.test.tsx.snap @@ -1,6 +1,684 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`MessageDetailsScrollViewAdditionalSpace Should match snapshot with button and CTAs 1`] = ` +exports[`MessageDetailsScrollViewAdditionalSpace Should match snapshot with button and a single CTA 1`] = ` + + + + + + + + + + + + + + + DUMMY + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; + +exports[`MessageDetailsScrollViewAdditionalSpace Should match snapshot with button and both CTA 1`] = ` + + + + + + + + + + + + + + + DUMMY + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; + +exports[`MessageDetailsScrollViewAdditionalSpace Should match snapshot with button and no CTAs 1`] = ` @@ -339,7 +1017,7 @@ exports[`MessageDetailsScrollViewAdditionalSpace Should match snapshot with butt `; -exports[`MessageDetailsScrollViewAdditionalSpace Should match snapshot with button and no CTAs 1`] = ` +exports[`MessageDetailsScrollViewAdditionalSpace Should match snapshot with hidden button and a single CTA 1`] = ` @@ -678,7 +1356,7 @@ exports[`MessageDetailsScrollViewAdditionalSpace Should match snapshot with butt `; -exports[`MessageDetailsScrollViewAdditionalSpace Should match snapshot with hidden button and CTAs 1`] = ` +exports[`MessageDetailsScrollViewAdditionalSpace Should match snapshot with hidden button and both CTAs 1`] = ` @@ -1339,7 +2017,7 @@ exports[`MessageDetailsScrollViewAdditionalSpace Should match snapshot with hidd diff --git a/ts/features/messages/components/MessageDetail/__tests__/__snapshots__/MessageDetailsStickyFooter.test.tsx.snap b/ts/features/messages/components/MessageDetail/__tests__/__snapshots__/MessageDetailsStickyFooter.test.tsx.snap index 86f13b0a1a6..93ca6778f3c 100644 --- a/ts/features/messages/components/MessageDetail/__tests__/__snapshots__/MessageDetailsStickyFooter.test.tsx.snap +++ b/ts/features/messages/components/MessageDetail/__tests__/__snapshots__/MessageDetailsStickyFooter.test.tsx.snap @@ -349,230 +349,213 @@ exports[`MessageDetailsStickyFooter should match snapshot with both CTAs and no } > - - - CTA 2 - - + CTA 1 + + + + - - - - - CTA 1 - - - + CTA 2 + @@ -939,230 +922,110 @@ exports[`MessageDetailsStickyFooter should match snapshot with both CTAs and vis } > - - - - CTA 2 - - - - - - - - - - CTA 1 - - - + Pay + @@ -1174,11 +1037,10 @@ exports[`MessageDetailsStickyFooter should match snapshot with both CTAs and vis } /> + + CTA 1 + + + + + + @@ -1245,38 +1202,28 @@ exports[`MessageDetailsStickyFooter should match snapshot with both CTAs and vis accessibilityElementsHidden={true} accessible={false} allowFontScaling={false} - color="white" - defaultColor="white" - defaultWeight="Bold" ellipsizeMode="tail" - font="TitilliumWeb" - fontStyle={ - Object { - "fontSize": 16, - } - } importantForAccessibility="no-hide-descendants" maxFontSizeMultiplier={1.3} numberOfLines={1} style={ Array [ Object { - "alignSelf": "center", - }, - Object { - "fontSize": 16, - }, - Object { - "color": "#FFFFFF", "fontFamily": "Titillium Web", + "fontSize": 16, "fontStyle": "normal", "fontWeight": "700", }, + Object { + "color": "#0073E6", + }, + Object { + "color": undefined, + }, ] } - weight="Bold" > - Pay + CTA 2 @@ -3384,126 +3331,110 @@ exports[`MessageDetailsStickyFooter should match snapshot with one CTA and no pa } > - - - - CTA 1 - - - + CTA 1 + @@ -3869,137 +3800,6 @@ exports[`MessageDetailsStickyFooter should match snapshot with one CTA and visib ] } > - - - - - - - CTA 1 - - - - - - - + + + + + + CTA 1 + + + + diff --git a/ts/features/messages/screens/MessageDetailsScreen.tsx b/ts/features/messages/screens/MessageDetailsScreen.tsx index 6005a87a353..6eebac0b4b7 100644 --- a/ts/features/messages/screens/MessageDetailsScreen.tsx +++ b/ts/features/messages/screens/MessageDetailsScreen.tsx @@ -41,6 +41,12 @@ import { userSelectedPaymentRptIdSelector } from "../store/reducers/payments"; import { MessageDetailsStickyFooter } from "../components/MessageDetail/MessageDetailsStickyFooter"; import { MessageDetailsScrollViewAdditionalSpace } from "../components/MessageDetail/MessageDetailsScrollViewAdditionalSpace"; import { serviceMetadataByIdSelector } from "../../../store/reducers/entities/services/servicesById"; +import { isPNOptInMessage } from "../../pn/utils"; +import { useOnFirstRender } from "../../../utils/hooks/useOnFirstRender"; +import { + trackPNOptInMessageCTADisplaySuccess, + trackPNOptInMessageOpened +} from "../../pn/analytics"; const styles = StyleSheet.create({ scrollContentContainer: { @@ -112,13 +118,27 @@ export const MessageDetailsScreen = (props: MessageDetailsScreenProps) => { [messageMarkdown, serviceId, serviceMetadata] ); + // Use the store since `isPNOptInMessage` is not a selector but an utility + // that uses a backend status configuration that is normally updated every + // minute. We do not want to cause a re-rendering or recompute the value + const store = useIOStore(); + const state = store.getState(); + const pnOptInMessageInfo = isPNOptInMessage(maybeCTAs, serviceId, state); + useHeaderSecondLevel({ title: "", goBack, supportRequest: true }); - const store = useIOStore(); + useOnFirstRender( + () => { + trackPNOptInMessageOpened(); + trackPNOptInMessageCTADisplaySuccess(); + }, + () => pnOptInMessageInfo.isPNOptInMessage + ); + useFocusEffect( useCallback(() => { const globalState = store.getState(); @@ -184,13 +204,16 @@ export const MessageDetailsScreen = (props: MessageDetailsScreenProps) => { diff --git a/ts/features/messages/utils/index.ts b/ts/features/messages/utils/index.ts index 86d14002ed7..4e29b4fb26e 100644 --- a/ts/features/messages/utils/index.ts +++ b/ts/features/messages/utils/index.ts @@ -16,6 +16,7 @@ import { PaymentAmount } from "../../../../definitions/backend/PaymentAmount"; import { getAmountFromPaymentAmount } from "../../../utils/payment"; import { trackPNPaymentStart } from "../../pn/analytics"; import { addUserSelectedPaymentRptId } from "../store/actions"; +import { Action } from "../../../store/actions/types"; import { MessagePaymentExpirationInfo } from "./messages"; export const gapBetweenItemsInAGrid = 8; @@ -55,7 +56,7 @@ export const initializeAndNavigateToWalletForPayment = ( isPaidOrHasAnError: boolean, paymentAmount: PaymentAmount | undefined, canNavigateToPayment: boolean, - dispatch: Dispatch, + dispatch: Dispatch, isPNPayment: boolean, decodeErrorCallback: (() => void) | undefined, preNavigationCallback: (() => void) | undefined = undefined diff --git a/ts/features/pn/hooks/usePNOptInMessage.ts b/ts/features/pn/hooks/usePNOptInMessage.ts deleted file mode 100644 index 47c87c297ea..00000000000 --- a/ts/features/pn/hooks/usePNOptInMessage.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { isPNOptInMessage } from "../utils"; -import { useOnFirstRender } from "../../../utils/hooks/useOnFirstRender"; -import { - trackPNOptInMessageCTADisplaySuccess, - trackPNOptInMessageOpened -} from "../analytics"; -import { ServiceId } from "../../../../definitions/backend/ServiceId"; -import { useIOStore } from "../../../store/hooks"; -import { CTAS } from "../../messages/types/MessageCTA"; - -export const usePNOptInMessage = ( - ctas: CTAS | undefined, - serviceId: ServiceId -) => { - // Use the store since `isPNOptInMessage` is not a selector but an utility - // that uses a backend status configuration that is normally updated every - // minute. We do not want to cause a re-rendering or recompute the value - const store = useIOStore(); - const state = store.getState(); - const pnOptInMessageInfo = isPNOptInMessage(ctas, serviceId, state); - - useOnFirstRender( - () => { - trackPNOptInMessageOpened(); - trackPNOptInMessageCTADisplaySuccess(); - }, - () => pnOptInMessageInfo.isPNOptInMessage - ); - - return pnOptInMessageInfo; -}; diff --git a/ts/features/pn/utils/__tests__/index.test.ts b/ts/features/pn/utils/__tests__/index.test.ts index 23bccc5f4d6..9877ffdf70e 100644 --- a/ts/features/pn/utils/__tests__/index.test.ts +++ b/ts/features/pn/utils/__tests__/index.test.ts @@ -43,8 +43,8 @@ type IsPNOptInMessageTestInputType = { }; output: { isPNOptInMessage: boolean; - cta1HasServiceNavigationLink: boolean; - cta2HasServiceNavigationLink: boolean; + cta1LinksToPNService: boolean; + cta2LinksToPNService: boolean; }; }; @@ -58,8 +58,8 @@ const isPNOptInMessageTestInput: Array = [ }, output: { isPNOptInMessage: true, - cta1HasServiceNavigationLink: true, - cta2HasServiceNavigationLink: true + cta1LinksToPNService: true, + cta2LinksToPNService: true } }, { @@ -75,8 +75,8 @@ const isPNOptInMessageTestInput: Array = [ }, output: { isPNOptInMessage: true, - cta1HasServiceNavigationLink: true, - cta2HasServiceNavigationLink: false + cta1LinksToPNService: true, + cta2LinksToPNService: false } }, { @@ -95,8 +95,8 @@ const isPNOptInMessageTestInput: Array = [ }, output: { isPNOptInMessage: true, - cta1HasServiceNavigationLink: true, - cta2HasServiceNavigationLink: false + cta1LinksToPNService: true, + cta2LinksToPNService: false } }, { @@ -115,8 +115,8 @@ const isPNOptInMessageTestInput: Array = [ }, output: { isPNOptInMessage: true, - cta1HasServiceNavigationLink: false, - cta2HasServiceNavigationLink: true + cta1LinksToPNService: false, + cta2LinksToPNService: true } }, { @@ -134,8 +134,8 @@ const isPNOptInMessageTestInput: Array = [ }, output: { isPNOptInMessage: false, - cta1HasServiceNavigationLink: false, - cta2HasServiceNavigationLink: false + cta1LinksToPNService: false, + cta2LinksToPNService: false } }, { @@ -157,8 +157,8 @@ const isPNOptInMessageTestInput: Array = [ }, output: { isPNOptInMessage: false, - cta1HasServiceNavigationLink: false, - cta2HasServiceNavigationLink: false + cta1LinksToPNService: false, + cta2LinksToPNService: false } }, { @@ -171,8 +171,8 @@ const isPNOptInMessageTestInput: Array = [ }, output: { isPNOptInMessage: false, - cta1HasServiceNavigationLink: false, - cta2HasServiceNavigationLink: false + cta1LinksToPNService: false, + cta2LinksToPNService: false } }, { @@ -185,8 +185,8 @@ const isPNOptInMessageTestInput: Array = [ }, output: { isPNOptInMessage: false, - cta1HasServiceNavigationLink: false, - cta2HasServiceNavigationLink: false + cta1LinksToPNService: false, + cta2LinksToPNService: false } }, { @@ -199,8 +199,8 @@ const isPNOptInMessageTestInput: Array = [ }, output: { isPNOptInMessage: false, - cta1HasServiceNavigationLink: false, - cta2HasServiceNavigationLink: false + cta1LinksToPNService: false, + cta2LinksToPNService: false } }, { @@ -224,8 +224,8 @@ const isPNOptInMessageTestInput: Array = [ }, output: { isPNOptInMessage: false, - cta1HasServiceNavigationLink: false, - cta2HasServiceNavigationLink: false + cta1LinksToPNService: false, + cta2LinksToPNService: false } }, { @@ -243,8 +243,8 @@ const isPNOptInMessageTestInput: Array = [ }, output: { isPNOptInMessage: false, - cta1HasServiceNavigationLink: false, - cta2HasServiceNavigationLink: false + cta1LinksToPNService: false, + cta2LinksToPNService: false } } ]; diff --git a/ts/features/pn/utils/index.ts b/ts/features/pn/utils/index.ts index d70616217f3..48fea153997 100644 --- a/ts/features/pn/utils/index.ts +++ b/ts/features/pn/utils/index.ts @@ -24,8 +24,8 @@ export function getNotificationStatusInfo(status: NotificationStatus) { export type PNOptInMessageInfo = { isPNOptInMessage: boolean; - cta1HasServiceNavigationLink: boolean; - cta2HasServiceNavigationLink: boolean; + cta1LinksToPNService: boolean; + cta2LinksToPNService: boolean; }; export const isPNOptInMessage = ( @@ -50,24 +50,24 @@ export const isPNOptInMessage = ( ctas, O.fromNullable, O.map(ctas => ({ - cta1HasServiceNavigationLink: isServiceDetailNavigationLink( + cta1LinksToPNService: isServiceDetailNavigationLink( ctas.cta_1.action ), - cta2HasServiceNavigationLink: + cta2LinksToPNService: !!ctas.cta_2 && isServiceDetailNavigationLink(ctas.cta_2.action) })), O.map(ctaNavigationLinkInfo => ({ isPNOptInMessage: - ctaNavigationLinkInfo.cta1HasServiceNavigationLink || - ctaNavigationLinkInfo.cta2HasServiceNavigationLink, + ctaNavigationLinkInfo.cta1LinksToPNService || + ctaNavigationLinkInfo.cta2LinksToPNService, ...ctaNavigationLinkInfo })) ) ), O.getOrElse(() => ({ isPNOptInMessage: false, - cta1HasServiceNavigationLink: false, - cta2HasServiceNavigationLink: false + cta1LinksToPNService: false, + cta2LinksToPNService: false })) );