diff --git a/src/common/Common.utils.tsx b/src/common/Common.utils.tsx index 7b33acc08f..0101a9e700 100644 --- a/src/common/Common.utils.tsx +++ b/src/common/Common.utils.tsx @@ -70,3 +70,17 @@ export function convertTimeStamp(timestamp: string) { return date.format('hh:mm A'); } } + +export const updateNotifCount = ( + setNavigationSetup: (value: any) => void, + listName: string, + itemId: string, + newCount: number +) => { + setNavigationSetup((prev: any) => ({ + ...prev, + [listName]: prev[listName].map((item: any) => + item.id === itemId ? { ...item, data: { ...item.data, count: newCount } } : item + ), + })); +}; diff --git a/src/common/hooks/useInAppNotifications.tsx b/src/common/hooks/useInAppNotifications.tsx index 2f58a684ab..6cf6ee1a28 100644 --- a/src/common/hooks/useInAppNotifications.tsx +++ b/src/common/hooks/useInAppNotifications.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useContext, useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; import { CONSTANTS, NotificationEvent } from '@pushprotocol/restapi'; @@ -7,6 +7,7 @@ import { deviceSizes, notification } from 'blocks'; import { InAppChannelNotifications, InAppChatNotifications } from 'common'; import { useDeviceWidthCheck } from 'hooks'; +import { AppContext } from 'contexts/AppContext'; export const useInAppNotifications = () => { const [isStreamConnected, setIsStreamConnected] = useState(false); @@ -15,6 +16,8 @@ export const useInAppNotifications = () => { const { userPushSDKInstance } = useSelector((state: any) => { return state.user; }); + const { setNewChatsCount, setNewNotifsCount } = useContext(AppContext); + const attachListeners = async () => { userPushSDKInstance?.stream?.on(CONSTANTS.STREAM.CONNECT, (err: Error) => { console.debug( @@ -43,7 +46,8 @@ export const useInAppNotifications = () => { userPushSDKInstance?.stream?.uid, userPushSDKInstance?.stream ); - if (data.source != 'PUSH_CHAT') + if (data.source != 'PUSH_CHAT') { + setNewNotifsCount((prev: number) => prev + 1); notification.show({ overlay: , position: isMobile ? 'top-center' : 'bottom-right', @@ -52,6 +56,7 @@ export const useInAppNotifications = () => { notification.hide(); }, }); + } }); userPushSDKInstance?.stream?.on(CONSTANTS.STREAM.CHAT, (data: any) => { console.debug( @@ -75,6 +80,7 @@ export const useInAppNotifications = () => { updatedMessages[data.chatId].push(data); } setNewMessages(updatedMessages); + setNewChatsCount((prev: number) => prev + 1); notification.show( { overlay: ( diff --git a/src/components/MobileNavButton.jsx b/src/components/MobileNavButton.jsx index 65119166a8..a5f9cfad32 100644 --- a/src/components/MobileNavButton.jsx +++ b/src/components/MobileNavButton.jsx @@ -153,6 +153,8 @@ function MobileNavButton({ item, data, sectionID, active, bg = 'none', showNavBa {data.name} + {!!data?.count && {data?.count}} + {data?.showNewTag && New} {item.hasItems && !item.opened && } @@ -226,5 +228,22 @@ const NewTag = styled(SpanV2)` width: fit-content; `; +const NewTag2 = styled(SpanV2)` + display: flex; + max-width: 20px; + height: 16px; + padding: 0px 8px; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 7.778px; + flex-shrink: 0; + border-radius: 22px; + background: #E20880; + font-weight: 500; + line-height: 16px; + font-size: 12px; +`; + // Export Default export default MobileNavButton; diff --git a/src/components/NavigationButton.jsx b/src/components/NavigationButton.jsx index 01f3a1297f..56c21708f9 100644 --- a/src/components/NavigationButton.jsx +++ b/src/components/NavigationButton.jsx @@ -19,6 +19,7 @@ import { GlobalContext } from 'contexts/GlobalContext'; // Assets import { navigationIcons } from 'assets/navigation'; +import { Text } from 'blocks'; // Interface @@ -158,6 +159,7 @@ function NavigationButton({ item, data, sectionID, active, bg = 'none' }) { {data.name} )} + {!!data?.count && {data?.count}} {data?.showNewTag && !sidebarCollapsed && New} @@ -252,6 +254,20 @@ const NewTag = styled(SpanV2)` width: fit-content; `; +const NewTag2 = styled(SpanV2)` + display: flex; + max-width: 20px; + height: 16px; + padding: 1px 8px 0px 8px; + justify-content: center; + align-items: center; + border-radius: 22px; + background: #E20880; + font-weight: 500; + line-height: 1; + font-size: 12px; +`; + const ProtectedRoute = styled(SpanV2)``; // Export Default diff --git a/src/config/NavigationList.js b/src/config/NavigationList.js index f1d92f7d28..5e462eeaae 100644 --- a/src/config/NavigationList.js +++ b/src/config/NavigationList.js @@ -49,6 +49,7 @@ const NavigationList = { hasMenuLogic: true, loading: false, hidden: false, + count: 0, headerTag: { title: 'Inbox', light: { @@ -103,6 +104,7 @@ const NavigationList = { hasMenuLogic: true, hidden: false, // allowReadOnly: false, + count: 0, headerTag: { title: 'Chat', light: { diff --git a/src/contexts/AppContext.tsx b/src/contexts/AppContext.tsx index 1ecc055873..115ee0e2b2 100644 --- a/src/contexts/AppContext.tsx +++ b/src/contexts/AppContext.tsx @@ -56,6 +56,8 @@ const AppContextProvider = ({ children }: { children: ReactNode }) => { title: null, }); const [displayQR, setDisplayQR] = useState(false); + const [newChatsCount, setNewChatsCount] = useState(0); + const [newNotifsCount, setNewNotifsCount] = useState(0); const { userPushSDKInstance } = useSelector((state: any) => { return state.user; @@ -554,6 +556,10 @@ const AppContextProvider = ({ children }: { children: ReactNode }) => { storePGPKeyForUser, isUserProfileUnlocked, setUserProfileUnlocked, + newChatsCount, + setNewChatsCount, + newNotifsCount, + setNewNotifsCount, }} > {children} diff --git a/src/modules/inbox/InboxModule.tsx b/src/modules/inbox/InboxModule.tsx index 6a17b3996a..cb31dc9648 100644 --- a/src/modules/inbox/InboxModule.tsx +++ b/src/modules/inbox/InboxModule.tsx @@ -1,6 +1,6 @@ // React + Web3 Essentials import { ethers } from 'ethers'; -import React from 'react'; +import React, { useContext, useEffect } from 'react'; // External Packages import ReactGA from 'react-ga'; @@ -22,6 +22,7 @@ import UsersDataStore from 'singletons/UsersDataStore'; import APP_PATHS from 'config/AppPaths'; import GLOBALS, { device, globalsMargin } from 'config/Globals'; import { CHAIN_DETAILS, abis, addresses, appConfig } from 'config/index.js'; +import { AppContext } from 'contexts/AppContext'; // Constants export const ALLOWED_CORE_NETWORK = appConfig.coreContractChain; @@ -34,6 +35,7 @@ const InboxModule = ({ isSpam }) => { const dispatch = useDispatch(); const { account, chainId, provider } = useAccount(); const { epnsReadProvider, epnsCommReadProvider } = useSelector((state) => state.contracts); + const { setNewNotifsCount, newNotifsCount } = useContext(AppContext); // toast related section const [toast, showToast] = React.useState(null); @@ -44,6 +46,10 @@ const InboxModule = ({ isSpam }) => { const themes = useTheme(); const onCoreNetwork = ALLOWED_CORE_NETWORK === chainId; + useEffect(() => { + setNewNotifsCount(0); + }, [newNotifsCount]); + //clear toast variable after it is shown React.useEffect(() => { if (toast) { diff --git a/src/sections/chat/ChatSection.tsx b/src/sections/chat/ChatSection.tsx index f553ee589d..10e1885dad 100644 --- a/src/sections/chat/ChatSection.tsx +++ b/src/sections/chat/ChatSection.tsx @@ -1,5 +1,5 @@ // React + Web3 Essentials -import React, { useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; // External Packages @@ -20,6 +20,7 @@ import { device } from 'config/Globals'; import Back from 'assets/chat/backchat.svg?react'; import UnlockProfileWrapper from 'components/chat/unlockProfile/UnlockProfileWrapper'; import { MODAL_POSITION } from 'hooks/useModalBlur'; +import { AppContext } from 'contexts/AppContext'; // Interface interface IntroContainerProps { @@ -38,6 +39,7 @@ const ChatSection = ({ chatId, setChatId, loggedIn }) => { const { userPushSDKInstance } = useSelector((state: any) => { return state.user; }); + const { setNewChatsCount, newChatsCount } = useContext(AppContext); // Get Theme const theme = useTheme(); @@ -48,6 +50,10 @@ const ChatSection = ({ chatId, setChatId, loggedIn }) => { // State to control the visibility of the unlock profile modal const [visible, setVisible] = useState(true); + useEffect(() => { + setNewChatsCount(0); + }, [newChatsCount]); + // RENDER return ( // If user is not logged in then show chat and unlock profile @@ -138,7 +144,7 @@ const ChatViewContainer = styled(ItemVV2)` overflow: hidden; `; -const IntroContainer = styled(ItemVV2) ` +const IntroContainer = styled(ItemVV2)` flex: 1; height: inherit; background: ${(props) => props.bg || 'transparent'}; diff --git a/src/structure/MobileNavigation.tsx b/src/structure/MobileNavigation.tsx index 6a942c7a17..fc948b7b56 100644 --- a/src/structure/MobileNavigation.tsx +++ b/src/structure/MobileNavigation.tsx @@ -24,6 +24,8 @@ import { appConfig } from 'config/index.js'; import useFetchChannelDetails from 'common/hooks/useFetchUsersChannelDetails'; import { convertAddressToAddrCaip } from 'helpers/CaipHelper'; import APP_PATHS from 'config/AppPaths'; +import { AppContext } from 'contexts/AppContext'; +import { updateNotifCount } from 'common'; // Create Header function MobileNavigation({ showNavBar, setShowNavBar }) { @@ -35,6 +37,7 @@ function MobileNavigation({ showNavBar, setShowNavBar }) { const { processingState } = useSelector((state: any) => state.channelCreation); const { run, stepIndex, isCommunicateOpen, isDeveloperOpen } = useSelector((state: any) => state.userJourney); const { navigationSetup, setNavigationSetup } = useContext(NavigationContext); + const { newChatsCount, newNotifsCount } = useContext(AppContext); const CORE_CHAIN_ID = appConfig.coreContractChain; const { account, chainId } = useAccount(); @@ -640,6 +643,18 @@ function MobileNavigation({ showNavBar, setShowNavBar }) { return rendered; }; + useEffect(() => { + if (navigationSetup) { + updateNotifCount(setNavigationSetup, 'notificationList', '2_inbox', newNotifsCount); + } + }, [newNotifsCount]); + + useEffect(() => { + if (navigationSetup) { + updateNotifCount(setNavigationSetup, 'messagingList', '3_chat', newChatsCount); + } + }, [newChatsCount]); + return ( state.admin); const [refresh, setRefresh] = useState(false); const { processingState } = useSelector((state: any) => state.channelCreation); - const { run, stepIndex, isCommunicateOpen, isDeveloperOpen } = useSelector((state: any) => state.userJourney); + const { run, stepIndex } = useSelector((state: any) => state.userJourney); const { navigationSetup, setNavigationSetup } = useContext(NavigationContext); const { sidebarCollapsed, setSidebarCollapsed } = useContext(GlobalContext); + const { newChatsCount, newNotifsCount } = useContext(AppContext); const CORE_CHAIN_ID = appConfig.coreContractChain; const { account, chainId } = useAccount(); @@ -677,7 +679,6 @@ function Navigation() { /> )} - {/* { section.hasItems ? renderChildItems( @@ -770,6 +771,18 @@ function Navigation() { return rendered; }; + useEffect(() => { + if (navigationSetup) { + updateNotifCount(setNavigationSetup, 'notificationList', '2_inbox', newNotifsCount); + } + }, [newNotifsCount]); + + useEffect(() => { + if (navigationSetup) { + updateNotifCount(setNavigationSetup, 'messagingList', '3_chat', newChatsCount); + } + }, [newChatsCount]); + return ( Promise; isUserProfileUnlocked: boolean; setUserProfileUnlocked: (isUserProfileUnlocked: boolean) => void; + newChatsCount: number; + setNewChatsCount: React.Dispatch>; + newNotifsCount: number; + setNewNotifsCount: React.Dispatch>; }