Skip to content

Commit aecdf83

Browse files
Hantex9LeleDallas
andauthored
feat: [IOBP-1817,IOBP-1869] Add email notification banner after IdPay onboarding success (#7168)
## Short description This PR introduces a new email notification banner feature to display on the services home screen only when a user completes the IDPay onboarding and has email notifications disabled. ## List of changes proposed in this pull request * Added new strings for the email notification banner; * * Added a new action `setIdPayOnboardingSucceeded` to the IDPay wallet actions file. * Updated `IdPayCompletionScreen` to dispatch an action (`setIdPayOnboardingSucceeded`) upon first render, signaling successful onboarding; * Extended the IDPay wallet reducer to handle the new `onboardingSucceeded` state and added a selector `isIdPayOnboardingSucceededSelector` for accessing this state; * Created a new `EmailNotificationBanner` component in `ts/features/services/home/components/EmailNotificationBanner.tsx`. This banner is conditionally displayed based on the onboarding state and email notification settings. It allows users to enable email notifications and provides feedback on success or failure. * Integrated the `EmailNotificationBanner` into the `ServicesHomeScreen`, ensuring it is displayed above the featured service and institution lists ## How to test - Disable the email notification toggle from the profile settings -> Preferences -> "Inoltro dei mesaggi via email". - Start an IDPay onboarding process until you reach the end of it with a successful thank you page. - Check that the Banner at the top of the Services home screen is correctly displayed. - Check that if you tap on it, the email notification setting is enabled correctly. - Check that if you tap on the close icon button of the Banner, it disappears correctly. ## Preview https://github.com/user-attachments/assets/badf162c-7108-47cf-bf58-b37c9a3e15f3 --------- Co-authored-by: Emanuele Dall'Ara <[email protected]>
1 parent ea62db0 commit aecdf83

File tree

10 files changed

+156
-18
lines changed

10 files changed

+156
-18
lines changed

locales/en/index.json

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5813,13 +5813,21 @@
58135813
"confirmAction": "Ok, active"
58145814
},
58155815
"enableNotification": {
5816-
"title": "Non perderti l’esito delle tue richieste",
5817-
"subtitle": "Attiva le notifiche push per sapere subito quando ricevi un nuovo messaggio in app.",
5818-
"action": "Attiva notifiche push",
5819-
"deny": "Non attivare",
5816+
"title": "Don't risk losing the outcome of your request",
5817+
"subtitle": "Activate push notifications to know immediately when you receive a new message in the app.",
5818+
"action": "Activate notifications",
5819+
"deny": "Not now",
58205820
"success": "You activated Push notifications",
58215821
"advice": "Consigliato"
58225822
},
5823+
"preferences": {
5824+
"enableEmailBanner": {
5825+
"title": "Don't miss important updates",
5826+
"content": "Enable email forwarding to receive all communications even outside the app.",
5827+
"cta": "Notify me via email",
5828+
"successOutcome": "You've enabled email notifications"
5829+
}
5830+
},
58235831
"loading": {
58245832
"subtitle": "Attendi qualche secondo",
58255833
"title": "Stiamo elaborando la tua richiesta"

locales/it/index.json

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5815,11 +5815,19 @@
58155815
"enableNotification": {
58165816
"title": "Non perderti l’esito delle tue richieste",
58175817
"subtitle": "Attiva le notifiche push per sapere subito quando ricevi un nuovo messaggio in app.",
5818-
"action": "Attiva notifiche push",
5819-
"deny": "Non attivare",
5818+
"action": "Attiva le notifiche",
5819+
"deny": "Non ora",
58205820
"success": "Hai attivato le notifiche push",
58215821
"advice": "Consigliato"
58225822
},
5823+
"preferences": {
5824+
"enableEmailBanner": {
5825+
"title": "Non perderti aggiornamenti importanti",
5826+
"content": "Attiva l’inoltro via email per avere tutte le comunicazioni anche fuori dall'app.",
5827+
"cta": "Aggiornami via email",
5828+
"successOutcome": "Hai attivato le notifiche via email"
5829+
}
5830+
},
58235831
"loading": {
58245832
"subtitle": "Attendi qualche secondo",
58255833
"title": "Stiamo elaborando la tua richiesta"

ts/features/common/store/reducers/__tests__/__snapshots__/index.test.ts.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ exports[`featuresPersistor should match snapshot 1`] = `
120120
"initiativesWithInstrument": {
121121
"kind": "PotNone",
122122
},
123+
"onboardingSucceeded": false,
123124
},
124125
},
125126
"ingress": {

ts/features/idpay/onboarding/screens/IdPayCompletionScreen.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,27 @@ import LoadingSpinnerOverlay from "../../../../components/LoadingSpinnerOverlay"
33
import { OperationResultScreenContent } from "../../../../components/screens/OperationResultScreenContent";
44
import { useHeaderSecondLevel } from "../../../../hooks/useHeaderSecondLevel";
55
import I18n from "../../../../i18n";
6-
import { useIOSelector } from "../../../../store/hooks";
6+
import { useIODispatch, useIOSelector } from "../../../../store/hooks";
77
import { useOnFirstRender } from "../../../../utils/hooks/useOnFirstRender";
88
import { areNotificationPermissionsEnabledSelector } from "../../../pushNotifications/store/reducers/environment";
99
import { isLoadingSelector } from "../../common/machine/selectors";
1010
import { IdPayOnboardingMachineContext } from "../machine/provider";
11+
import { setIdPayOnboardingSucceeded } from "../../wallet/store/actions";
1112

1213
const IdPayCompletionScreen = () => {
1314
const { useActorRef, useSelector } = IdPayOnboardingMachineContext;
1415
const machine = useActorRef();
16+
const dispatch = useIODispatch();
1517

1618
const isLoading = useSelector(isLoadingSelector);
1719
const isPushNotificationEnabled = useIOSelector(
1820
areNotificationPermissionsEnabledSelector
1921
);
2022

21-
const handleClosePress = () => machine.send({ type: "close" });
23+
const handleClosePress = () => {
24+
dispatch(setIdPayOnboardingSucceeded(true));
25+
machine.send({ type: "close" });
26+
};
2227

2328
useHeaderSecondLevel({
2429
title: I18n.t("idpay.onboarding.headerTitle"),

ts/features/idpay/wallet/store/actions/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,15 @@ export const idPayInitiativesFromInstrumentRefreshStop = createStandardAction(
7272
"IDPAY_INITIATIVES_FROM_INSTRUMENT_REFRESH_STOP"
7373
)();
7474

75+
export const setIdPayOnboardingSucceeded = createStandardAction(
76+
"IDPAY_ONBOARDING_SUCCEEDED_SET"
77+
)<boolean>();
78+
7579
export type IdPayWalletActions =
7680
| ActionType<typeof idPayWalletGet>
7781
| ActionType<typeof idPayInitiativesFromInstrumentGet>
7882
| ActionType<typeof idpayInitiativesInstrumentEnroll>
7983
| ActionType<typeof idpayInitiativesInstrumentDelete>
8084
| ActionType<typeof idPayInitiativesFromInstrumentRefreshStart>
81-
| ActionType<typeof idPayInitiativesFromInstrumentRefreshStop>;
85+
| ActionType<typeof idPayInitiativesFromInstrumentRefreshStop>
86+
| ActionType<typeof setIdPayOnboardingSucceeded>;

ts/features/idpay/wallet/store/reducers/index.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ import {
1414
idPayInitiativesFromInstrumentGet,
1515
idPayWalletGet,
1616
idpayInitiativesInstrumentDelete,
17-
idpayInitiativesInstrumentEnroll
17+
idpayInitiativesInstrumentEnroll,
18+
setIdPayOnboardingSucceeded
1819
} from "../actions";
1920

2021
export type IdPayWalletState = {
@@ -27,12 +28,14 @@ export type IdPayWalletState = {
2728
// structure: {initiativeId: is waiting for response to pair/unpair api call}
2829
// this will be populated on selection and reset when not loading and
2930
// we have a response from BE
31+
onboardingSucceeded: boolean;
3032
};
3133

3234
const INITIAL_STATE: IdPayWalletState = {
3335
initiatives: pot.none,
3436
initiativesWithInstrument: pot.none,
35-
initiativesAwaitingStatusUpdate: {}
37+
initiativesAwaitingStatusUpdate: {},
38+
onboardingSucceeded: false
3639
};
3740

3841
const reducer = (
@@ -124,6 +127,11 @@ const reducer = (
124127
[action.payload.initiativeId]: false
125128
}
126129
};
130+
case getType(setIdPayOnboardingSucceeded):
131+
return {
132+
...state,
133+
onboardingSucceeded: action.payload
134+
};
127135
}
128136
return state;
129137
};
@@ -205,4 +213,9 @@ export const idPayInitiativeFromInstrumentPotSelector = (
205213
}
206214
);
207215

216+
export const isIdPayOnboardingSucceededSelector = createSelector(
217+
selectIdPayWallet,
218+
wallet => wallet.onboardingSucceeded
219+
);
220+
208221
export default reducer;

ts/features/payments/details/components/__tests__/WalletDetailsPaymentMethodFeatures.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const renderComponent = (paymentMethod: WalletInfo, isIdPayEnabled = false) => {
3232
idPay: {
3333
...store.getState().features.idPay,
3434
wallet: {
35+
onboardingSucceeded: false,
3536
initiativesAwaitingStatusUpdate: {},
3637
initiatives: pot.none,
3738
initiativesWithInstrument: pot.some({
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { Banner, IOToast, VSpacer } from "@pagopa/io-app-design-system";
2+
import * as pot from "@pagopa/ts-commons/lib/pot";
3+
import Animated, {
4+
FadeIn,
5+
FadeOut,
6+
LinearTransition
7+
} from "react-native-reanimated";
8+
import { useEffect } from "react";
9+
import { useIODispatch, useIOSelector } from "../../../../store/hooks";
10+
import { isIdPayEnabledSelector } from "../../../../store/reducers/backendStatus/remoteConfig";
11+
import { isIdPayOnboardingSucceededSelector } from "../../../idpay/wallet/store/reducers";
12+
import {
13+
isEmailEnabledSelector,
14+
profileSelector
15+
} from "../../../settings/common/store/selectors";
16+
import { profileUpsert } from "../../../settings/common/store/actions";
17+
import I18n from "../../../../i18n";
18+
import { usePrevious } from "../../../../utils/hooks/usePrevious";
19+
import { setIdPayOnboardingSucceeded } from "../../../idpay/wallet/store/actions";
20+
21+
export const EmailNotificationBanner = () => {
22+
const dispatch = useIODispatch();
23+
const isIdPayOnboardingSucceeded = useIOSelector(
24+
isIdPayOnboardingSucceededSelector
25+
);
26+
const isIdPayEnabled = useIOSelector(isIdPayEnabledSelector);
27+
const isEmailChannelEnabled = useIOSelector(isEmailEnabledSelector);
28+
const profile = useIOSelector(profileSelector);
29+
const prevProfile = usePrevious(profile);
30+
31+
const canShowBanner =
32+
isIdPayEnabled && isIdPayOnboardingSucceeded && !isEmailChannelEnabled;
33+
34+
const handleOnEnableEmailChannel = () => {
35+
dispatch(
36+
profileUpsert.request({
37+
is_email_enabled: true
38+
})
39+
);
40+
};
41+
42+
useEffect(() => {
43+
if (prevProfile && pot.isUpdating(prevProfile)) {
44+
if (pot.isError(profile)) {
45+
IOToast.error(I18n.t("global.genericError"));
46+
return;
47+
}
48+
if (pot.isSome(profile)) {
49+
dispatch(setIdPayOnboardingSucceeded(false));
50+
IOToast.hideAll();
51+
IOToast.success(
52+
I18n.t(
53+
"idpay.onboarding.preferences.enableEmailBanner.successOutcome"
54+
)
55+
);
56+
return;
57+
}
58+
}
59+
}, [profile, prevProfile, canShowBanner, dispatch]);
60+
61+
const handleOnCloseBanner = () => {
62+
dispatch(setIdPayOnboardingSucceeded(false));
63+
};
64+
65+
if (!canShowBanner) {
66+
return null;
67+
}
68+
69+
return (
70+
<Animated.View
71+
entering={FadeIn.duration(300)}
72+
exiting={FadeOut.duration(300)}
73+
layout={LinearTransition.duration(300)}
74+
>
75+
<VSpacer size={16} />
76+
<Banner
77+
labelClose={I18n.t("global.buttons.close")}
78+
onClose={handleOnCloseBanner}
79+
color="turquoise"
80+
pictogramName="emailDotNotif"
81+
title={I18n.t("idpay.onboarding.preferences.enableEmailBanner.title")}
82+
content={I18n.t(
83+
"idpay.onboarding.preferences.enableEmailBanner.content"
84+
)}
85+
action={I18n.t("idpay.onboarding.preferences.enableEmailBanner.cta")}
86+
onPress={handleOnEnableEmailChannel}
87+
/>
88+
</Animated.View>
89+
);
90+
};

ts/features/services/home/screens/ServicesHomeScreen.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ import {
1212
import { useFocusEffect } from "@react-navigation/native";
1313
import { useCallback, useEffect, useMemo } from "react";
1414
import { ListRenderItemInfo, View } from "react-native";
15-
import Animated, { useAnimatedRef } from "react-native-reanimated";
15+
import Animated, {
16+
LinearTransition,
17+
useAnimatedRef
18+
} from "react-native-reanimated";
1619
import { Institution } from "../../../../../definitions/services/Institution";
1720
import SectionStatusComponent from "../../../../components/SectionStatus";
1821
import { useHeaderFirstLevel } from "../../../../hooks/useHeaderFirstLevel";
@@ -31,6 +34,7 @@ import { FeaturedServiceList } from "../components/FeaturedServiceList";
3134
import { useInstitutionsFetcher } from "../hooks/useInstitutionsFetcher";
3235
import { useServicesHomeBottomSheet } from "../hooks/useServicesHomeBottomSheet";
3336
import { featuredInstitutionsGet, featuredServicesGet } from "../store/actions";
37+
import { EmailNotificationBanner } from "../components/EmailNotificationBanner";
3438

3539
export const ServicesHomeScreen = () => {
3640
const dispatch = useIODispatch();
@@ -96,9 +100,12 @@ export const ServicesHomeScreen = () => {
96100
}
97101
}}
98102
/>
99-
<FeaturedServiceList />
100-
<FeaturedInstitutionList />
101-
<ListItemHeader label={I18n.t("services.home.institutions.title")} />
103+
<EmailNotificationBanner />
104+
<Animated.View layout={LinearTransition.duration(300)}>
105+
<FeaturedServiceList />
106+
<FeaturedInstitutionList />
107+
<ListItemHeader label={I18n.t("services.home.institutions.title")} />
108+
</Animated.View>
102109
</>
103110
),
104111
[navigateToSearch]

yarn.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17849,12 +17849,12 @@ __metadata:
1784917849
linkType: hard
1785017850

1785117851
"react-native-safe-area-context@npm:^5.4.1":
17852-
version: 5.5.0
17853-
resolution: "react-native-safe-area-context@npm:5.5.0"
17852+
version: 5.5.1
17853+
resolution: "react-native-safe-area-context@npm:5.5.1"
1785417854
peerDependencies:
1785517855
react: "*"
1785617856
react-native: "*"
17857-
checksum: e04fd9e9f52cbc73f495c4ef7ad527ec48398b17032cbbba412bd72d8b2c9a7b184b57e8e8a72edc3e6c41832442596dafbfee7d29ca9d1dd85081991dd834f5
17857+
checksum: 57d27eac29288a906e97055512cde656369fe3bdc2bc9f26ca078bd0b340e5535d7619d6bcc0f10b68630fe7e721099fcd64d15eb8101b146ddb660ae36e1d64
1785817858
languageName: node
1785917859
linkType: hard
1786017860

0 commit comments

Comments
 (0)