Skip to content

Commit

Permalink
chore: [IOBP-236] Banner pay with CIE into IDPay discount initiative (#…
Browse files Browse the repository at this point in the history
…5005)

## ⚠️ This PR depends on #4983 and #4995

## Short description
This PR adds a `Banner` inside a discount initiative details if the user
has already a CIE onboarded to his account. The banner is shown from the
first time the user lands in the bonus detail, and remains visible
until:
1. The user closes it via "X" close button
2. The user enables the initiative (either via banner or via toggle
within the relevant section)

## List of changes proposed in this pull request
- Added a new action preferences to dismiss the banner, the state is
persisted on the device based on the initiativeId;
- Added a new component `IdPayCodeCieBanner` where is handled all the
business logic to show or not the banner

## How to test
Open a discount initiative, the first time you open the detail you
should see the banner. If you dismiss it, even restarting the app you
should not see it anymore.

## Preview

https://github.com/pagopa/io-app/assets/34343582/01245e66-b80f-4f72-94f9-5a7b87b37c1c

---------

Co-authored-by: forrest57 <[email protected]>
Co-authored-by: Federico Mastrini <[email protected]>
  • Loading branch information
3 people authored Oct 2, 2023
1 parent 40c3f6f commit 2e90981
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 15 deletions.
5 changes: 5 additions & 0 deletions locales/en/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3476,6 +3476,11 @@ idpay:
discountDetails:
authorizeButton: Autorizza un pagamento
IDPayCode:
banner:
title: Paga con la tua carta d’identità elettronica
body: Spendi il denaro a te riservato con la semplicità del contactless.
action: Abilita ora
close: Chiudi
failureScreen:
cta: Chiudi
header:
Expand Down
5 changes: 5 additions & 0 deletions locales/it/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3500,6 +3500,11 @@ idpay:
discountDetails:
authorizeButton: Autorizza un pagamento
IDPayCode:
banner:
title: Paga con la tua carta d’identità elettronica
body: Spendi il denaro a te riservato con la semplicità del contactless.
action: Abilita ora
close: Chiudi
failureScreen:
cta: Chiudi
header:
Expand Down
83 changes: 83 additions & 0 deletions ts/features/idpay/code/components/IdPayCodeCieBanner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import * as React from "react";
import { VSpacer, Banner } from "@pagopa/io-app-design-system";
import { useNavigation } from "@react-navigation/native";

import I18n from "../../../../i18n";
import { useIODispatch, useIOSelector } from "../../../../store/hooks";
import { idpayInitiativeInstrumentsGet } from "../../configuration/store/actions";
import { showIdPayCodeBannerSelector } from "../store/selectors";
import { IdPayCodeParamsList } from "../navigation/params";
import { IdPayCodeRoutes } from "../navigation/routes";
import { IOStackNavigationProp } from "../../../../navigation/params/AppParamsList";
import { isLoadingDiscountInitiativeInstrumentsSelector } from "../../configuration/store";
import { idPayCodeCieBannerClose } from "../store/actions";

export type IdPayCodeCIEBannerParams = {
initiativeId: string;
};

const IdPayCodeCieBanner = ({ initiativeId }: IdPayCodeCIEBannerParams) => {
const bannerViewRef = React.useRef(null);
const navigation =
useNavigation<IOStackNavigationProp<IdPayCodeParamsList>>();
const dispatch = useIODispatch();
const showBanner = useIOSelector(showIdPayCodeBannerSelector);
const isLoadingInitiativeInstruments = useIOSelector(
isLoadingDiscountInitiativeInstrumentsSelector
);

React.useEffect(() => {
if (initiativeId) {
dispatch(
idpayInitiativeInstrumentsGet.request({
initiativeId
})
);
}
}, [initiativeId, dispatch]);

const handleOnCloseBanner = () => {
dispatch(idPayCodeCieBannerClose({ initiativeId }));
};

const handleNavigateToOnboardingStart = () => {
navigation.navigate(IdPayCodeRoutes.IDPAY_CODE_MAIN, {
screen: IdPayCodeRoutes.IDPAY_CODE_ONBOARDING,
params: {
initiativeId
}
});
};

if (showBanner && !isLoadingInitiativeInstruments) {
return (
<>
<Banner
color="turquoise"
pictogramName="cie"
title={I18n.t(
"idpay.initiative.discountDetails.IDPayCode.banner.title"
)}
size="big"
content={I18n.t(
"idpay.initiative.discountDetails.IDPayCode.banner.body"
)}
action={I18n.t(
"idpay.initiative.discountDetails.IDPayCode.banner.action"
)}
onPress={handleNavigateToOnboardingStart}
onClose={handleOnCloseBanner}
labelClose={I18n.t(
"idpay.initiative.discountDetails.IDPayCode.banner.close"
)}
viewRef={bannerViewRef}
/>
<VSpacer size={24} />
</>
);
}

return <></>;
};

