From 305ce627d5f1ccb3aca68383d3404d8c30e3dbf1 Mon Sep 17 00:00:00 2001 From: Damiano Plebani Date: Fri, 29 Nov 2024 15:03:41 +0100 Subject: [PATCH 1/7] Remove `DSLegacyAlert` screen --- ts/components/SectionStatus/StatusContent.tsx | 96 -------- .../__tests__/SectionStatusContent.test.tsx | 22 -- .../SectionStatusContent.test.tsx.snap | 159 ------------ .../SectionStatus/__tests__/index.test.tsx | 230 ------------------ ts/components/SectionStatus/index.tsx | 92 ------- ts/components/SectionStatus/modal/index.tsx | 157 ------------ .../bonus/cdc/components/CdcServiceCTA.tsx | 20 +- .../components/__test__/CdcServiceCTA.test.ts | 180 -------------- .../design-system/core/DSLegacyAlert.tsx | 56 ----- .../design-system/navigation/navigator.tsx | 9 - .../design-system/navigation/params.ts | 1 - .../design-system/navigation/routes.ts | 4 - 12 files changed, 7 insertions(+), 1019 deletions(-) delete mode 100644 ts/components/SectionStatus/StatusContent.tsx delete mode 100644 ts/components/SectionStatus/__tests__/SectionStatusContent.test.tsx delete mode 100644 ts/components/SectionStatus/__tests__/__snapshots__/SectionStatusContent.test.tsx.snap delete mode 100644 ts/components/SectionStatus/__tests__/index.test.tsx delete mode 100644 ts/components/SectionStatus/index.tsx delete mode 100644 ts/components/SectionStatus/modal/index.tsx delete mode 100644 ts/features/bonus/cdc/components/__test__/CdcServiceCTA.test.ts delete mode 100644 ts/features/design-system/core/DSLegacyAlert.tsx diff --git a/ts/components/SectionStatus/StatusContent.tsx b/ts/components/SectionStatus/StatusContent.tsx deleted file mode 100644 index fabf129a958..00000000000 --- a/ts/components/SectionStatus/StatusContent.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import React, { ComponentProps, forwardRef } from "react"; -import { AccessibilityRole, StyleSheet, View } from "react-native"; -import { - IOColors, - IOFontSize, - IOIcons, - Icon, - Label -} from "@pagopa/io-app-design-system"; -import { WithTestID } from "../../types/WithTestID"; -import { LevelEnum } from "../../../definitions/content/SectionStatus"; - -const iconSize = 24; - -const styles = StyleSheet.create({ - container: { - flexDirection: "row", - width: "100%", - paddingLeft: 24, - paddingRight: 24, - paddingBottom: 8, - paddingTop: 8, - alignItems: "flex-start", - alignContent: "center" - }, - alignCenter: { alignSelf: "center" }, - text: { marginLeft: 16, flex: 1 } -}); - -type Props = WithTestID<{ - accessible?: boolean; - accessibilityHint?: string; - accessibilityLabel?: string; - accessibilityRole?: AccessibilityRole; - backgroundColor: IOColors; - iconName: IOIcons; - fontSize?: IOFontSize; - foregroundColor: ComponentProps["color"]; - labelPaddingVertical?: number; -}>; - -export const statusColorMap: Record = { - [LevelEnum.normal]: "aqua", - [LevelEnum.critical]: "red", - [LevelEnum.warning]: "orange" -}; - -export const statusIconMap: Record = { - [LevelEnum.normal]: "ok", - [LevelEnum.critical]: "notice", - [LevelEnum.warning]: "info" -}; - -// map the text background color with the relative text color -export const getStatusTextColor = (level: LevelEnum): "grey-700" | "white" => - level === LevelEnum.normal ? "grey-700" : "white"; - -const StatusContent = forwardRef>( - ( - { - accessible = true, - backgroundColor, - children, - iconName, - fontSize, - foregroundColor, - labelPaddingVertical, - ...rest - }, - ref - ) => ( - - - - - - - ) -); - -export default StatusContent; diff --git a/ts/components/SectionStatus/__tests__/SectionStatusContent.test.tsx b/ts/components/SectionStatus/__tests__/SectionStatusContent.test.tsx deleted file mode 100644 index 64d88674bd4..00000000000 --- a/ts/components/SectionStatus/__tests__/SectionStatusContent.test.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from "react"; -import { View } from "react-native"; -import { render } from "@testing-library/react-native"; - -import StatusContent from "../StatusContent"; - -describe("StatusContent", () => { - it("should match the snapshot", () => { - const viewRef = React.createRef(); - const component = render( - - ); - expect(component.toJSON()).toMatchSnapshot(); - }); -}); diff --git a/ts/components/SectionStatus/__tests__/__snapshots__/SectionStatusContent.test.tsx.snap b/ts/components/SectionStatus/__tests__/__snapshots__/SectionStatusContent.test.tsx.snap deleted file mode 100644 index 133af5f36d6..00000000000 --- a/ts/components/SectionStatus/__tests__/__snapshots__/SectionStatusContent.test.tsx.snap +++ /dev/null @@ -1,159 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`StatusContent should match the snapshot 1`] = ` - - - - - - - - - - - - - -`; diff --git a/ts/components/SectionStatus/__tests__/index.test.tsx b/ts/components/SectionStatus/__tests__/index.test.tsx deleted file mode 100644 index bdf32f573ab..00000000000 --- a/ts/components/SectionStatus/__tests__/index.test.tsx +++ /dev/null @@ -1,230 +0,0 @@ -import { fireEvent } from "@testing-library/react-native"; -import * as O from "fp-ts/lib/Option"; -import * as React from "react"; -import configureMockStore from "redux-mock-store"; -import { IOColors } from "@pagopa/io-app-design-system"; -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { ToolEnum } from "../../../../definitions/content/AssistanceToolConfig"; -import { Config } from "../../../../definitions/content/Config"; -import { - LevelEnum, - SectionStatus -} from "../../../../definitions/content/SectionStatus"; -import I18n, { setLocale } from "../../../i18n"; -import { SectionStatusKey } from "../../../store/reducers/backendStatus/sectionStatus"; -import { renderScreenWithNavigationStoreContext } from "../../../utils/testWrapper"; -import { openWebUrl } from "../../../utils/url"; -import SectionStatusComponent from "../index"; -import { SubscriptionStateEnum } from "../../../../definitions/trial_system/SubscriptionState"; -import { itwTrialId } from "../../../config"; -import { TrialSystemState } from "../../../features/trialSystem/store/reducers"; -import { PersistedFeaturesState } from "../../../features/common/store/reducers"; -import { ItwLifecycleState } from "../../../features/itwallet/lifecycle/store/reducers"; -import { ItWalletState } from "../../../features/itwallet/common/store/reducers"; -import { GlobalState } from "../../../store/reducers/types"; - -jest.mock("../../../utils/url"); - -const sectionStatus: SectionStatus = { - is_visible: true, - level: LevelEnum.normal, - web_url: { - "it-IT": "https://io.italia.it/it/cashback/faq/#n44", - "en-EN": "https://io.italia.it/en/cashback/faq/#n44" - }, - message: { - "it-IT": "message IT", - "en-EN": "message EN" - } -}; - -const mockSectionStatusState = ( - sectionKey: SectionStatusKey, - status: SectionStatus -) => - ({ - sectionStatus: O.some({ [sectionKey]: status }), - remoteConfig: O.some({ - assistanceTool: { tool: ToolEnum.none }, - cgn: { enabled: true }, - newPaymentSection: { - enabled: false, - min_app_version: { - android: "0.0.0.0", - ios: "0.0.0.0" - } - }, - fims: { enabled: true }, - itw: { - enabled: true, - min_app_version: { - android: "0.0.0.0", - ios: "0.0.0.0" - } - } - } as Config), - features: { - itWallet: { - lifecycle: ItwLifecycleState.ITW_LIFECYCLE_INSTALLED - } as ItWalletState - } as PersistedFeaturesState, - trialSystem: { - [itwTrialId]: pot.some(SubscriptionStateEnum.UNSUBSCRIBED) - } as TrialSystemState - } as unknown as GlobalState); - -const mockStore = configureMockStore(); - -describe("SectionStatusComponent", () => { - describe("given different locales", () => { - it("should display the IT message in the label component", () => { - setLocale("it"); - const component = getComponent("messages"); - expect( - component.queryByText(RegExp(sectionStatus.message["it-IT"])) - ).not.toBeNull(); - }); - - it("should display the EN message in the label component", () => { - setLocale("en"); - const component = getComponent("messages"); - expect( - component.queryByText(new RegExp(sectionStatus.message["en-EN"])) - ).not.toBeNull(); - }); - }); - - [ - [LevelEnum.normal, IOColors["info-100"]], - [LevelEnum.warning, IOColors["warning-100"]], - [LevelEnum.critical, IOColors["error-100"]] - ].forEach(([level, color]) => { - describe(`given the level ${level}`, () => { - const store = mockStore( - mockSectionStatusState("messages", { - ...sectionStatus, - web_url: undefined, - level: level as LevelEnum - }) - ); - - it(`should apply background color ${color} to the status content`, () => { - const component = getComponent("messages", store); - const view = component.getByTestId("SectionStatusComponentContent"); - expect(view).toHaveStyle({ backgroundColor: color }); - }); - }); - }); - - describe("when web_url is defined", () => { - it("should display `more info` text", () => { - const component = getComponent("messages"); - expect( - component.queryByText(I18n.t("global.sectionStatus.moreInfo")) - ).not.toBeNull(); - }); - - it("should set the correct a11y properties on the touchable wrapper", () => { - setLocale("it"); - const component = getComponent("messages"); - const view = component.getByTestId("SectionStatusComponentPressable"); - expect(view.props.accessible).toBe(true); - expect(view.props.accessibilityRole).toBe("button"); - }); - - it("should render the touchable wrapper which opens the correct url", () => { - setLocale("it"); - const component = getComponent("messages"); - const touchable = component.getByTestId( - "SectionStatusComponentPressable" - )!; - fireEvent.press(touchable); - expect(openWebUrl).toHaveBeenCalledTimes(1); - expect(openWebUrl).toHaveBeenCalledWith(sectionStatus.web_url!["it-IT"]); - }); - }); - - describe("when web_url is not defined", () => { - const store = mockStore( - mockSectionStatusState("messages", { - ...sectionStatus, - web_url: undefined - }) - ); - - it("should set the correct a11y properties to the status content", () => { - setLocale("it"); - const component = getComponent("messages", store); - const view = component.getByTestId("SectionStatusComponentContent"); - expect(view.props.accessible).toBe(false); - expect(view.props.accessibilityRole).toBe("alert"); - }); - - it("should display the accessibility alert text", () => { - const component = getComponent("messages"); - expect( - component.queryByText(new RegExp(I18n.t("global.accessibility.alert"))) - ).toBeNull(); - }); - - it("should render the touchable wrapper which opens the correct url", () => { - setLocale("it"); - const component = getComponent("messages", store); - const touchable = component.queryByTestId( - "SectionStatusComponentPressable" - )!; - expect(touchable).toBeNull(); - }); - }); -}); - -describe("Section Status Component should return null", () => { - it("when sectionStatus is none", () => { - const component = getComponent( - "messages", - mockStore({ - remoteConfig: O.none, - sectionStatus: O.none, - trialSystem: { - [itwTrialId]: pot.some(SubscriptionStateEnum.UNSUBSCRIBED) - } as TrialSystemState, - features: { - itWallet: { - lifecycle: ItwLifecycleState.ITW_LIFECYCLE_INSTALLED - } as ItWalletState - } as PersistedFeaturesState - }) - ); - expect(component.queryByTestId("SectionStatusComponentLabel")).toBeNull(); - expect( - component.queryByTestId("SectionStatusComponentTouchable") - ).toBeNull(); - }); - - it("when sectionStatus has `is_visible` false", () => { - const component = getComponent( - "messages", - mockStore( - mockSectionStatusState("messages", { - ...sectionStatus, - is_visible: false - }) - ) - ); - expect(component.queryByTestId("SectionStatusComponentLabel")).toBeNull(); - expect( - component.queryByTestId("SectionStatusComponentTouchable") - ).toBeNull(); - }); -}); - -const getComponent = ( - sectionKey: SectionStatusKey, - store?: ReturnType -) => - renderScreenWithNavigationStoreContext( - () => , - "DUMMY", - {}, - store || mockStore(mockSectionStatusState("messages", sectionStatus)) - ); diff --git a/ts/components/SectionStatus/index.tsx b/ts/components/SectionStatus/index.tsx deleted file mode 100644 index 97f68ad87f0..00000000000 --- a/ts/components/SectionStatus/index.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import React, { ComponentProps, useCallback, useEffect, useRef } from "react"; -import { View } from "react-native"; -import { Alert } from "@pagopa/io-app-design-system"; -import { LevelEnum } from "../../../definitions/content/SectionStatus"; -import I18n from "../../i18n"; -import { - isSectionVisibleSelector, - levelForSectionSelector, - messageForSectionSelector, - SectionStatusKey, - webUrlForSectionSelector -} from "../../store/reducers/backendStatus/sectionStatus"; -import { getFullLocale } from "../../utils/locale"; -import { openWebUrl } from "../../utils/url"; -import { useIOSelector } from "../../store/hooks"; -import { useIONavigation } from "../../navigation/params/AppParamsList"; - -type Props = { - sectionKey: SectionStatusKey; - onSectionRef?: (ref: React.RefObject) => void; -}; - -const statusVariantMap: Record< - LevelEnum, - ComponentProps["variant"] -> = { - [LevelEnum.normal]: "info", - [LevelEnum.critical]: "error", - [LevelEnum.warning]: "warning" -}; - -const SectionStatusComponent = ({ sectionKey, onSectionRef }: Props) => { - const viewRef = useRef(null); - const locale = getFullLocale(); - - const navigation = useIONavigation(); - const isSectionVisible = useIOSelector(state => - isSectionVisibleSelector(state, sectionKey) - ); - const webUrl = useIOSelector(state => - webUrlForSectionSelector(state, sectionKey, locale) - ); - const message = useIOSelector(state => - messageForSectionSelector(state, sectionKey, locale) - ); - const level = useIOSelector(state => - levelForSectionSelector(state, sectionKey) - ); - - const onPressCallback = useCallback(() => { - if (webUrl) { - openWebUrl(webUrl); - } - }, [webUrl]); - const invokeSessionRefCallback = useCallback(() => { - if (viewRef.current) { - onSectionRef?.(viewRef); - } - }, [onSectionRef, viewRef]); - - useEffect(() => { - if (!isSectionVisible) { - return; - } - invokeSessionRefCallback(); - navigation?.addListener("focus", invokeSessionRefCallback); - return () => navigation?.removeListener("focus", invokeSessionRefCallback); - }, [invokeSessionRefCallback, isSectionVisible, navigation, viewRef]); - - if (!isSectionVisible) { - return null; - } - - const action = webUrl ? I18n.t("global.sectionStatus.moreInfo") : undefined; - const content = `${message}`; - const variant = statusVariantMap[level ?? LevelEnum.normal]; - const testId = `SectionStatusComponent${webUrl ? "Pressable" : "Content"}`; - - return ( - - ); -}; - -export default SectionStatusComponent; diff --git a/ts/components/SectionStatus/modal/index.tsx b/ts/components/SectionStatus/modal/index.tsx deleted file mode 100644 index f47db63ab9a..00000000000 --- a/ts/components/SectionStatus/modal/index.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import React, { ComponentProps, useCallback, useEffect, useRef } from "react"; -import { AccessibilityInfo, View } from "react-native"; -import { Alert, IOColors } from "@pagopa/io-app-design-system"; -import { useSafeAreaInsets } from "react-native-safe-area-context"; -import { LevelEnum } from "../../../../definitions/content/SectionStatus"; -import I18n from "../../../i18n"; -import { - isSectionVisibleSelector, - levelForSectionSelector, - messageForSectionSelector, - SectionStatusKey, - webUrlForSectionSelector -} from "../../../store/reducers/backendStatus/sectionStatus"; -import { getFullLocale } from "../../../utils/locale"; -import { openWebUrl } from "../../../utils/url"; -import { useIOSelector } from "../../../store/hooks"; -import { useIONavigation } from "../../../navigation/params/AppParamsList"; -import { isMixpanelInitializedSelector } from "../../../features/mixpanel/store/selectors"; -import { isMixpanelEnabled as isMixpanelEnabledSelector } from "../../../store/reducers/persistedPreferences"; - -type Props = { - sectionKey: SectionStatusKey; - onSectionRef?: (ref: React.RefObject) => void; - trackingAction?: () => void; - sticky?: boolean; -}; - -const statusVariantMap: Record< - LevelEnum, - { - variant: ComponentProps["variant"]; - background: IOColors; - } -> = { - [LevelEnum.normal]: { - variant: "info", - background: "info-100" - }, - [LevelEnum.critical]: { variant: "error", background: "error-100" }, - [LevelEnum.warning]: { variant: "warning", background: "warning-100" } -}; - -/** - * This component is a clone of `SectionStatusComponent` but, in addition, - * when `sticky` prop is `true` it can be anchored to the top of the screen to avoid to push down the content. - * Another additional feature is that it adds top padding related to `SafeAreaInsets` top value - * to avoid content from being covered from device status bar. - */ -const ModalSectionStatusComponent = ({ - sectionKey, - sticky, - onSectionRef, - trackingAction -}: Props) => { - const { top } = useSafeAreaInsets(); - const viewRef = useRef(null); - const locale = getFullLocale(); - const isMixpanelInitialized = useIOSelector(isMixpanelInitializedSelector); - const isMixpanelEnabled = useIOSelector(isMixpanelEnabledSelector); - - const navigation = useIONavigation(); - const isSectionVisible = useIOSelector(state => - isSectionVisibleSelector(state, sectionKey) - ); - const webUrl = useIOSelector(state => - webUrlForSectionSelector(state, sectionKey, locale) - ); - const message = useIOSelector(state => - messageForSectionSelector(state, sectionKey, locale) - ); - const level = useIOSelector(state => - levelForSectionSelector(state, sectionKey) - ); - - const onPressCallback = useCallback(() => { - if (webUrl) { - openWebUrl(webUrl); - } - }, [webUrl]); - const invokeSessionRefCallback = useCallback(() => { - if (viewRef.current) { - onSectionRef?.(viewRef); - } - }, [onSectionRef, viewRef]); - - useEffect(() => { - if ( - isSectionVisible && - isMixpanelInitialized && - isMixpanelEnabled !== false - ) { - trackingAction?.(); - } - }, [ - isSectionVisible, - isMixpanelInitialized, - isMixpanelEnabled, - trackingAction - ]); - - useEffect(() => { - if (!isSectionVisible) { - return; - } - invokeSessionRefCallback(); - navigation?.addListener("focus", invokeSessionRefCallback); - return () => navigation?.removeListener("focus", invokeSessionRefCallback); - }, [invokeSessionRefCallback, isSectionVisible, navigation, viewRef]); - - useEffect(() => { - if (isSectionVisible && message) { - AccessibilityInfo.announceForAccessibilityWithOptions(message, { - queue: true - }); - } - }, [isSectionVisible, message]); - - if (!isSectionVisible) { - return null; - } - - const action = webUrl ? I18n.t("global.sectionStatus.moreInfo") : undefined; - const { variant, background } = statusVariantMap[level ?? LevelEnum.normal]; - const testId = `ModalSectionStatusComponent${ - webUrl ? "Pressable" : "Content" - }`; - - return ( - - - - ); -}; - -export default ModalSectionStatusComponent; diff --git a/ts/features/bonus/cdc/components/CdcServiceCTA.tsx b/ts/features/bonus/cdc/components/CdcServiceCTA.tsx index 195073b0ff1..6612d51e715 100644 --- a/ts/features/bonus/cdc/components/CdcServiceCTA.tsx +++ b/ts/features/bonus/cdc/components/CdcServiceCTA.tsx @@ -1,4 +1,5 @@ import { + Alert, ButtonOutline, ButtonSolid, VSpacer @@ -11,8 +12,6 @@ import { View } from "react-native"; import { StatoBeneficiarioEnum } from "../../../../../definitions/cdc/StatoBeneficiario"; import { BonusVisibilityEnum } from "../../../../../definitions/content/BonusVisibility"; import { fold } from "../../../../common/model/RemoteValue"; -import SectionStatusComponent from "../../../../components/SectionStatus"; -import StatusContent from "../../../../components/SectionStatus/StatusContent"; import ActivityIndicator from "../../../../components/ui/ActivityIndicator"; import I18n from "../../../../i18n"; import { useIONavigation } from "../../../../navigation/params/AppParamsList"; @@ -84,17 +83,13 @@ const ErrorButton = (props: ErrorButtonProp) => { return ( - - {I18n.t("bonus.cdc.serviceCta.error.status")} - + variant="warning" + content={I18n.t("bonus.cdc.serviceCta.error.status")} + accessibilityHint={I18n.t("global.accessibility.alert")} + testID={"errorAlert"} + /> { const CdcServiceCTA = () => ( - diff --git a/ts/features/bonus/cdc/components/__test__/CdcServiceCTA.test.ts b/ts/features/bonus/cdc/components/__test__/CdcServiceCTA.test.ts deleted file mode 100644 index 2bd221e23f3..00000000000 --- a/ts/features/bonus/cdc/components/__test__/CdcServiceCTA.test.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { createStore, Store } from "redux"; -import { Anno } from "../../../../../../definitions/cdc/Anno"; -import { StatoBeneficiarioEnum } from "../../../../../../definitions/cdc/StatoBeneficiario"; -import { BonusAvailable } from "../../../../../../definitions/content/BonusAvailable"; -import ROUTES from "../../../../../navigation/routes"; -import { applicationChangeState } from "../../../../../store/actions/application"; -import { appReducer } from "../../../../../store/reducers"; -import { GlobalState } from "../../../../../store/reducers/types"; -import { getTimeoutError } from "../../../../../utils/errors"; -import { renderScreenWithNavigationStoreContext } from "../../../../../utils/testWrapper"; -import { loadAvailableBonuses } from "../../../common/store/actions/availableBonusesTypes"; -import { cdcRequestBonusList } from "../../store/actions/cdcBonusRequest"; -import { CdcBonusRequest } from "../../types/CdcBonusRequest"; -import CdcServiceCTA from "../CdcServiceCTA"; - -jest.useFakeTimers(); - -const mockActivableBonus: CdcBonusRequest = { - year: "2018" as Anno, - status: StatoBeneficiarioEnum.ATTIVABILE -}; -const mockPendingBonus: CdcBonusRequest = { - year: "2018" as Anno, - status: StatoBeneficiarioEnum.VALUTAZIONE -}; -const mockActiveBonus: CdcBonusRequest = { - year: "2018" as Anno, - status: StatoBeneficiarioEnum.ATTIVO -}; -const mockNotRequestableBonus: CdcBonusRequest = { - year: "2018" as Anno, - status: StatoBeneficiarioEnum.INATTIVO -}; -const mockExpiredBonus: CdcBonusRequest = { - year: "2018" as Anno, - status: StatoBeneficiarioEnum.INATTIVABILE -}; - -const bonusMockContent = { - name: "MockCartadellacultura", - description: "MockDescription", - subtitle: "MockSubtitle", - title: "MockTitle", - content: "MockContent", - tos_url: "https://io.italia.it/mockTosUrl" -}; - -const mockBonus: BonusAvailable = { - id_type: 4, - it: bonusMockContent, - en: bonusMockContent, - valid_from: new Date(), - valid_to: new Date(), - is_active: false -}; - -describe("CdcServiceCTA", () => { - const globalState = appReducer(undefined, applicationChangeState("active")); - describe("When the allAvailableBonusTypes is potSome and the cdc bonus is different from undefined", () => { - it("Should show the activateCardButton if the cdcBonusRequestList is ready and there is at least a card with the status Activable", () => { - const store: Store = createStore( - appReducer, - globalState as any - ); - store.dispatch(loadAvailableBonuses.success([mockBonus])); - const component = renderComponent(store); - store.dispatch(cdcRequestBonusList.success([mockActivableBonus])); - const activateCardButton = component.getByTestId("activateCardButton"); - expect(activateCardButton).toBeDefined(); - }); - it("Should show the pendingCardButton if the cdcBonusRequestList is ready and there is at least a card with the status Pending and there isn't card with the status Activable", () => { - const store: Store = createStore( - appReducer, - globalState as any - ); - store.dispatch(loadAvailableBonuses.success([mockBonus])); - const component = renderComponent(store); - store.dispatch(cdcRequestBonusList.success([mockPendingBonus])); - const pendingCardButton = component.getByTestId("pendingCardButton"); - expect(pendingCardButton).toBeDefined(); - }); - it("Should return null if the cdcBonusRequestList is ready and there isn't card with the status Activable or Pending", () => { - const store: Store = createStore( - appReducer, - globalState as any - ); - store.dispatch(loadAvailableBonuses.success([mockBonus])); - const component = renderComponent(store); - store.dispatch( - cdcRequestBonusList.success([ - mockActiveBonus, - mockNotRequestableBonus, - mockExpiredBonus - ]) - ); - const activateCardButton = component.queryByTestId("activateCardButton"); - const pendingCardButton = component.queryByTestId("pendingCardButton"); - - expect(activateCardButton).toBeNull(); - expect(pendingCardButton).toBeNull(); - }); - it("Should show the activityIndicator if the cdcBonusRequestList is in loading state", () => { - const store: Store = createStore( - appReducer, - globalState as any - ); - store.dispatch(loadAvailableBonuses.success([mockBonus])); - const component = renderComponent(store); - const activityIndicator = component.getByTestId("rn-activity-indicator"); - - expect(activityIndicator).toBeDefined(); - }); - it("Should show the errorStatusComponent and the retryButton button if the cdcBonusRequestList is in failure state", () => { - const store: Store = createStore( - appReducer, - globalState as any - ); - store.dispatch(loadAvailableBonuses.success([mockBonus])); - const component = renderComponent(store); - store.dispatch(cdcRequestBonusList.failure(getTimeoutError())); - const errorStatusComponent = component.getByTestId( - "SectionStatusContent" - ); - const retryButton = component.getByTestId("retryButton"); - - expect(errorStatusComponent).toBeDefined(); - expect(retryButton).toBeDefined(); - }); - }); - it("Should show the activityIndicator if the allAvailableBonusTypes is potNoneLoading or potSomeLoading", () => { - const store: Store = createStore( - appReducer, - globalState as any - ); - const component = renderComponent(store); - const activityIndicator = component.getByTestId("rn-activity-indicator"); - - expect(activityIndicator).toBeDefined(); - store.dispatch(loadAvailableBonuses.success([mockBonus])); - store.dispatch(loadAvailableBonuses.request()); - expect(activityIndicator).toBeDefined(); - }); - it("Should show the ErrorButton if the allAvailableBonusTypes is potNoneError or potSomeError", () => { - const store: Store = createStore( - appReducer, - globalState as any - ); - const component = renderComponent(store); - store.dispatch(loadAvailableBonuses.failure(new Error())); - const retryButton = component.getByTestId("retryButton"); - expect(retryButton).toBeDefined(); - store.dispatch(loadAvailableBonuses.success([mockBonus])); - store.dispatch(loadAvailableBonuses.failure(new Error())); - expect(retryButton).toBeDefined(); - }); - it("Should return null if the allAvailableBonusTypes is potSome but the cdcBonus is not available", () => { - const store: Store = createStore( - appReducer, - globalState as any - ); - const component = renderComponent(store); - store.dispatch( - loadAvailableBonuses.success([{ ...mockBonus, id_type: 1 }]) - ); - const activateCardButton = component.queryByTestId("activateCardButton"); - const pendingCardButton = component.queryByTestId("pendingCardButton"); - - expect(activateCardButton).toBeNull(); - expect(pendingCardButton).toBeNull(); - }); -}); - -function renderComponent(store: Store) { - return renderScreenWithNavigationStoreContext( - CdcServiceCTA, - ROUTES.MAIN, - {}, - store - ); -} diff --git a/ts/features/design-system/core/DSLegacyAlert.tsx b/ts/features/design-system/core/DSLegacyAlert.tsx deleted file mode 100644 index 03a49f1daa8..00000000000 --- a/ts/features/design-system/core/DSLegacyAlert.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { VSpacer } from "@pagopa/io-app-design-system"; -import * as React from "react"; -import StatusContent, { - statusColorMap, - getStatusTextColor, - statusIconMap -} from "../../../components/SectionStatus/StatusContent"; -import { DSFullWidthComponent } from "../components/DSFullWidthComponent"; - -/* Types */ -import { LevelEnum } from "../../../../definitions/content/SectionStatus"; -import { DesignSystemScreen } from "../components/DesignSystemScreen"; - -export const DSLegacyAlert = () => ( - - - - { - "L’invio dei Certificati Verdi è in corso e potrebbe richiedere diversi giorni." - } - - - - - - {"La sezione Messaggi è in manutenzione, tornerà operativa a breve"} - - - - - - { - "I nostri sistemi potrebbero rispondere con lentezza, ci scusiamo per il disagio." - } - - - -); diff --git a/ts/features/design-system/navigation/navigator.tsx b/ts/features/design-system/navigation/navigator.tsx index 094c1ea9fc3..e5780f876d5 100644 --- a/ts/features/design-system/navigation/navigator.tsx +++ b/ts/features/design-system/navigation/navigator.tsx @@ -52,7 +52,6 @@ import { DSIcons } from "../core/DSIcons"; import { DSIridescentTrustmark } from "../core/DSIridescentTrustmark"; import { DSLayout } from "../core/DSLayout"; import { DSLegacyAdvice } from "../core/DSLegacyAdvice"; -import { DSLegacyAlert } from "../core/DSLegacyAlert"; import { DSLegacyBadges } from "../core/DSLegacyBadges"; import { DSLegacyButtons } from "../core/DSLegacyButtons"; import { DSLegacyListItems } from "../core/DSLegacyListItems"; @@ -618,14 +617,6 @@ export const DesignSystemNavigator = () => { }} /> - - Date: Fri, 29 Nov 2024 15:25:01 +0100 Subject: [PATCH 2/7] Remove `DSLegacyBadges` --- ts/components/core/IOBadge.tsx | 151 ------------------ ts/components/screens/ListItemComponent.tsx | 39 ++--- ts/components/ui/CustomBadge.tsx | 95 ----------- ts/components/ui/TabIconComponent.tsx | 11 +- .../ui/__test__/CustomBadge.test.tsx | 61 ------- .../__snapshots__/CustomBadge.test.tsx.snap | 95 ----------- .../merchants/CgnDiscountValueBox.tsx | 49 ------ .../common/components/AvailableBonusItem.tsx | 21 +-- .../design-system/core/DSLegacyBadges.tsx | 120 -------------- .../design-system/navigation/navigator.tsx | 9 -- .../design-system/navigation/params.ts | 1 - .../design-system/navigation/routes.ts | 4 - .../TimelineRefundDetailsComponent.tsx | 23 +-- 13 files changed, 41 insertions(+), 638 deletions(-) delete mode 100644 ts/components/core/IOBadge.tsx delete mode 100644 ts/components/ui/CustomBadge.tsx delete mode 100644 ts/components/ui/__test__/CustomBadge.test.tsx delete mode 100644 ts/components/ui/__test__/__snapshots__/CustomBadge.test.tsx.snap delete mode 100644 ts/features/bonus/cgn/components/merchants/CgnDiscountValueBox.tsx delete mode 100644 ts/features/design-system/core/DSLegacyBadges.tsx diff --git a/ts/components/core/IOBadge.tsx b/ts/components/core/IOBadge.tsx deleted file mode 100644 index 6d4281b5727..00000000000 --- a/ts/components/core/IOBadge.tsx +++ /dev/null @@ -1,151 +0,0 @@ -import React from "react"; -import { View, Text, StyleSheet, PixelRatio, Platform } from "react-native"; -import { IOColors } from "@pagopa/io-app-design-system"; -import { makeFontStyleObject } from "./fonts"; - -export type IOBadgeOutlineColors = Extract< - IOColors, - "blue" | "white" | "grey" | "red" | "orange" ->; -export type IOBadgeSolidColors = Extract< - IOColors, - "blue" | "white" | "grey" | "aqua" | "red" ->; - -type IOBadgeCommonProps = { - text: string; - small?: boolean; - labelColor?: Extract; - testID?: string; - labelTestID?: string; -}; - -type IOBadgeConditionalProps = - | { - variant: "solid"; - color: IOBadgeSolidColors; - } - | { - variant: "outline"; - color: IOBadgeOutlineColors; - }; - -export type IOBadge = IOBadgeCommonProps & IOBadgeConditionalProps; - -type SolidVariantProps = { - background: IOColors; - text: IOColors; -}; - -const mapOutlineColor: Record, IOColors> = { - blue: "blue", - white: "white", - grey: "bluegreyDark", - red: "red", - orange: "orange" -}; - -const mapSolidColor: Record< - NonNullable, - SolidVariantProps -> = { - blue: { - background: "blue", - text: "white" - }, - white: { - background: "white", - text: "bluegrey" - }, - aqua: { - background: "aqua", - text: "bluegreyDark" - }, - grey: { - background: "grey", - text: "white" - }, - red: { - background: "red", - text: "white" - } -}; - -const defaultVariant: IOBadge["variant"] = "solid"; -const defaultColor: IOBadge["color"] = "blue"; - -const styles = StyleSheet.create({ - badge: { - alignItems: "center", - justifyContent: "center", - ...Platform.select({ - android: { - textAlignVertical: "center" - } - }), - // Visual parameters based on the FontScale - // ~20 = Small size height - // ~24 = Default size height - paddingVertical: PixelRatio.getFontScale() * 1.25, - paddingHorizontal: PixelRatio.getFontScale() * 10, - borderRadius: PixelRatio.getFontScale() * 25 - }, - label: { - alignSelf: "center", - ...makeFontStyleObject("Semibold") - }, - labelSizeDefault: { - fontSize: 14, - lineHeight: 20 - }, - labelSizeSmall: { - fontSize: 11, - lineHeight: 16 - } -}); - -/** - * Official badge component -@deprecated("Use the new Badge component instead") - */ -export const IOBadge = ({ - text, - variant = defaultVariant, - color = defaultColor, - small, - testID, - labelTestID -}: IOBadge) => ( - - {/* TODO: Enable bolder text using `isBoldTextEnabled` RN API - (not yet released at the time I am writing this comment). */} - - {text} - - -); diff --git a/ts/components/screens/ListItemComponent.tsx b/ts/components/screens/ListItemComponent.tsx index d9625e022b6..408ed1bd8ab 100644 --- a/ts/components/screens/ListItemComponent.tsx +++ b/ts/components/screens/ListItemComponent.tsx @@ -1,28 +1,28 @@ -import * as React from "react"; -import { - Text, - View, - AccessibilityRole, - StyleProp, - StyleSheet, - ViewStyle, - AccessibilityState, - Pressable -} from "react-native"; import { + Badge, + Body, + Divider, + HSpacer, Icon, IOColors, IOIcons, IOIconSizeScale, - HSpacer, - Divider, - VSpacer, NativeSwitch, - Body + VSpacer } from "@pagopa/io-app-design-system"; +import * as React from "react"; +import { + AccessibilityRole, + AccessibilityState, + Pressable, + StyleProp, + StyleSheet, + Text, + View, + ViewStyle +} from "react-native"; import customVariables from "../../theme/variables"; import { makeFontStyleObject } from "../core/fonts"; -import { IOBadge } from "../core/IOBadge"; import { IOStyles } from "../core/variables/IOStyles"; import { BadgeComponent } from "./BadgeComponent"; @@ -141,12 +141,7 @@ export default class ListItemComponent extends React.Component { {/* Use marginTop to align the badge to the text. TODO: Replace it with a more robust approach. */} - + )} diff --git a/ts/components/ui/CustomBadge.tsx b/ts/components/ui/CustomBadge.tsx deleted file mode 100644 index f2aab20fcba..00000000000 --- a/ts/components/ui/CustomBadge.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import React, { memo } from "react"; -import { Text, StyleSheet, View } from "react-native"; -import { IOColors } from "@pagopa/io-app-design-system"; -import variables from "../../theme/variables"; -import { isTestEnv } from "../../utils/environment"; -import { makeFontStyleObject } from "../core/fonts"; - -type Props = { - badgeValue?: number; -}; - -const BADGE_SIZE = 20; - -const styles = StyleSheet.create({ - textStyle: { - color: IOColors.white, - fontSize: 10, - textAlign: "center", - ...makeFontStyleObject("Bold") - }, - badgeStyle: { - width: BADGE_SIZE, - height: BADGE_SIZE, - borderRadius: BADGE_SIZE / 2, - alignSelf: "flex-start", - justifyContent: "center", - alignContent: "center", - backgroundColor: variables.brandPrimary, - borderColor: IOColors.white, - borderWidth: 2, - position: "absolute", - left: 12, - bottom: 10, - paddingLeft: 0, - paddingRight: 0 - } -}); - -const MAX_BADGE_VALUE = 99; -const multiplierFallback = 1.4; -// map the digits to display within the badge with the relative width multiplier factor -// (the badge displays value below MAX_BADGE_VALUE) -const multiplierMap: Record = { - 1: 1, - 2: 1.2 -}; -// get the width multiplier relative to the count of digits to display -const getWidthMultiplier = (text: string): number => { - const digits = text.length; - return multiplierMap[digits] ?? multiplierFallback; -}; - -/** - * A simple round badge to display a positive number - * Display all numbers less or equal of MAX_BADGE_VALUE - * otherwise ${MAX_BADGE_VALUE}"+" will be displayed - * - * if badgeValue is nullish or negative, null element will be returned - */ -const CustomBadge = (props: Props) => { - const badgeValue = props.badgeValue ?? 0; - if (badgeValue <= 0) { - return null; - } - const badge = `${Math.min(badgeValue, MAX_BADGE_VALUE)}${ - badgeValue > MAX_BADGE_VALUE ? "+" : "" - }`; - return ( - - - {badge} - - - ); -}; - -export default memo( - CustomBadge, - (prev, next) => prev.badgeValue === next.badgeValue -); - -// to ensure right code encapsulation we export functions/variables just for tests purposes -export const customBadgeTestable = isTestEnv - ? { getWidthMultiplier, styles } - : undefined; diff --git a/ts/components/ui/TabIconComponent.tsx b/ts/components/ui/TabIconComponent.tsx index 7cfaa88abf4..7194c7586ff 100644 --- a/ts/components/ui/TabIconComponent.tsx +++ b/ts/components/ui/TabIconComponent.tsx @@ -1,14 +1,12 @@ import { ColorValue, View } from "react-native"; import React from "react"; import { AnimatedIcon, IONavIcons } from "@pagopa/io-app-design-system"; -import CustomBadge from "./CustomBadge"; type TabIconComponent = { focused: boolean; iconName: IONavIcons; iconNameFocused: IONavIcons; color?: ColorValue; - badgeValue?: number; }; /** @@ -16,13 +14,7 @@ type TabIconComponent = { */ export const TabIconComponent = React.memo( - ({ - focused, - iconName, - iconNameFocused, - color, - badgeValue - }: TabIconComponent) => ( + ({ focused, iconName, iconNameFocused, color }: TabIconComponent) => ( // accessibilityLabel={""} in order to read the font icon, without modify the library element - {badgeValue && } ) ); diff --git a/ts/components/ui/__test__/CustomBadge.test.tsx b/ts/components/ui/__test__/CustomBadge.test.tsx deleted file mode 100644 index c49c27b1a88..00000000000 --- a/ts/components/ui/__test__/CustomBadge.test.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { render } from "@testing-library/react-native"; -import React from "react"; -import CustomBadge, { customBadgeTestable } from "../CustomBadge"; - -const testID = "badgeTestID"; -describe("CustomBadge component", () => { - jest.useFakeTimers(); - it(`match snapshot for 0`, () => { - const component = render(); - expect(component).toMatchSnapshot(); - }); - - it(`match snapshot for 10`, () => { - const component = render(); - expect(component).toMatchSnapshot(); - }); - - it(`match snapshot for 100`, () => { - const component = render(); - expect(component).toMatchSnapshot(); - }); - - it(`should be defined for 1 as badge value`, () => { - const component = render().queryByTestId( - testID - ); - expect(component).toBeDefined(); - }); - - it(`should be not defined for 0 as badge value`, () => { - const component = render().queryByTestId( - testID - ); - expect(component).toBeNull(); - }); - - it(`should be not defined for undefined as badge value`, () => { - const component = render( - - ).queryByTestId(testID); - expect(component).toBeNull(); - }); - - it(`should have a specific with for 1 digits`, () => { - const digits = [1, 10, 50, 80, 99, 100, 200, 1000]; - digits.forEach(digit => { - const component = render( - - ).queryByTestId(testID); - - expect(component).toBeDefined(); - if (component) { - expect(component).toHaveStyle({ - width: - customBadgeTestable!.styles.badgeStyle.width * - customBadgeTestable!.getWidthMultiplier(digit.toString()) - }); - } - }); - }); -}); diff --git a/ts/components/ui/__test__/__snapshots__/CustomBadge.test.tsx.snap b/ts/components/ui/__test__/__snapshots__/CustomBadge.test.tsx.snap deleted file mode 100644 index c2c73c6a392..00000000000 --- a/ts/components/ui/__test__/__snapshots__/CustomBadge.test.tsx.snap +++ /dev/null @@ -1,95 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CustomBadge component match snapshot for 0 1`] = `null`; - -exports[`CustomBadge component match snapshot for 10 1`] = ` - - - 10 - - -`; - -exports[`CustomBadge component match snapshot for 100 1`] = ` - - - 99+ - - -`; diff --git a/ts/features/bonus/cgn/components/merchants/CgnDiscountValueBox.tsx b/ts/features/bonus/cgn/components/merchants/CgnDiscountValueBox.tsx deleted file mode 100644 index 0c25dcd6d62..00000000000 --- a/ts/features/bonus/cgn/components/merchants/CgnDiscountValueBox.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { H6, IOColors } from "@pagopa/io-app-design-system"; -import * as React from "react"; -import { StyleSheet, View } from "react-native"; -import { normalizedDiscountPercentage } from "./utils"; - -type ValueBoxProps = { - value: number; - small?: true; -}; - -const BADGE_SIZE_SMALL = 40; -const BADGE_SIZE_DEFAULT = 48; - -const styles = StyleSheet.create({ - percentage: { textAlign: "center", lineHeight: 30 }, - smallValueBox: { - alignSelf: "center", - borderRadius: BADGE_SIZE_SMALL / 4, - paddingVertical: 4, - width: BADGE_SIZE_SMALL, - textAlign: "center", - backgroundColor: IOColors.antiqueFuchsia - }, - discountValueBox: { - alignSelf: "center", - borderRadius: BADGE_SIZE_DEFAULT / 4, - paddingVertical: 8, - width: BADGE_SIZE_DEFAULT, - backgroundColor: IOColors.antiqueFuchsia - } -}); - -const PERCENTAGE_SYMBOL = "%"; -const MINUS_SYMBOL = "-"; - -const CgnDiscountValueBox = ({ value, small }: ValueBoxProps) => { - const percentage =
{PERCENTAGE_SYMBOL}
; - return ( - -
- {MINUS_SYMBOL} - {normalizedDiscountPercentage(value)} - {percentage} -
-
- ); -}; - -export default CgnDiscountValueBox; diff --git a/ts/features/bonus/common/components/AvailableBonusItem.tsx b/ts/features/bonus/common/components/AvailableBonusItem.tsx index 31a7bab780e..aebfa021166 100644 --- a/ts/features/bonus/common/components/AvailableBonusItem.tsx +++ b/ts/features/bonus/common/components/AvailableBonusItem.tsx @@ -1,14 +1,12 @@ -import { Body, H6, HSpacer } from "@pagopa/io-app-design-system"; +import { Badge, Body, H6, HSpacer } from "@pagopa/io-app-design-system"; import * as React from "react"; import { Image, Pressable, StyleSheet, View } from "react-native"; import { BonusAvailable } from "../../../../../definitions/content/BonusAvailable"; import { BonusAvailableContent } from "../../../../../definitions/content/BonusAvailableContent"; +import { IOStyles } from "../../../../components/core/variables/IOStyles"; import I18n from "../../../../i18n"; import { getRemoteLocale } from "../../../messages/utils/messages"; -import { IOBadge } from "../../../../components/core/IOBadge"; -import { IOStyles } from "../../../../components/core/variables/IOStyles"; - export type AvailableBonusItemState = "incoming" | "active" | "completed"; type Props = { @@ -42,16 +40,19 @@ const styles = StyleSheet.create({ } }); -const BonusBadge = (props: { caption: string }) => ( - -); - const renderBadge = (state: AvailableBonusItemState) => { switch (state) { case "incoming": - return ; + return ( + + ); case "completed": - return ; + return ( + + ); case "active": return null; } diff --git a/ts/features/design-system/core/DSLegacyBadges.tsx b/ts/features/design-system/core/DSLegacyBadges.tsx deleted file mode 100644 index 1976abf2703..00000000000 --- a/ts/features/design-system/core/DSLegacyBadges.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import { - H4, - H6, - HSpacer, - IOColors, - IOStyles, - VSpacer -} from "@pagopa/io-app-design-system"; -import * as React from "react"; -import { StyleSheet, View } from "react-native"; - -import { DesignSystemScreen } from "../components/DesignSystemScreen"; -import CgnDiscountValueBox from "../../bonus/cgn/components/merchants/CgnDiscountValueBox"; -import CustomBadge from "../../../components/ui/CustomBadge"; -import { IOBadge } from "../../../components/core/IOBadge"; - -const styles = StyleSheet.create({ - fakeNavItem: { - aspectRatio: 1, - width: 25, - backgroundColor: IOColors.greyLight - } -}); - -export const DSLegacyBadges = () => ( - -
IOBadge
- {renderIOBadge()} - - - -
DiscountValueBox (CGN)
- - - - - - - - - -

Notifications

- -
CustomBadge
- - - - - - - - - - -
-); - -const renderIOBadge = () => ( - <> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -); diff --git a/ts/features/design-system/navigation/navigator.tsx b/ts/features/design-system/navigation/navigator.tsx index e5780f876d5..375b1d10d99 100644 --- a/ts/features/design-system/navigation/navigator.tsx +++ b/ts/features/design-system/navigation/navigator.tsx @@ -52,7 +52,6 @@ import { DSIcons } from "../core/DSIcons"; import { DSIridescentTrustmark } from "../core/DSIridescentTrustmark"; import { DSLayout } from "../core/DSLayout"; import { DSLegacyAdvice } from "../core/DSLegacyAdvice"; -import { DSLegacyBadges } from "../core/DSLegacyBadges"; import { DSLegacyButtons } from "../core/DSLegacyButtons"; import { DSLegacyListItems } from "../core/DSLegacyListItems"; import { DSLegacyTextFields } from "../core/DSLegacyTextFields"; @@ -609,14 +608,6 @@ export const DesignSystemNavigator = () => { }} /> - - { {I18n.t("idpay.initiative.operationDetails.refund.resultLabel")} {refund.operationType === OperationTypeEnum.REJECTED_REFUND ? ( - ) : ( - Date: Fri, 29 Nov 2024 15:39:16 +0100 Subject: [PATCH 3/7] Restore `SectionStatusComponent` --- .../__tests__/SectionStatusContent.test.tsx | 22 ++ .../SectionStatusContent.test.tsx.snap | 159 ++++++++++++ .../SectionStatus/__tests__/index.test.tsx | 230 ++++++++++++++++++ ts/components/SectionStatus/index.tsx | 92 +++++++ ts/components/SectionStatus/modal/index.tsx | 157 ++++++++++++ .../bonus/cdc/components/CdcServiceCTA.tsx | 2 + .../components/__test__/CdcServiceCTA.test.ts | 180 ++++++++++++++ .../design-system/core/DSLegacyAlert.tsx | 56 +++++ 8 files changed, 898 insertions(+) create mode 100644 ts/components/SectionStatus/__tests__/SectionStatusContent.test.tsx create mode 100644 ts/components/SectionStatus/__tests__/__snapshots__/SectionStatusContent.test.tsx.snap create mode 100644 ts/components/SectionStatus/__tests__/index.test.tsx create mode 100644 ts/components/SectionStatus/index.tsx create mode 100644 ts/components/SectionStatus/modal/index.tsx create mode 100644 ts/features/bonus/cdc/components/__test__/CdcServiceCTA.test.ts create mode 100644 ts/features/design-system/core/DSLegacyAlert.tsx diff --git a/ts/components/SectionStatus/__tests__/SectionStatusContent.test.tsx b/ts/components/SectionStatus/__tests__/SectionStatusContent.test.tsx new file mode 100644 index 00000000000..64d88674bd4 --- /dev/null +++ b/ts/components/SectionStatus/__tests__/SectionStatusContent.test.tsx @@ -0,0 +1,22 @@ +import React from "react"; +import { View } from "react-native"; +import { render } from "@testing-library/react-native"; + +import StatusContent from "../StatusContent"; + +describe("StatusContent", () => { + it("should match the snapshot", () => { + const viewRef = React.createRef(); + const component = render( + + ); + expect(component.toJSON()).toMatchSnapshot(); + }); +}); diff --git a/ts/components/SectionStatus/__tests__/__snapshots__/SectionStatusContent.test.tsx.snap b/ts/components/SectionStatus/__tests__/__snapshots__/SectionStatusContent.test.tsx.snap new file mode 100644 index 00000000000..133af5f36d6 --- /dev/null +++ b/ts/components/SectionStatus/__tests__/__snapshots__/SectionStatusContent.test.tsx.snap @@ -0,0 +1,159 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`StatusContent should match the snapshot 1`] = ` + + + + + + + + + + + + + +`; diff --git a/ts/components/SectionStatus/__tests__/index.test.tsx b/ts/components/SectionStatus/__tests__/index.test.tsx new file mode 100644 index 00000000000..bdf32f573ab --- /dev/null +++ b/ts/components/SectionStatus/__tests__/index.test.tsx @@ -0,0 +1,230 @@ +import { fireEvent } from "@testing-library/react-native"; +import * as O from "fp-ts/lib/Option"; +import * as React from "react"; +import configureMockStore from "redux-mock-store"; +import { IOColors } from "@pagopa/io-app-design-system"; +import * as pot from "@pagopa/ts-commons/lib/pot"; +import { ToolEnum } from "../../../../definitions/content/AssistanceToolConfig"; +import { Config } from "../../../../definitions/content/Config"; +import { + LevelEnum, + SectionStatus +} from "../../../../definitions/content/SectionStatus"; +import I18n, { setLocale } from "../../../i18n"; +import { SectionStatusKey } from "../../../store/reducers/backendStatus/sectionStatus"; +import { renderScreenWithNavigationStoreContext } from "../../../utils/testWrapper"; +import { openWebUrl } from "../../../utils/url"; +import SectionStatusComponent from "../index"; +import { SubscriptionStateEnum } from "../../../../definitions/trial_system/SubscriptionState"; +import { itwTrialId } from "../../../config"; +import { TrialSystemState } from "../../../features/trialSystem/store/reducers"; +import { PersistedFeaturesState } from "../../../features/common/store/reducers"; +import { ItwLifecycleState } from "../../../features/itwallet/lifecycle/store/reducers"; +import { ItWalletState } from "../../../features/itwallet/common/store/reducers"; +import { GlobalState } from "../../../store/reducers/types"; + +jest.mock("../../../utils/url"); + +const sectionStatus: SectionStatus = { + is_visible: true, + level: LevelEnum.normal, + web_url: { + "it-IT": "https://io.italia.it/it/cashback/faq/#n44", + "en-EN": "https://io.italia.it/en/cashback/faq/#n44" + }, + message: { + "it-IT": "message IT", + "en-EN": "message EN" + } +}; + +const mockSectionStatusState = ( + sectionKey: SectionStatusKey, + status: SectionStatus +) => + ({ + sectionStatus: O.some({ [sectionKey]: status }), + remoteConfig: O.some({ + assistanceTool: { tool: ToolEnum.none }, + cgn: { enabled: true }, + newPaymentSection: { + enabled: false, + min_app_version: { + android: "0.0.0.0", + ios: "0.0.0.0" + } + }, + fims: { enabled: true }, + itw: { + enabled: true, + min_app_version: { + android: "0.0.0.0", + ios: "0.0.0.0" + } + } + } as Config), + features: { + itWallet: { + lifecycle: ItwLifecycleState.ITW_LIFECYCLE_INSTALLED + } as ItWalletState + } as PersistedFeaturesState, + trialSystem: { + [itwTrialId]: pot.some(SubscriptionStateEnum.UNSUBSCRIBED) + } as TrialSystemState + } as unknown as GlobalState); + +const mockStore = configureMockStore(); + +describe("SectionStatusComponent", () => { + describe("given different locales", () => { + it("should display the IT message in the label component", () => { + setLocale("it"); + const component = getComponent("messages"); + expect( + component.queryByText(RegExp(sectionStatus.message["it-IT"])) + ).not.toBeNull(); + }); + + it("should display the EN message in the label component", () => { + setLocale("en"); + const component = getComponent("messages"); + expect( + component.queryByText(new RegExp(sectionStatus.message["en-EN"])) + ).not.toBeNull(); + }); + }); + + [ + [LevelEnum.normal, IOColors["info-100"]], + [LevelEnum.warning, IOColors["warning-100"]], + [LevelEnum.critical, IOColors["error-100"]] + ].forEach(([level, color]) => { + describe(`given the level ${level}`, () => { + const store = mockStore( + mockSectionStatusState("messages", { + ...sectionStatus, + web_url: undefined, + level: level as LevelEnum + }) + ); + + it(`should apply background color ${color} to the status content`, () => { + const component = getComponent("messages", store); + const view = component.getByTestId("SectionStatusComponentContent"); + expect(view).toHaveStyle({ backgroundColor: color }); + }); + }); + }); + + describe("when web_url is defined", () => { + it("should display `more info` text", () => { + const component = getComponent("messages"); + expect( + component.queryByText(I18n.t("global.sectionStatus.moreInfo")) + ).not.toBeNull(); + }); + + it("should set the correct a11y properties on the touchable wrapper", () => { + setLocale("it"); + const component = getComponent("messages"); + const view = component.getByTestId("SectionStatusComponentPressable"); + expect(view.props.accessible).toBe(true); + expect(view.props.accessibilityRole).toBe("button"); + }); + + it("should render the touchable wrapper which opens the correct url", () => { + setLocale("it"); + const component = getComponent("messages"); + const touchable = component.getByTestId( + "SectionStatusComponentPressable" + )!; + fireEvent.press(touchable); + expect(openWebUrl).toHaveBeenCalledTimes(1); + expect(openWebUrl).toHaveBeenCalledWith(sectionStatus.web_url!["it-IT"]); + }); + }); + + describe("when web_url is not defined", () => { + const store = mockStore( + mockSectionStatusState("messages", { + ...sectionStatus, + web_url: undefined + }) + ); + + it("should set the correct a11y properties to the status content", () => { + setLocale("it"); + const component = getComponent("messages", store); + const view = component.getByTestId("SectionStatusComponentContent"); + expect(view.props.accessible).toBe(false); + expect(view.props.accessibilityRole).toBe("alert"); + }); + + it("should display the accessibility alert text", () => { + const component = getComponent("messages"); + expect( + component.queryByText(new RegExp(I18n.t("global.accessibility.alert"))) + ).toBeNull(); + }); + + it("should render the touchable wrapper which opens the correct url", () => { + setLocale("it"); + const component = getComponent("messages", store); + const touchable = component.queryByTestId( + "SectionStatusComponentPressable" + )!; + expect(touchable).toBeNull(); + }); + }); +}); + +describe("Section Status Component should return null", () => { + it("when sectionStatus is none", () => { + const component = getComponent( + "messages", + mockStore({ + remoteConfig: O.none, + sectionStatus: O.none, + trialSystem: { + [itwTrialId]: pot.some(SubscriptionStateEnum.UNSUBSCRIBED) + } as TrialSystemState, + features: { + itWallet: { + lifecycle: ItwLifecycleState.ITW_LIFECYCLE_INSTALLED + } as ItWalletState + } as PersistedFeaturesState + }) + ); + expect(component.queryByTestId("SectionStatusComponentLabel")).toBeNull(); + expect( + component.queryByTestId("SectionStatusComponentTouchable") + ).toBeNull(); + }); + + it("when sectionStatus has `is_visible` false", () => { + const component = getComponent( + "messages", + mockStore( + mockSectionStatusState("messages", { + ...sectionStatus, + is_visible: false + }) + ) + ); + expect(component.queryByTestId("SectionStatusComponentLabel")).toBeNull(); + expect( + component.queryByTestId("SectionStatusComponentTouchable") + ).toBeNull(); + }); +}); + +const getComponent = ( + sectionKey: SectionStatusKey, + store?: ReturnType +) => + renderScreenWithNavigationStoreContext( + () => , + "DUMMY", + {}, + store || mockStore(mockSectionStatusState("messages", sectionStatus)) + ); diff --git a/ts/components/SectionStatus/index.tsx b/ts/components/SectionStatus/index.tsx new file mode 100644 index 00000000000..97f68ad87f0 --- /dev/null +++ b/ts/components/SectionStatus/index.tsx @@ -0,0 +1,92 @@ +import React, { ComponentProps, useCallback, useEffect, useRef } from "react"; +import { View } from "react-native"; +import { Alert } from "@pagopa/io-app-design-system"; +import { LevelEnum } from "../../../definitions/content/SectionStatus"; +import I18n from "../../i18n"; +import { + isSectionVisibleSelector, + levelForSectionSelector, + messageForSectionSelector, + SectionStatusKey, + webUrlForSectionSelector +} from "../../store/reducers/backendStatus/sectionStatus"; +import { getFullLocale } from "../../utils/locale"; +import { openWebUrl } from "../../utils/url"; +import { useIOSelector } from "../../store/hooks"; +import { useIONavigation } from "../../navigation/params/AppParamsList"; + +type Props = { + sectionKey: SectionStatusKey; + onSectionRef?: (ref: React.RefObject) => void; +}; + +const statusVariantMap: Record< + LevelEnum, + ComponentProps["variant"] +> = { + [LevelEnum.normal]: "info", + [LevelEnum.critical]: "error", + [LevelEnum.warning]: "warning" +}; + +const SectionStatusComponent = ({ sectionKey, onSectionRef }: Props) => { + const viewRef = useRef(null); + const locale = getFullLocale(); + + const navigation = useIONavigation(); + const isSectionVisible = useIOSelector(state => + isSectionVisibleSelector(state, sectionKey) + ); + const webUrl = useIOSelector(state => + webUrlForSectionSelector(state, sectionKey, locale) + ); + const message = useIOSelector(state => + messageForSectionSelector(state, sectionKey, locale) + ); + const level = useIOSelector(state => + levelForSectionSelector(state, sectionKey) + ); + + const onPressCallback = useCallback(() => { + if (webUrl) { + openWebUrl(webUrl); + } + }, [webUrl]); + const invokeSessionRefCallback = useCallback(() => { + if (viewRef.current) { + onSectionRef?.(viewRef); + } + }, [onSectionRef, viewRef]); + + useEffect(() => { + if (!isSectionVisible) { + return; + } + invokeSessionRefCallback(); + navigation?.addListener("focus", invokeSessionRefCallback); + return () => navigation?.removeListener("focus", invokeSessionRefCallback); + }, [invokeSessionRefCallback, isSectionVisible, navigation, viewRef]); + + if (!isSectionVisible) { + return null; + } + + const action = webUrl ? I18n.t("global.sectionStatus.moreInfo") : undefined; + const content = `${message}`; + const variant = statusVariantMap[level ?? LevelEnum.normal]; + const testId = `SectionStatusComponent${webUrl ? "Pressable" : "Content"}`; + + return ( + + ); +}; + +export default SectionStatusComponent; diff --git a/ts/components/SectionStatus/modal/index.tsx b/ts/components/SectionStatus/modal/index.tsx new file mode 100644 index 00000000000..f47db63ab9a --- /dev/null +++ b/ts/components/SectionStatus/modal/index.tsx @@ -0,0 +1,157 @@ +import React, { ComponentProps, useCallback, useEffect, useRef } from "react"; +import { AccessibilityInfo, View } from "react-native"; +import { Alert, IOColors } from "@pagopa/io-app-design-system"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { LevelEnum } from "../../../../definitions/content/SectionStatus"; +import I18n from "../../../i18n"; +import { + isSectionVisibleSelector, + levelForSectionSelector, + messageForSectionSelector, + SectionStatusKey, + webUrlForSectionSelector +} from "../../../store/reducers/backendStatus/sectionStatus"; +import { getFullLocale } from "../../../utils/locale"; +import { openWebUrl } from "../../../utils/url"; +import { useIOSelector } from "../../../store/hooks"; +import { useIONavigation } from "../../../navigation/params/AppParamsList"; +import { isMixpanelInitializedSelector } from "../../../features/mixpanel/store/selectors"; +import { isMixpanelEnabled as isMixpanelEnabledSelector } from "../../../store/reducers/persistedPreferences"; + +type Props = { + sectionKey: SectionStatusKey; + onSectionRef?: (ref: React.RefObject) => void; + trackingAction?: () => void; + sticky?: boolean; +}; + +const statusVariantMap: Record< + LevelEnum, + { + variant: ComponentProps["variant"]; + background: IOColors; + } +> = { + [LevelEnum.normal]: { + variant: "info", + background: "info-100" + }, + [LevelEnum.critical]: { variant: "error", background: "error-100" }, + [LevelEnum.warning]: { variant: "warning", background: "warning-100" } +}; + +/** + * This component is a clone of `SectionStatusComponent` but, in addition, + * when `sticky` prop is `true` it can be anchored to the top of the screen to avoid to push down the content. + * Another additional feature is that it adds top padding related to `SafeAreaInsets` top value + * to avoid content from being covered from device status bar. + */ +const ModalSectionStatusComponent = ({ + sectionKey, + sticky, + onSectionRef, + trackingAction +}: Props) => { + const { top } = useSafeAreaInsets(); + const viewRef = useRef(null); + const locale = getFullLocale(); + const isMixpanelInitialized = useIOSelector(isMixpanelInitializedSelector); + const isMixpanelEnabled = useIOSelector(isMixpanelEnabledSelector); + + const navigation = useIONavigation(); + const isSectionVisible = useIOSelector(state => + isSectionVisibleSelector(state, sectionKey) + ); + const webUrl = useIOSelector(state => + webUrlForSectionSelector(state, sectionKey, locale) + ); + const message = useIOSelector(state => + messageForSectionSelector(state, sectionKey, locale) + ); + const level = useIOSelector(state => + levelForSectionSelector(state, sectionKey) + ); + + const onPressCallback = useCallback(() => { + if (webUrl) { + openWebUrl(webUrl); + } + }, [webUrl]); + const invokeSessionRefCallback = useCallback(() => { + if (viewRef.current) { + onSectionRef?.(viewRef); + } + }, [onSectionRef, viewRef]); + + useEffect(() => { + if ( + isSectionVisible && + isMixpanelInitialized && + isMixpanelEnabled !== false + ) { + trackingAction?.(); + } + }, [ + isSectionVisible, + isMixpanelInitialized, + isMixpanelEnabled, + trackingAction + ]); + + useEffect(() => { + if (!isSectionVisible) { + return; + } + invokeSessionRefCallback(); + navigation?.addListener("focus", invokeSessionRefCallback); + return () => navigation?.removeListener("focus", invokeSessionRefCallback); + }, [invokeSessionRefCallback, isSectionVisible, navigation, viewRef]); + + useEffect(() => { + if (isSectionVisible && message) { + AccessibilityInfo.announceForAccessibilityWithOptions(message, { + queue: true + }); + } + }, [isSectionVisible, message]); + + if (!isSectionVisible) { + return null; + } + + const action = webUrl ? I18n.t("global.sectionStatus.moreInfo") : undefined; + const { variant, background } = statusVariantMap[level ?? LevelEnum.normal]; + const testId = `ModalSectionStatusComponent${ + webUrl ? "Pressable" : "Content" + }`; + + return ( + + + + ); +}; + +export default ModalSectionStatusComponent; diff --git a/ts/features/bonus/cdc/components/CdcServiceCTA.tsx b/ts/features/bonus/cdc/components/CdcServiceCTA.tsx index 6612d51e715..641d0c2b57e 100644 --- a/ts/features/bonus/cdc/components/CdcServiceCTA.tsx +++ b/ts/features/bonus/cdc/components/CdcServiceCTA.tsx @@ -12,6 +12,7 @@ import { View } from "react-native"; import { StatoBeneficiarioEnum } from "../../../../../definitions/cdc/StatoBeneficiario"; import { BonusVisibilityEnum } from "../../../../../definitions/content/BonusVisibility"; import { fold } from "../../../../common/model/RemoteValue"; +import SectionStatusComponent from "../../../../components/SectionStatus"; import ActivityIndicator from "../../../../components/ui/ActivityIndicator"; import I18n from "../../../../i18n"; import { useIONavigation } from "../../../../navigation/params/AppParamsList"; @@ -159,6 +160,7 @@ const CdcServiceCTAButton = () => { const CdcServiceCTA = () => ( + diff --git a/ts/features/bonus/cdc/components/__test__/CdcServiceCTA.test.ts b/ts/features/bonus/cdc/components/__test__/CdcServiceCTA.test.ts new file mode 100644 index 00000000000..2bd221e23f3 --- /dev/null +++ b/ts/features/bonus/cdc/components/__test__/CdcServiceCTA.test.ts @@ -0,0 +1,180 @@ +import { createStore, Store } from "redux"; +import { Anno } from "../../../../../../definitions/cdc/Anno"; +import { StatoBeneficiarioEnum } from "../../../../../../definitions/cdc/StatoBeneficiario"; +import { BonusAvailable } from "../../../../../../definitions/content/BonusAvailable"; +import ROUTES from "../../../../../navigation/routes"; +import { applicationChangeState } from "../../../../../store/actions/application"; +import { appReducer } from "../../../../../store/reducers"; +import { GlobalState } from "../../../../../store/reducers/types"; +import { getTimeoutError } from "../../../../../utils/errors"; +import { renderScreenWithNavigationStoreContext } from "../../../../../utils/testWrapper"; +import { loadAvailableBonuses } from "../../../common/store/actions/availableBonusesTypes"; +import { cdcRequestBonusList } from "../../store/actions/cdcBonusRequest"; +import { CdcBonusRequest } from "../../types/CdcBonusRequest"; +import CdcServiceCTA from "../CdcServiceCTA"; + +jest.useFakeTimers(); + +const mockActivableBonus: CdcBonusRequest = { + year: "2018" as Anno, + status: StatoBeneficiarioEnum.ATTIVABILE +}; +const mockPendingBonus: CdcBonusRequest = { + year: "2018" as Anno, + status: StatoBeneficiarioEnum.VALUTAZIONE +}; +const mockActiveBonus: CdcBonusRequest = { + year: "2018" as Anno, + status: StatoBeneficiarioEnum.ATTIVO +}; +const mockNotRequestableBonus: CdcBonusRequest = { + year: "2018" as Anno, + status: StatoBeneficiarioEnum.INATTIVO +}; +const mockExpiredBonus: CdcBonusRequest = { + year: "2018" as Anno, + status: StatoBeneficiarioEnum.INATTIVABILE +}; + +const bonusMockContent = { + name: "MockCartadellacultura", + description: "MockDescription", + subtitle: "MockSubtitle", + title: "MockTitle", + content: "MockContent", + tos_url: "https://io.italia.it/mockTosUrl" +}; + +const mockBonus: BonusAvailable = { + id_type: 4, + it: bonusMockContent, + en: bonusMockContent, + valid_from: new Date(), + valid_to: new Date(), + is_active: false +}; + +describe("CdcServiceCTA", () => { + const globalState = appReducer(undefined, applicationChangeState("active")); + describe("When the allAvailableBonusTypes is potSome and the cdc bonus is different from undefined", () => { + it("Should show the activateCardButton if the cdcBonusRequestList is ready and there is at least a card with the status Activable", () => { + const store: Store = createStore( + appReducer, + globalState as any + ); + store.dispatch(loadAvailableBonuses.success([mockBonus])); + const component = renderComponent(store); + store.dispatch(cdcRequestBonusList.success([mockActivableBonus])); + const activateCardButton = component.getByTestId("activateCardButton"); + expect(activateCardButton).toBeDefined(); + }); + it("Should show the pendingCardButton if the cdcBonusRequestList is ready and there is at least a card with the status Pending and there isn't card with the status Activable", () => { + const store: Store = createStore( + appReducer, + globalState as any + ); + store.dispatch(loadAvailableBonuses.success([mockBonus])); + const component = renderComponent(store); + store.dispatch(cdcRequestBonusList.success([mockPendingBonus])); + const pendingCardButton = component.getByTestId("pendingCardButton"); + expect(pendingCardButton).toBeDefined(); + }); + it("Should return null if the cdcBonusRequestList is ready and there isn't card with the status Activable or Pending", () => { + const store: Store = createStore( + appReducer, + globalState as any + ); + store.dispatch(loadAvailableBonuses.success([mockBonus])); + const component = renderComponent(store); + store.dispatch( + cdcRequestBonusList.success([ + mockActiveBonus, + mockNotRequestableBonus, + mockExpiredBonus + ]) + ); + const activateCardButton = component.queryByTestId("activateCardButton"); + const pendingCardButton = component.queryByTestId("pendingCardButton"); + + expect(activateCardButton).toBeNull(); + expect(pendingCardButton).toBeNull(); + }); + it("Should show the activityIndicator if the cdcBonusRequestList is in loading state", () => { + const store: Store = createStore( + appReducer, + globalState as any + ); + store.dispatch(loadAvailableBonuses.success([mockBonus])); + const component = renderComponent(store); + const activityIndicator = component.getByTestId("rn-activity-indicator"); + + expect(activityIndicator).toBeDefined(); + }); + it("Should show the errorStatusComponent and the retryButton button if the cdcBonusRequestList is in failure state", () => { + const store: Store = createStore( + appReducer, + globalState as any + ); + store.dispatch(loadAvailableBonuses.success([mockBonus])); + const component = renderComponent(store); + store.dispatch(cdcRequestBonusList.failure(getTimeoutError())); + const errorStatusComponent = component.getByTestId( + "SectionStatusContent" + ); + const retryButton = component.getByTestId("retryButton"); + + expect(errorStatusComponent).toBeDefined(); + expect(retryButton).toBeDefined(); + }); + }); + it("Should show the activityIndicator if the allAvailableBonusTypes is potNoneLoading or potSomeLoading", () => { + const store: Store = createStore( + appReducer, + globalState as any + ); + const component = renderComponent(store); + const activityIndicator = component.getByTestId("rn-activity-indicator"); + + expect(activityIndicator).toBeDefined(); + store.dispatch(loadAvailableBonuses.success([mockBonus])); + store.dispatch(loadAvailableBonuses.request()); + expect(activityIndicator).toBeDefined(); + }); + it("Should show the ErrorButton if the allAvailableBonusTypes is potNoneError or potSomeError", () => { + const store: Store = createStore( + appReducer, + globalState as any + ); + const component = renderComponent(store); + store.dispatch(loadAvailableBonuses.failure(new Error())); + const retryButton = component.getByTestId("retryButton"); + expect(retryButton).toBeDefined(); + store.dispatch(loadAvailableBonuses.success([mockBonus])); + store.dispatch(loadAvailableBonuses.failure(new Error())); + expect(retryButton).toBeDefined(); + }); + it("Should return null if the allAvailableBonusTypes is potSome but the cdcBonus is not available", () => { + const store: Store = createStore( + appReducer, + globalState as any + ); + const component = renderComponent(store); + store.dispatch( + loadAvailableBonuses.success([{ ...mockBonus, id_type: 1 }]) + ); + const activateCardButton = component.queryByTestId("activateCardButton"); + const pendingCardButton = component.queryByTestId("pendingCardButton"); + + expect(activateCardButton).toBeNull(); + expect(pendingCardButton).toBeNull(); + }); +}); + +function renderComponent(store: Store) { + return renderScreenWithNavigationStoreContext( + CdcServiceCTA, + ROUTES.MAIN, + {}, + store + ); +} diff --git a/ts/features/design-system/core/DSLegacyAlert.tsx b/ts/features/design-system/core/DSLegacyAlert.tsx new file mode 100644 index 00000000000..03a49f1daa8 --- /dev/null +++ b/ts/features/design-system/core/DSLegacyAlert.tsx @@ -0,0 +1,56 @@ +import { VSpacer } from "@pagopa/io-app-design-system"; +import * as React from "react"; +import StatusContent, { + statusColorMap, + getStatusTextColor, + statusIconMap +} from "../../../components/SectionStatus/StatusContent"; +import { DSFullWidthComponent } from "../components/DSFullWidthComponent"; + +/* Types */ +import { LevelEnum } from "../../../../definitions/content/SectionStatus"; +import { DesignSystemScreen } from "../components/DesignSystemScreen"; + +export const DSLegacyAlert = () => ( + + + + { + "L’invio dei Certificati Verdi è in corso e potrebbe richiedere diversi giorni." + } + + + + + + {"La sezione Messaggi è in manutenzione, tornerà operativa a breve"} + + + + + + { + "I nostri sistemi potrebbero rispondere con lentezza, ci scusiamo per il disagio." + } + + + +); From e49f5b88043ab6b0b03b51630e089e7349d10bda Mon Sep 17 00:00:00 2001 From: Damiano Plebani Date: Fri, 29 Nov 2024 16:00:13 +0100 Subject: [PATCH 4/7] Remove `DSLegacyAlert` and fix tests --- .../__tests__/SectionStatusContent.test.tsx | 22 --- .../SectionStatusContent.test.tsx.snap | 159 ------------------ .../components/__test__/CdcServiceCTA.test.ts | 16 -- .../design-system/core/DSLegacyAlert.tsx | 56 ------ 4 files changed, 253 deletions(-) delete mode 100644 ts/components/SectionStatus/__tests__/SectionStatusContent.test.tsx delete mode 100644 ts/components/SectionStatus/__tests__/__snapshots__/SectionStatusContent.test.tsx.snap delete mode 100644 ts/features/design-system/core/DSLegacyAlert.tsx diff --git a/ts/components/SectionStatus/__tests__/SectionStatusContent.test.tsx b/ts/components/SectionStatus/__tests__/SectionStatusContent.test.tsx deleted file mode 100644 index 64d88674bd4..00000000000 --- a/ts/components/SectionStatus/__tests__/SectionStatusContent.test.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from "react"; -import { View } from "react-native"; -import { render } from "@testing-library/react-native"; - -import StatusContent from "../StatusContent"; - -describe("StatusContent", () => { - it("should match the snapshot", () => { - const viewRef = React.createRef(); - const component = render( - - ); - expect(component.toJSON()).toMatchSnapshot(); - }); -}); diff --git a/ts/components/SectionStatus/__tests__/__snapshots__/SectionStatusContent.test.tsx.snap b/ts/components/SectionStatus/__tests__/__snapshots__/SectionStatusContent.test.tsx.snap deleted file mode 100644 index 133af5f36d6..00000000000 --- a/ts/components/SectionStatus/__tests__/__snapshots__/SectionStatusContent.test.tsx.snap +++ /dev/null @@ -1,159 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`StatusContent should match the snapshot 1`] = ` - - - - - - - - - - - - - -`; diff --git a/ts/features/bonus/cdc/components/__test__/CdcServiceCTA.test.ts b/ts/features/bonus/cdc/components/__test__/CdcServiceCTA.test.ts index 2bd221e23f3..a5ac0dcd0c7 100644 --- a/ts/features/bonus/cdc/components/__test__/CdcServiceCTA.test.ts +++ b/ts/features/bonus/cdc/components/__test__/CdcServiceCTA.test.ts @@ -110,22 +110,6 @@ describe("CdcServiceCTA", () => { expect(activityIndicator).toBeDefined(); }); - it("Should show the errorStatusComponent and the retryButton button if the cdcBonusRequestList is in failure state", () => { - const store: Store = createStore( - appReducer, - globalState as any - ); - store.dispatch(loadAvailableBonuses.success([mockBonus])); - const component = renderComponent(store); - store.dispatch(cdcRequestBonusList.failure(getTimeoutError())); - const errorStatusComponent = component.getByTestId( - "SectionStatusContent" - ); - const retryButton = component.getByTestId("retryButton"); - - expect(errorStatusComponent).toBeDefined(); - expect(retryButton).toBeDefined(); - }); }); it("Should show the activityIndicator if the allAvailableBonusTypes is potNoneLoading or potSomeLoading", () => { const store: Store = createStore( diff --git a/ts/features/design-system/core/DSLegacyAlert.tsx b/ts/features/design-system/core/DSLegacyAlert.tsx deleted file mode 100644 index 03a49f1daa8..00000000000 --- a/ts/features/design-system/core/DSLegacyAlert.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { VSpacer } from "@pagopa/io-app-design-system"; -import * as React from "react"; -import StatusContent, { - statusColorMap, - getStatusTextColor, - statusIconMap -} from "../../../components/SectionStatus/StatusContent"; -import { DSFullWidthComponent } from "../components/DSFullWidthComponent"; - -/* Types */ -import { LevelEnum } from "../../../../definitions/content/SectionStatus"; -import { DesignSystemScreen } from "../components/DesignSystemScreen"; - -export const DSLegacyAlert = () => ( - - - - { - "L’invio dei Certificati Verdi è in corso e potrebbe richiedere diversi giorni." - } - - - - - - {"La sezione Messaggi è in manutenzione, tornerà operativa a breve"} - - - - - - { - "I nostri sistemi potrebbero rispondere con lentezza, ci scusiamo per il disagio." - } - - - -); From 783867d87ca1f3ff7e68eeea8373241809db693b Mon Sep 17 00:00:00 2001 From: Damiano Plebani Date: Fri, 29 Nov 2024 16:02:02 +0100 Subject: [PATCH 5/7] Fix `tsc` error --- ts/features/bonus/cdc/components/__test__/CdcServiceCTA.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/ts/features/bonus/cdc/components/__test__/CdcServiceCTA.test.ts b/ts/features/bonus/cdc/components/__test__/CdcServiceCTA.test.ts index a5ac0dcd0c7..a6b2933075a 100644 --- a/ts/features/bonus/cdc/components/__test__/CdcServiceCTA.test.ts +++ b/ts/features/bonus/cdc/components/__test__/CdcServiceCTA.test.ts @@ -6,7 +6,6 @@ import ROUTES from "../../../../../navigation/routes"; import { applicationChangeState } from "../../../../../store/actions/application"; import { appReducer } from "../../../../../store/reducers"; import { GlobalState } from "../../../../../store/reducers/types"; -import { getTimeoutError } from "../../../../../utils/errors"; import { renderScreenWithNavigationStoreContext } from "../../../../../utils/testWrapper"; import { loadAvailableBonuses } from "../../../common/store/actions/availableBonusesTypes"; import { cdcRequestBonusList } from "../../store/actions/cdcBonusRequest"; From 873fde153e609e4386a34ead948dde15636297a3 Mon Sep 17 00:00:00 2001 From: Damiano Plebani Date: Fri, 29 Nov 2024 16:04:55 +0100 Subject: [PATCH 6/7] Remove legacy `CopyButtonComponent` --- ts/components/CopyButtonComponent.tsx | 24 ------- .../design-system/core/DSLegacyButtons.tsx | 16 ----- ...ineDiscountTransactionDetailsComponent.tsx | 32 ++++------ .../TimelineRefundDetailsComponent.tsx | 28 +++------ .../TimelineTransactionDetailsComponent.tsx | 63 +++++++------------ 5 files changed, 42 insertions(+), 121 deletions(-) delete mode 100644 ts/components/CopyButtonComponent.tsx diff --git a/ts/components/CopyButtonComponent.tsx b/ts/components/CopyButtonComponent.tsx deleted file mode 100644 index d777ca3fa84..00000000000 --- a/ts/components/CopyButtonComponent.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import * as React from "react"; -import { IconButton } from "@pagopa/io-app-design-system"; -import I18n from "../i18n"; -import { clipboardSetStringWithFeedback } from "../utils/clipboard"; - -type Props = Readonly<{ - textToCopy: string; -}>; - -const CopyButtonComponent: React.FunctionComponent = (props: Props) => { - const handlePress = () => { - clipboardSetStringWithFeedback(props.textToCopy); - }; - - return ( - - ); -}; - -export default CopyButtonComponent; diff --git a/ts/features/design-system/core/DSLegacyButtons.tsx b/ts/features/design-system/core/DSLegacyButtons.tsx index 69b6fff67a6..73639425717 100644 --- a/ts/features/design-system/core/DSLegacyButtons.tsx +++ b/ts/features/design-system/core/DSLegacyButtons.tsx @@ -6,8 +6,6 @@ import { } from "@pagopa/io-app-design-system"; import * as React from "react"; import { Alert } from "react-native"; -import CopyButtonComponent from "../../../components/CopyButtonComponent"; -import { DSComponentViewerBox } from "../components/DSComponentViewerBox"; import { DesignSystemScreen } from "../components/DesignSystemScreen"; const onButtonPress = () => { @@ -15,7 +13,6 @@ const onButtonPress = () => { }; const componentInnerMargin = 16; -const componentMargin = 24; const sectionTitleMargin = 16; const blockMargin = 40; @@ -29,11 +26,6 @@ export const DSLegacyButtons = () => {

Block Buttons

{renderBlockButtons()} - - -

Specific buttons

- {renderSpecificButtons()} -
); @@ -118,11 +110,3 @@ const renderBlockButtons = () => ( /> ); - -const renderSpecificButtons = () => ( - - - - - -); diff --git a/ts/features/idpay/timeline/components/TimelineDiscountTransactionDetailsComponent.tsx b/ts/features/idpay/timeline/components/TimelineDiscountTransactionDetailsComponent.tsx index d8b869f0eba..9bea069faea 100644 --- a/ts/features/idpay/timeline/components/TimelineDiscountTransactionDetailsComponent.tsx +++ b/ts/features/idpay/timeline/components/TimelineDiscountTransactionDetailsComponent.tsx @@ -3,6 +3,7 @@ import { Body, H6, HSpacer, + ListItemInfoCopy, VSpacer } from "@pagopa/io-app-design-system"; import * as O from "fp-ts/lib/Option"; @@ -13,10 +14,10 @@ import { TransactionDetailDTO, StatusEnum as TransactionStatusEnum } from "../../../../../definitions/idpay/TransactionDetailDTO"; -import CopyButtonComponent from "../../../../components/CopyButtonComponent"; import ItemSeparatorComponent from "../../../../components/ItemSeparatorComponent"; import { IOStyles } from "../../../../components/core/variables/IOStyles"; import I18n from "../../../../i18n"; +import { clipboardSetStringWithFeedback } from "../../../../utils/clipboard"; import { format } from "../../../../utils/dates"; import { formatNumberCentsToAmount } from "../../../../utils/stringBuilder"; @@ -127,26 +128,15 @@ const TimelineDiscountTransactionDetailsComponent = (props: Props) => { {format(transaction.operationDate, "DD MMM YYYY, HH:mm")}
- - - {I18n.t( - "idpay.initiative.operationDetails.discount.details.labels.transactionID" - )} - - - - - {transaction.operationId} - - - - - + { + clipboardSetStringWithFeedback(transaction.operationId); + }} + />
); }; diff --git a/ts/features/idpay/timeline/components/TimelineRefundDetailsComponent.tsx b/ts/features/idpay/timeline/components/TimelineRefundDetailsComponent.tsx index d0ecb8290d1..d9a7f1bc60a 100644 --- a/ts/features/idpay/timeline/components/TimelineRefundDetailsComponent.tsx +++ b/ts/features/idpay/timeline/components/TimelineRefundDetailsComponent.tsx @@ -3,7 +3,7 @@ import { Alert, Badge, Body, - HSpacer, + ListItemInfoCopy, VSpacer } from "@pagopa/io-app-design-system"; import { CommonActions } from "@react-navigation/native"; @@ -14,12 +14,11 @@ import React from "react"; import { StyleSheet, View } from "react-native"; import { RefundDetailDTO } from "../../../../../definitions/idpay/RefundDetailDTO"; import { OperationTypeEnum } from "../../../../../definitions/idpay/RefundOperationDTO"; -import CopyButtonComponent from "../../../../components/CopyButtonComponent"; -import { IOStyles } from "../../../../components/core/variables/IOStyles"; import I18n from "../../../../i18n"; import NavigationService from "../../../../navigation/NavigationService"; import { useIOSelector } from "../../../../store/hooks"; import themeVariables from "../../../../theme/variables"; +import { clipboardSetStringWithFeedback } from "../../../../utils/clipboard"; import { formatNumberCentsToAmount } from "../../../../utils/stringBuilder"; import { IdPayConfigurationRoutes } from "../../configuration/navigation/routes"; import { idpayInitiativeIdSelector } from "../../details/store"; @@ -124,22 +123,13 @@ const TimelineRefundDetailsComponent = (props: Props) => { {format(refund.operationDate, "DD MMM YYYY, HH:mm")}
- - CRO - - - - {refund.cro} - - - - - + { + clipboardSetStringWithFeedback(refund.cro || ""); + }} + /> ); }; diff --git a/ts/features/idpay/timeline/components/TimelineTransactionDetailsComponent.tsx b/ts/features/idpay/timeline/components/TimelineTransactionDetailsComponent.tsx index 6652739baf0..ef9085d4130 100644 --- a/ts/features/idpay/timeline/components/TimelineTransactionDetailsComponent.tsx +++ b/ts/features/idpay/timeline/components/TimelineTransactionDetailsComponent.tsx @@ -1,23 +1,24 @@ -import * as O from "fp-ts/lib/Option"; -import { pipe } from "fp-ts/lib/function"; -import React from "react"; -import { StyleSheet, View } from "react-native"; import { Alert, Body, H6, HSpacer, + ListItemInfoCopy, VSpacer } from "@pagopa/io-app-design-system"; +import * as O from "fp-ts/lib/Option"; +import { pipe } from "fp-ts/lib/function"; +import React from "react"; +import { StyleSheet, View } from "react-native"; import { TransactionDetailDTO, OperationTypeEnum as TransactionTypeEnum } from "../../../../../definitions/idpay/TransactionDetailDTO"; -import CopyButtonComponent from "../../../../components/CopyButtonComponent"; import ItemSeparatorComponent from "../../../../components/ItemSeparatorComponent"; import { IOStyles } from "../../../../components/core/variables/IOStyles"; import { LogoPaymentWithFallback } from "../../../../components/ui/utils/components/LogoPaymentWithFallback"; import I18n from "../../../../i18n"; +import { clipboardSetStringWithFeedback } from "../../../../utils/clipboard"; import { format } from "../../../../utils/dates"; import { formatNumberCentsToAmount } from "../../../../utils/stringBuilder"; import { getLabelForCircuitType } from "../../common/labels"; @@ -118,42 +119,22 @@ const TimelineTransactionDetailsComponent = (props: Props) => { {getLabelForCircuitType(transaction.circuitType)}
- - - {I18n.t("idpay.initiative.operationDetails.transaction.acquirerId")} - - - - - {idTrxAcquirer} - - - - - - - - {I18n.t("idpay.initiative.operationDetails.transaction.issuerId")} - - - - - {idTrxIssuer} - - - - - + { + clipboardSetStringWithFeedback(idTrxAcquirer); + }} + /> + { + clipboardSetStringWithFeedback(idTrxIssuer); + }} + />
); }; From d4a25331eb732bf865e2c376299e630c66c3bf82 Mon Sep 17 00:00:00 2001 From: Damiano Plebani Date: Fri, 29 Nov 2024 16:19:20 +0100 Subject: [PATCH 7/7] Remove `DSLegacyButtons` and `BlockButtons` references --- .../design-system/core/DSLegacyButtons.tsx | 112 ------------------ .../design-system/navigation/navigator.tsx | 9 -- .../design-system/navigation/params.ts | 1 - .../design-system/navigation/routes.ts | 4 - .../fci/hooks/useFciAbortSignatureFlow.tsx | 51 ++++---- 5 files changed, 21 insertions(+), 156 deletions(-) delete mode 100644 ts/features/design-system/core/DSLegacyButtons.tsx diff --git a/ts/features/design-system/core/DSLegacyButtons.tsx b/ts/features/design-system/core/DSLegacyButtons.tsx deleted file mode 100644 index 73639425717..00000000000 --- a/ts/features/design-system/core/DSLegacyButtons.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import { - BlockButtons, - H4, - VStack, - useIOTheme -} from "@pagopa/io-app-design-system"; -import * as React from "react"; -import { Alert } from "react-native"; -import { DesignSystemScreen } from "../components/DesignSystemScreen"; - -const onButtonPress = () => { - Alert.alert("Alert", "Action triggered"); -}; - -const componentInnerMargin = 16; -const sectionTitleMargin = 16; -const blockMargin = 40; - -export const DSLegacyButtons = () => { - const theme = useIOTheme(); - - return ( - - - -

Block Buttons

- {renderBlockButtons()} -
-
-
- ); -}; - -const renderBlockButtons = () => ( - - - - - - -); diff --git a/ts/features/design-system/navigation/navigator.tsx b/ts/features/design-system/navigation/navigator.tsx index 375b1d10d99..9e0a78cb878 100644 --- a/ts/features/design-system/navigation/navigator.tsx +++ b/ts/features/design-system/navigation/navigator.tsx @@ -52,7 +52,6 @@ import { DSIcons } from "../core/DSIcons"; import { DSIridescentTrustmark } from "../core/DSIridescentTrustmark"; import { DSLayout } from "../core/DSLayout"; import { DSLegacyAdvice } from "../core/DSLegacyAdvice"; -import { DSLegacyButtons } from "../core/DSLegacyButtons"; import { DSLegacyListItems } from "../core/DSLegacyListItems"; import { DSLegacyTextFields } from "../core/DSLegacyTextFields"; import { DSListItems } from "../core/DSListItems"; @@ -584,14 +583,6 @@ export const DesignSystemNavigator = () => { {/* LEGACY */} - - { dismiss(); }; - const cancelButtonProps: ButtonSolidProps = { - testID: "FciStopAbortingSignatureTestID", - onPress: () => dismiss(), - label: I18n.t("features.fci.abort.confirm"), - accessibilityLabel: I18n.t("features.fci.abort.confirm") - }; - const continueButtonProps: ButtonSolidProps = { - onPress: () => abortSignatureFlow(), - color: "danger", - label: I18n.t("features.fci.abort.cancel"), - accessibilityLabel: I18n.t("features.fci.abort.cancel") - }; - const { present: presentBs, bottomSheet, @@ -61,13 +46,19 @@ export const useFciAbortSignatureFlow = () => { ), snapPoint: [280], footer: ( - - - + dismiss(), + testID: "FciStopAbortingSignatureTestID" + }} + endAction={{ + color: "danger", + label: I18n.t("features.fci.abort.cancel"), + onPress: () => abortSignatureFlow() + }} + /> ) });