diff --git a/locales/en/index.yml b/locales/en/index.yml index 31eaa692c85..c7a78509f87 100644 --- a/locales/en/index.yml +++ b/locales/en/index.yml @@ -3199,6 +3199,7 @@ features: other: Altro badge: active: Già presente + unavailable: Non disponibile options: cgn: Carta Giovani Nazionale welfare: Iniziative welfare diff --git a/locales/it/index.yml b/locales/it/index.yml index e79963f19a2..dd1bc42a0ad 100644 --- a/locales/it/index.yml +++ b/locales/it/index.yml @@ -3199,6 +3199,7 @@ features: other: Altro badge: active: Già presente + unavailable: Non disponibile options: cgn: Carta Giovani Nazionale welfare: Iniziative welfare diff --git a/ts/features/itwallet/onboarding/components/ItwOnboardingModuleCredential.tsx b/ts/features/itwallet/onboarding/components/ItwOnboardingModuleCredential.tsx new file mode 100644 index 00000000000..b3f2f289d3d --- /dev/null +++ b/ts/features/itwallet/onboarding/components/ItwOnboardingModuleCredential.tsx @@ -0,0 +1,69 @@ +import React, { useMemo, memo } from "react"; +import { Badge, IOIcons, ModuleCredential } from "@pagopa/io-app-design-system"; +import I18n from "../../../../i18n"; +import { getCredentialNameFromType } from "../../common/utils/itwCredentialUtils"; +import { CredentialType } from "../../common/utils/itwMocksUtils"; + +type Props = { + type: string; + onPress: (type: string) => void; + isActive: boolean; + isDisabled: boolean; + isCredentialIssuancePending: boolean; + isSelectedCredential: boolean; +}; + +const credentialIconByType: Record = { + [CredentialType.DRIVING_LICENSE]: "car", + [CredentialType.EUROPEAN_DISABILITY_CARD]: "accessibility", + [CredentialType.EUROPEAN_HEALTH_INSURANCE_CARD]: "healthCard" +}; + +const activeBadge: Badge = { + variant: "success", + text: I18n.t("features.wallet.onboarding.badge.active") +}; + +const disabledBadge: Badge = { + variant: "default", + text: I18n.t("features.wallet.onboarding.badge.unavailable") +}; + +const ItwOnboardingModuleCredential = ({ + type, + onPress, + isActive, + isDisabled, + isSelectedCredential, + isCredentialIssuancePending +}: Props) => { + const badge = useMemo((): Badge | undefined => { + if (isActive) { + return activeBadge; + } + if (isDisabled) { + return disabledBadge; + } + return undefined; + }, [isActive, isDisabled]); + + const handleOnPress = () => { + onPress(type); + }; + + const isPressable = !(isActive || isDisabled || isCredentialIssuancePending); + + return ( + + ); +}; + +const MemoizedComponent = memo(ItwOnboardingModuleCredential); +export { MemoizedComponent as ItwOnboardingModuleCredential }; diff --git a/ts/features/itwallet/onboarding/screens/WalletCardOnboardingScreen.tsx b/ts/features/itwallet/onboarding/screens/WalletCardOnboardingScreen.tsx index 29a3ac6f281..d3d90ff7794 100644 --- a/ts/features/itwallet/onboarding/screens/WalletCardOnboardingScreen.tsx +++ b/ts/features/itwallet/onboarding/screens/WalletCardOnboardingScreen.tsx @@ -1,20 +1,22 @@ import { Badge, ContentWrapper, - IOIcons, ListItemHeader, ModuleCredential, VStack } from "@pagopa/io-app-design-system"; import { constFalse, pipe } from "fp-ts/lib/function"; import * as O from "fp-ts/lib/Option"; -import React from "react"; +import React, { useCallback } from "react"; import { useFocusEffect } from "@react-navigation/native"; import { IOScrollViewWithLargeHeader } from "../../../../components/ui/IOScrollViewWithLargeHeader"; import I18n from "../../../../i18n"; import { useIONavigation } from "../../../../navigation/params/AppParamsList"; import { useIODispatch, useIOSelector } from "../../../../store/hooks"; -import { isItwEnabledSelector } from "../../../../store/reducers/backendStatus/remoteConfig"; +import { + isItwEnabledSelector, + itwDisabledCredentialsSelector +} from "../../../../store/reducers/backendStatus/remoteConfig"; import { emptyContextualHelp } from "../../../../utils/emptyContextualHelp"; import { cgnActivationStart } from "../../../bonus/cgn/store/actions/activation"; import { @@ -24,7 +26,6 @@ import { import { loadAvailableBonuses } from "../../../bonus/common/store/actions/availableBonusesTypes"; import { PaymentsOnboardingRoutes } from "../../../payments/onboarding/navigation/routes"; import { isItwTrialActiveSelector } from "../../../trialSystem/store/reducers"; -import { getCredentialNameFromType } from "../../common/utils/itwCredentialUtils"; import { CredentialType } from "../../common/utils/itwMocksUtils"; import { itwCredentialsTypesSelector } from "../../credentials/store/selectors"; import { itwLifecycleIsValidSelector } from "../../lifecycle/store/selectors"; @@ -38,6 +39,14 @@ import { trackShowCredentialsList, trackStartAddNewCredential } from "../../analytics"; +import { ItwOnboardingModuleCredential } from "../components/ItwOnboardingModuleCredential"; + +// List of available credentials to show to the user +const availableCredentials = [ + CredentialType.DRIVING_LICENSE, + CredentialType.EUROPEAN_DISABILITY_CARD, + CredentialType.EUROPEAN_HEALTH_INSURANCE_CARD +] as const; const activeBadge: Badge = { variant: "success", @@ -49,7 +58,7 @@ const WalletCardOnboardingScreen = () => { const isItwValid = useIOSelector(itwLifecycleIsValidSelector); const isItwEnabled = useIOSelector(isItwEnabledSelector); - useFocusEffect(() => trackShowCredentialsList()); + useFocusEffect(trackShowCredentialsList); const isItwSectionVisible = React.useMemo( // IT Wallet credential catalog should be visible if @@ -79,6 +88,9 @@ const WalletCardOnboardingScreen = () => { const ItwCredentialOnboardingSection = () => { const machineRef = ItwCredentialIssuanceMachineContext.useActorRef(); + const remotelyDisabledCredentials = useIOSelector( + itwDisabledCredentialsSelector + ); const isCredentialIssuancePending = ItwCredentialIssuanceMachineContext.useSelector(selectIsLoading); @@ -87,33 +99,17 @@ const ItwCredentialOnboardingSection = () => { const itwCredentialsTypes = useIOSelector(itwCredentialsTypesSelector); - const beginCredentialIssuance = (type: CredentialType) => () => { - if (isCredentialIssuancePending) { - return; - } - const credentialName = CREDENTIALS_MAP[type]; - trackStartAddNewCredential(credentialName); - machineRef.send({ - type: "select-credential", - credentialType: type, - skipNavigation: true - }); - }; - // List of available credentials to show to the user - const availableCredentials = [ - CredentialType.DRIVING_LICENSE, - CredentialType.EUROPEAN_DISABILITY_CARD, - CredentialType.EUROPEAN_HEALTH_INSURANCE_CARD - ] as const; - - const credentialIconByType: Record< - (typeof availableCredentials)[number], - IOIcons - > = { - [CredentialType.DRIVING_LICENSE]: "car", - [CredentialType.EUROPEAN_DISABILITY_CARD]: "accessibility", - [CredentialType.EUROPEAN_HEALTH_INSURANCE_CARD]: "healthCard" - }; + const beginCredentialIssuance = useCallback( + (type: string) => { + trackStartAddNewCredential(CREDENTIALS_MAP[type]); + machineRef.send({ + type: "select-credential", + credentialType: type, + skipNavigation: true + }); + }, + [machineRef] + ); return ( <> @@ -121,32 +117,21 @@ const ItwCredentialOnboardingSection = () => { label={I18n.t("features.wallet.onboarding.sections.itw")} /> - {availableCredentials.map(type => { - const isCredentialAlreadyActive = itwCredentialsTypes.includes(type); - - return ( - t === type), - O.getOrElse(constFalse) - ) - } - badge={isCredentialAlreadyActive ? activeBadge : undefined} - /> - ); - })} + {availableCredentials.map(type => ( + t === type), + O.getOrElse(constFalse) + )} + onPress={beginCredentialIssuance} + /> + ))} ); diff --git a/ts/features/itwallet/onboarding/screens/__tests__/WalletCardOnboardingScreen.test.tsx b/ts/features/itwallet/onboarding/screens/__tests__/WalletCardOnboardingScreen.test.tsx index 2d71dfb8947..cd7897bf5ec 100644 --- a/ts/features/itwallet/onboarding/screens/__tests__/WalletCardOnboardingScreen.test.tsx +++ b/ts/features/itwallet/onboarding/screens/__tests__/WalletCardOnboardingScreen.test.tsx @@ -29,12 +29,9 @@ type RenderOptions = { isItwEnabled?: boolean; isItwTestEnabled?: boolean; itwLifecycle?: ItwLifecycleState; + remotelyDisabledCredentials?: Array; }; -jest.mock("../../../../../config", () => ({ - itwEnabled: true -})); - describe("WalletCardOnboardingScreen", () => { it("it should render the screen correctly", () => { const component = renderComponent({}); @@ -47,6 +44,11 @@ describe("WalletCardOnboardingScreen", () => { expect( queryByTestId(`${CredentialType.DRIVING_LICENSE}ModuleTestID`) ).toBeTruthy(); + expect( + queryByTestId( + `${CredentialType.EUROPEAN_HEALTH_INSURANCE_CARD}ModuleTestID` + ) + ).toBeTruthy(); expect( queryByTestId(`${CredentialType.EUROPEAN_DISABILITY_CARD}ModuleTestID`) ).toBeTruthy(); @@ -64,13 +66,30 @@ describe("WalletCardOnboardingScreen", () => { expect(queryByTestId("itwDiscoveryBannerTestID")).toBeNull(); } ); + + test.each([ + { remotelyDisabledCredentials: ["MDL"] }, + { remotelyDisabledCredentials: ["MDL", "EuropeanHealthInsuranceCard"] } + ] as ReadonlyArray)( + "it should hide credential modules when $remotelyDisabledCredentials are remotely disabled", + options => { + const { queryByTestId } = renderComponent(options); + for (const type of options.remotelyDisabledCredentials!) { + // Currently ModuleCredential does not attach the testID if onPress is undefined. + // Since disabled credentials have undefined onPress, we can test for null. + expect(queryByTestId(`${type}ModuleTestID`)).toBeNull(); + } + expect(queryByTestId("EuropeanDisabilityCardModuleTestID")).toBeTruthy(); + } + ); }); const renderComponent = ({ isIdPayEnabled = true, isItwEnabled = true, itwTrialStatus = SubscriptionStateEnum.ACTIVE, - itwLifecycle = ItwLifecycleState.ITW_LIFECYCLE_VALID + itwLifecycle = ItwLifecycleState.ITW_LIFECYCLE_VALID, + remotelyDisabledCredentials }: RenderOptions) => { const globalState = appReducer(undefined, applicationChangeState("active")); @@ -100,7 +119,8 @@ const renderComponent = ({ min_app_version: { android: "0.0.0.0", ios: "0.0.0.0" - } + }, + disabled_credentials: remotelyDisabledCredentials }, idPay: isIdPayEnabled && { min_app_version: { diff --git a/ts/store/reducers/backendStatus/remoteConfig.ts b/ts/store/reducers/backendStatus/remoteConfig.ts index 4d56ce160aa..7e93c116ec3 100644 --- a/ts/store/reducers/backendStatus/remoteConfig.ts +++ b/ts/store/reducers/backendStatus/remoteConfig.ts @@ -443,3 +443,16 @@ export const isItwActivationDisabledSelector = createSelector( O.getOrElse(() => false) ) ); + +/** + * Return IT Wallet credentials that have been disabled remotely. + */ +export const itwDisabledCredentialsSelector = createSelector( + remoteConfigSelector, + remoteConfig => + pipe( + remoteConfig, + O.chainNullableK(config => config.itw.disabled_credentials), + O.getOrElse(() => emptyArray) + ) +);