export { IdPayCodeCieBanner };
12 changes: 11 additions & 1 deletion ts/features/idpay/code/store/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,18 @@ export const idPayEnrollCode = createAsyncAction(
export const idPayResetCode =
createStandardAction("IDPAY_RESET_CODE")<undefined>();

export type IdPayCodeCieBannerClosePayloadType = {
initiativeId: string;
};

/** This action is used to close a CIE banner into an initiative details */
export const idPayCodeCieBannerClose = createStandardAction(
"IDPAY_CODE_CIE_BANNER_CLOSE"
)<IdPayCodeCieBannerClosePayloadType>();

export type IdPayCodeActions =
| ActionType<typeof idPayGetCodeStatus>
| ActionType<typeof idPayGenerateCode>
| ActionType<typeof idPayEnrollCode>
| ActionType<typeof idPayResetCode>;
| ActionType<typeof idPayResetCode>
| ActionType<typeof idPayCodeCieBannerClose>;
31 changes: 29 additions & 2 deletions ts/features/idpay/code/store/reducers/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import * as pot from "@pagopa/ts-commons/lib/pot";
import { getType } from "typesafe-actions";
import { PersistConfig, persistReducer } from "redux-persist";
import AsyncStorage from "@react-native-async-storage/async-storage";

import { Action } from "../../../../../store/actions/types";
import { NetworkError } from "../../../../../utils/errors";
import {
idPayCodeCieBannerClose,
idPayEnrollCode,
idPayGenerateCode,
idPayGetCodeStatus,
Expand All @@ -13,12 +17,14 @@ export type IdPayCodeState = {
isOnboarded: pot.Pot<boolean, NetworkError>;
code: pot.Pot<string, NetworkError>;
enrollmentRequest: pot.Pot<void, NetworkError>;
isIdPayInitiativeBannerClosed: Record<string, boolean>;
};

const INITIAL_STATE: IdPayCodeState = {
isOnboarded: pot.none,
code: pot.none,
enrollmentRequest: pot.none
enrollmentRequest: pot.none,
isIdPayInitiativeBannerClosed: {}
};

const reducer = (
Expand Down Expand Up @@ -92,8 +98,29 @@ const reducer = (
...state,
code: pot.none
};
case getType(idPayCodeCieBannerClose):
return {
...state,
isIdPayInitiativeBannerClosed: {
[action.payload.initiativeId]: true
}
};
}
return state;
};

export default reducer;
const CURRENT_REDUX_FEATURES_STORE_VERSION = -1;

const persistConfig: PersistConfig = {
key: "code",
storage: AsyncStorage,
version: CURRENT_REDUX_FEATURES_STORE_VERSION,
whitelist: ["isIdPayInitiativeBannerClosed"]
};

export const idPayCodePersistor = persistReducer<IdPayCodeState, Action>(
persistConfig,
reducer
);

export default idPayCodePersistor;
24 changes: 24 additions & 0 deletions ts/features/idpay/code/store/selectors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import * as pot from "@pagopa/ts-commons/lib/pot";
import { createSelector } from "reselect";
import { GlobalState } from "../../../../../store/reducers/types";
import { IdPayCodeState } from "../reducers";
import { idpayDiscountInitiativeInstrumentsSelector } from "../../../configuration/store";
import { InstrumentTypeEnum } from "../../../../../../definitions/idpay/InstrumentDTO";
import { idpayInitiativeIdSelector } from "../../../details/store";

export const idPayCodeStateSelector = (state: GlobalState): IdPayCodeState =>
state.features.idPay.code;
Expand All @@ -20,3 +23,24 @@ export const idPayCodeSelector = createSelector(
idPayCodeStateSelector,
state => state.code
);

export const isIdPayInitiativeBannerClosedSelector = (state: GlobalState) =>
state.features.idPay.code.isIdPayInitiativeBannerClosed;

export const hasIdPayCodeInstrument = createSelector(
idpayDiscountInitiativeInstrumentsSelector,
instruments =>
instruments.some(
instrument => instrument.instrumentType === InstrumentTypeEnum.IDPAYCODE
)
);

export const showIdPayCodeBannerSelector = createSelector(
idpayInitiativeIdSelector,
isIdPayInitiativeBannerClosedSelector,
hasIdPayCodeInstrument,
(initiativeId, initiativeBannerClosed, hasIdPayCodeInstrument) =>
initiativeId !== undefined &&
(!initiativeBannerClosed || !initiativeBannerClosed[initiativeId]) &&
!hasIdPayCodeInstrument
);
8 changes: 5 additions & 3 deletions ts/features/idpay/common/store/reducers/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { combineReducers } from "redux";
import codeReducer, { IdPayCodeState } from "../../../code/store/reducers";
import { PersistPartial } from "redux-persist";

import codePersistor, { IdPayCodeState } from "../../../code/store/reducers";
import initiativeDetailsReducer, {
IdPayInitiativeState
} from "../../../details/store/index";
Expand All @@ -16,14 +18,14 @@ export type IDPayState = {
initiative: IdPayInitiativeState;
timeline: IdPayTimelineState;
configuration: IdPayInitiativeConfigurationState;
code: IdPayCodeState;
code: IdPayCodeState & PersistPartial;
};

const idPayReducer = combineReducers({
wallet: walletReducer,
initiative: initiativeDetailsReducer,
timeline: timelineReducer,
code: codeReducer,
code: codePersistor,
configuration: configurationReducer
});

Expand Down
23 changes: 14 additions & 9 deletions ts/features/idpay/details/screens/InitiativeDetailsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
Pictogram,
ContentWrapper
} from "@pagopa/io-app-design-system";
import Animated, { Layout } from "react-native-reanimated";
import {
InitiativeDTO,
InitiativeRewardTypeEnum,
Expand Down Expand Up @@ -42,6 +43,7 @@ import { idpayInitiativeGet, idpayTimelinePageGet } from "../store/actions";
import { IDPayPaymentRoutes } from "../../payment/navigation/navigator";
import { InitiativeDiscountSettingsComponent } from "../components/InitiativeDiscountSettingsComponent";
import { IDPayConfigurationRoutes } from "../../configuration/navigation/navigator";
import { IdPayCodeCieBanner } from "../../code/components/IdPayCodeCieBanner";

export type InitiativeDetailsScreenParams = {
initiativeId: string;
Expand Down Expand Up @@ -180,15 +182,18 @@ const InitiativeDetailsScreen = () => {
return (
<ContentWrapper>
<VSpacer size={8} />
<InitiativeTimelineComponent
initiativeId={initiativeId}
size={5}
/>
<VSpacer size={32} />
<InitiativeDiscountSettingsComponent
initiative={initiative}
/>
<VSpacer size={16} />
<IdPayCodeCieBanner initiativeId={initiative.initiativeId} />
<Animated.View layout={Layout.duration(200)}>
<InitiativeTimelineComponent
initiativeId={initiativeId}
size={5}
/>
<VSpacer size={32} />
<InitiativeDiscountSettingsComponent
initiative={initiative}
/>
<VSpacer size={16} />
</Animated.View>
</ContentWrapper>
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ import { NetworkError } from "../../../../../utils/errors";
import I18n from "../../../../../i18n";
import { formatDateAsLocal } from "../../../../../utils/dates";

jest.mock("react-native-reanimated", () => ({
...require("react-native-reanimated/mock"),
Layout: {
duration: jest.fn()
}
}));

const mockedInitiative: InitiativeDTO = {
endDate: new Date(2023, 1, 1),
initiativeId: "ABC123",
Expand Down

0 comments on commit 2e90981

Please sign in to comment.