diff --git a/.changeset/curvy-parents-push.md b/.changeset/curvy-parents-push.md new file mode 100644 index 000000000000..d10108d03b1b --- /dev/null +++ b/.changeset/curvy-parents-push.md @@ -0,0 +1,5 @@ +--- +"ledger-live-desktop": minor +--- + +Support anonymous user based braze campaign notification management. diff --git a/apps/ledger-live-desktop/src/newArch/features/DynamicContent/components/LogContentCardWrapper.tsx b/apps/ledger-live-desktop/src/newArch/features/DynamicContent/components/LogContentCardWrapper.tsx index 8ae23cfb7e1c..e0a5a0bfb6aa 100644 --- a/apps/ledger-live-desktop/src/newArch/features/DynamicContent/components/LogContentCardWrapper.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/DynamicContent/components/LogContentCardWrapper.tsx @@ -1,9 +1,13 @@ import React, { useRef, useEffect, useMemo } from "react"; import * as braze from "@braze/web-sdk"; -import { useSelector } from "react-redux"; -import { trackingEnabledSelector } from "~/renderer/reducers/settings"; +import { useDispatch, useSelector } from "react-redux"; +import { + anonymousUserNotificationsSelector, + trackingEnabledSelector, +} from "~/renderer/reducers/settings"; import { track } from "~/renderer/analytics/segment"; import { Box } from "@ledgerhq/react-ui"; +import { updateAnonymousUserNotifications } from "~/renderer/actions/settings"; interface LogContentCardWrapperProps { id: string; @@ -12,6 +16,7 @@ interface LogContentCardWrapperProps { } const PERCENTAGE_OF_CARD_VISIBLE = 0.5; +const OFFLINE_SEEN_DELAY = 3000; const LogContentCardWrapper: React.FC = ({ id, @@ -20,6 +25,8 @@ const LogContentCardWrapper: React.FC = ({ }) => { const ref = useRef(null); const isTrackedUser = useSelector(trackingEnabledSelector); + const anonymousUserNotifications = useSelector(anonymousUserNotificationsSelector); + const dispatch = useDispatch(); const currentCard = useMemo(() => { const cards = braze.getCachedContentCards().cards; @@ -27,17 +34,33 @@ const LogContentCardWrapper: React.FC = ({ }, [id]); useEffect(() => { - if (!currentCard || !isTrackedUser) return; + if (!currentCard) return; const intersectionObserver = new IntersectionObserver( ([entry]) => { if (entry.intersectionRatio > PERCENTAGE_OF_CARD_VISIBLE) { - braze.logContentCardImpressions([currentCard]); - track("contentcard_impression", { - id: currentCard.id, - ...currentCard.extras, - ...additionalProps, - }); + console.log("hereee"); + if (isTrackedUser) { + braze.logContentCardImpressions([currentCard]); + track("contentcard_impression", { + id: currentCard.id, + ...currentCard.extras, + ...additionalProps, + }); + } else if ( + anonymousUserNotifications[currentCard.id as string] !== + currentCard?.expiresAt?.getTime() + ) { + // support new campaign or resumed campaign with the same id + different expiration date targeting anonymous users + setTimeout(() => { + dispatch( + updateAnonymousUserNotifications({ + id: currentCard.id as string, + expiresAt: currentCard?.expiresAt?.getTime() as number, + }), + ); + }, OFFLINE_SEEN_DELAY); + } } }, { threshold: PERCENTAGE_OF_CARD_VISIBLE }, @@ -54,7 +77,7 @@ const LogContentCardWrapper: React.FC = ({ intersectionObserver.unobserve(currentRef); } }; - }, [currentCard, isTrackedUser, additionalProps]); + }, [currentCard, isTrackedUser, additionalProps, dispatch, anonymousUserNotifications]); return ( diff --git a/apps/ledger-live-desktop/src/renderer/actions/constants.ts b/apps/ledger-live-desktop/src/renderer/actions/constants.ts index 09cf07655e5a..33fa74b72708 100644 --- a/apps/ledger-live-desktop/src/renderer/actions/constants.ts +++ b/apps/ledger-live-desktop/src/renderer/actions/constants.ts @@ -5,3 +5,4 @@ export const TOGGLE_MEMOTAG_INFO = "settings/toggleShouldDisplayMemoTagInfo"; export const TOGGLE_MEV = "settings/toggleMEV"; export const TOGGLE_MARKET_WIDGET = "settings/toggleMarketWidget"; export const UPDATE_NFT_COLLECTION_STATUS = "settings/updateNftCollectionStatus"; +export const UPDATE_ANONYMOUS_USER_NOTIFICATIONS = "settings/updateAnonymousUserNotifications"; diff --git a/apps/ledger-live-desktop/src/renderer/actions/settings.ts b/apps/ledger-live-desktop/src/renderer/actions/settings.ts index be1df1bef74c..94c6ab834a0b 100644 --- a/apps/ledger-live-desktop/src/renderer/actions/settings.ts +++ b/apps/ledger-live-desktop/src/renderer/actions/settings.ts @@ -29,6 +29,7 @@ import { TOGGLE_MARKET_WIDGET, TOGGLE_MEMOTAG_INFO, TOGGLE_MEV, + UPDATE_ANONYMOUS_USER_NOTIFICATIONS, UPDATE_NFT_COLLECTION_STATUS, } from "./constants"; import { BlockchainsType } from "@ledgerhq/live-nft/supported"; @@ -454,3 +455,10 @@ export const toggleShouldDisplayMemoTagInfo = (payload: boolean) => { payload, }; }; + +export const updateAnonymousUserNotifications = (payload: { id: string; expiresAt: number }) => { + return { + type: UPDATE_ANONYMOUS_USER_NOTIFICATIONS, + payload, + }; +}; diff --git a/apps/ledger-live-desktop/src/renderer/components/TopBar/NotificationIndicator/AnnouncementPanel.tsx b/apps/ledger-live-desktop/src/renderer/components/TopBar/NotificationIndicator/AnnouncementPanel.tsx index 4a26d6a32dbc..53e6fa645018 100644 --- a/apps/ledger-live-desktop/src/renderer/components/TopBar/NotificationIndicator/AnnouncementPanel.tsx +++ b/apps/ledger-live-desktop/src/renderer/components/TopBar/NotificationIndicator/AnnouncementPanel.tsx @@ -244,7 +244,6 @@ const Separator = styled.div` export function AnnouncementPanel() { const { notificationsCards, groupNotifications, onClickNotif } = useNotifications(); - const groups = useMemo( () => groupNotifications(notificationsCards), // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/apps/ledger-live-desktop/src/renderer/reducers/dynamicContent.ts b/apps/ledger-live-desktop/src/renderer/reducers/dynamicContent.ts index 8cdc27dbab2b..88b4364646de 100644 --- a/apps/ledger-live-desktop/src/renderer/reducers/dynamicContent.ts +++ b/apps/ledger-live-desktop/src/renderer/reducers/dynamicContent.ts @@ -5,6 +5,7 @@ import { PortfolioContentCard, } from "~/types/dynamicContent"; import { Handlers } from "./types"; +import { SettingsState } from "./settings"; export type DynamicContentState = { portfolioCards: PortfolioContentCard[]; @@ -61,8 +62,14 @@ export const portfolioContentCardSelector = (state: { dynamicContent: DynamicCon export const actionContentCardSelector = (state: { dynamicContent: DynamicContentState }) => state.dynamicContent.actionCards; -export const notificationsContentCardSelector = (state: { dynamicContent: DynamicContentState }) => - state.dynamicContent.notificationsCards; +export const notificationsContentCardSelector = (state: { + dynamicContent: DynamicContentState; + settings: SettingsState; +}) => + state.dynamicContent.notificationsCards.map(n => ({ + ...n, + viewed: !!state.settings.anonymousUserNotifications[n.id], + })); // Exporting reducer diff --git a/apps/ledger-live-desktop/src/renderer/reducers/settings.ts b/apps/ledger-live-desktop/src/renderer/reducers/settings.ts index 83817274d815..27c5b9dde54f 100644 --- a/apps/ledger-live-desktop/src/renderer/reducers/settings.ts +++ b/apps/ledger-live-desktop/src/renderer/reducers/settings.ts @@ -36,6 +36,7 @@ import { TOGGLE_MARKET_WIDGET, TOGGLE_MEV, UPDATE_NFT_COLLECTION_STATUS, + UPDATE_ANONYMOUS_USER_NOTIFICATIONS, } from "../actions/constants"; import { BlockchainsType, SupportedBlockchainsType } from "@ledgerhq/live-nft/supported"; import { NftStatus } from "@ledgerhq/live-nft/types"; @@ -134,6 +135,7 @@ export type SettingsState = { onboardingUseCase: OnboardingUseCase | null; lastOnboardedDevice: Device | null; alwaysShowMemoTagInfo: boolean; + anonymousUserNotifications: Record; }; export const getInitialLanguageAndLocale = (): { language: Language; locale: Locale } => { @@ -239,6 +241,7 @@ export const INITIAL_STATE: SettingsState = { onboardingUseCase: null, lastOnboardedDevice: null, alwaysShowMemoTagInfo: true, + anonymousUserNotifications: {}, }; /* Handlers */ @@ -311,6 +314,7 @@ type HandlersPayloads = { [TOGGLE_MEV]: boolean; [TOGGLE_MEMOTAG_INFO]: boolean; [TOGGLE_MARKET_WIDGET]: boolean; + [UPDATE_ANONYMOUS_USER_NOTIFICATIONS]: Record; }; type SettingsHandlers = Handlers; @@ -556,6 +560,13 @@ const handlers: SettingsHandlers = { ...state, alwaysShowMemoTagInfo: payload, }), + [UPDATE_ANONYMOUS_USER_NOTIFICATIONS]: (state: SettingsState, { payload }) => ({ + ...state, + anonymousUserNotifications: { + ...state.anonymousUserNotifications, + [payload.id]: payload.expiresAt, + }, + }), }; export default handleActions( @@ -912,3 +923,5 @@ export const marketPerformanceWidgetSelector = (state: State) => export const alwaysShowMemoTagInfoSelector = (state: State) => state.settings.alwaysShowMemoTagInfo; export const nftCollectionsStatusByNetworkSelector = (state: State) => state.settings.nftCollectionsStatusByNetwork; +export const anonymousUserNotificationsSelector = (state: State) => + state.settings.anonymousUserNotifications;