From ac48cb1e39e8f154b01ed1b6b0431a27bb80a9d6 Mon Sep 17 00:00:00 2001 From: marcoelissa <marco.elissa@gmail.com> Date: Thu, 15 Dec 2022 05:46:42 +0700 Subject: [PATCH] useLocalStorage to store notifications --- src/@context/Orbis.tsx | 139 ++++++++++++------ .../Orbis/DirectMessages/Conversation.tsx | 15 +- .../Orbis/DirectMessages/Header.module.css | 5 + .../@shared/Orbis/DirectMessages/Header.tsx | 13 +- .../@shared/Orbis/DirectMessages/List.tsx | 7 +- .../@shared/Orbis/DirectMessages/ListItem.tsx | 20 +-- 6 files changed, 128 insertions(+), 71 deletions(-) diff --git a/src/@context/Orbis.tsx b/src/@context/Orbis.tsx index 383b29451..76e5f1de5 100644 --- a/src/@context/Orbis.tsx +++ b/src/@context/Orbis.tsx @@ -25,7 +25,7 @@ type IOrbisProvider = { conversationId: string conversations: IOrbisConversation[] conversationTitle: string - unreadMessages: IOrbisNotification[] + notifications: Record<string, string[]> connectOrbis: (options: { address: string lit?: boolean @@ -44,6 +44,8 @@ type IOrbisProvider = { setOpenConversations: (value: boolean) => void setConversationId: (value: string) => void createConversation: (value: string) => Promise<void> + clearMessageNotifs: (conversationId: string) => void + getConversationTitle: (conversation: IOrbisConversation) => string getDid: (value: string) => Promise<string> } @@ -56,26 +58,27 @@ const CONVERSATION_CONTEXT = 'ocean_market' // Can be changed to whatever function OrbisProvider({ children }: { children: ReactNode }): ReactElement { const { web3Provider, accountId } = useWeb3() const prevAccountId = usePrevious(accountId) + const [ceramicSessions, setCeramicSessions] = useLocalStorage<string[]>( 'ocean-ceramic-sessions', [] ) + const [usersNotifications, setUsersNotifications] = useLocalStorage< + Record<string, Record<string, string[]>> + >('ocean-convo-notifs', {}) + const [account, setAccount] = useState<IOrbisProfile | null>(null) const [hasLit, setHasLit] = useState(false) const [openConversations, setOpenConversations] = useState(false) const [conversationId, setConversationId] = useState(null) const [conversations, setConversations] = useState<IOrbisConversation[]>([]) - const [unreadMessages, setUnreadMessages] = useState<IOrbisNotification[]>([]) // Function to reset states const resetStates = () => { setAccount(null) setConversationId(null) setConversations([]) - setUnreadMessages([]) setHasLit(false) - window.localStorage.removeItem('lit-auth-signature') - window.localStorage.removeItem('lit-auth-sol-signature') } // Remove ceramic session @@ -85,6 +88,12 @@ function OrbisProvider({ children }: { children: ReactNode }): ReactElement { setCeramicSessions({ ..._ceramicSessions }) } + // Remove lit signature + const removeLitSignature = () => { + window.localStorage.removeItem('lit-auth-signature') + window.localStorage.removeItem('lit-auth-sol-signature') + } + // Connecting to Orbis const connectOrbis = async ({ address, @@ -117,7 +126,9 @@ function OrbisProvider({ children }: { children: ReactNode }): ReactElement { const disconnectOrbis = (address: string) => { const res = orbis.logout() if (res.status === 200) { + console.log('disconnected') resetStates() + removeLitSignature() removeCeramicSession(address) } } @@ -143,7 +154,6 @@ function OrbisProvider({ children }: { children: ReactNode }): ReactElement { res.status === 200 && didToAddress(res.did) === accountId.toLowerCase() ) { - console.log(res) setHasLit(res.details.hasLit) const { data } = await orbis.getProfile(res.did) setAccount(data) @@ -152,7 +162,9 @@ function OrbisProvider({ children }: { children: ReactNode }): ReactElement { const data = await connectOrbis({ address, lit }) return data } else { + console.log('not connected') resetStates() + removeLitSignature() removeCeramicSession(address) return null } @@ -264,58 +276,100 @@ function OrbisProvider({ children }: { children: ReactNode }): ReactElement { did = await getDid(accountId) } + const address = didToAddress(did) + const { data, error } = await orbis.api.rpc('orbis_f_notifications', { user_did: did || 'none', notif_type: 'messages' }) - if (error) { - setUnreadMessages([]) - } else if (data.length > 0) { - // Check if did is mainnet - const chainId = parseInt(did.split(':')[3]) - if (chainId === 0) { - setUnreadMessages(data) - } else { - const _unreads = data.filter((o: IOrbisNotification) => { - return o.status === 'new' - }) - setUnreadMessages(_unreads) + if (!error && data.length > 0) { + const _usersNotifications = { ...usersNotifications } + const _unreads = data.filter((o: IOrbisNotification) => { + return o.status === 'new' + }) + _unreads.forEach((o: IOrbisNotification) => { + const conversationId = o.content.conversation_id + const { encryptedString } = o.content.encryptedMessage + + // Add address if not exists + if (!_usersNotifications[address]) _usersNotifications[address] = {} + + // Add conversationId if not exists + if (!_usersNotifications[address][conversationId]) + _usersNotifications[address][conversationId] = [] + + // Add encryptedString if not exists + if ( + !_usersNotifications[address][conversationId].includes( + encryptedString + ) + ) { + _usersNotifications[address][conversationId].push(encryptedString) + } + }) + setUsersNotifications(_usersNotifications) + + if (_unreads.length > 0) { + // Get unix timestamp in seconds + const timestamp = Math.floor(Date.now() / 1000) + // Set read time + await orbis.setNotificationsReadTime('messages', timestamp) } } } + const clearMessageNotifs = (conversationId: string) => { + console.log('clearMessageNotifs', conversationId) + const _usersNotifications = { ...usersNotifications } + const address = didToAddress(account?.did) + if (_usersNotifications[address]) { + delete _usersNotifications[address][conversationId] + } + setUsersNotifications(_usersNotifications) + } + + const getConversationTitle = (conversation: IOrbisConversation) => { + let title = null + + if (conversation) { + const details = conversation.recipients_details.find( + (o: IOrbisProfile) => o.did !== account.did + ) + const did = conversation.recipients.find((o: string) => o !== account.did) + + const address = didToAddress(did) + + if (details) { + title = details?.metadata?.ensName || accountTruncate(address) + } else { + title = accountTruncate(address) + } + } + + return title + } + const conversationTitle = useMemo(() => { let title = null if (conversationId && conversations.length) { const conversation = conversations.find( (o) => o.stream_id === conversationId ) - if (conversation) { - const details = conversation.recipients_details.find( - (o: IOrbisProfile) => o.did !== account.did - ) - const did = conversation.recipients.find( - (o: string) => o !== account.did - ) - - const address = didToAddress(did) - - console.log({ details, address }) - - if (details) { - title = - details?.metadata?.ensName || - details?.profile?.username || - accountTruncate(address) - } else { - title = accountTruncate(address) - } - } + title = getConversationTitle(conversation) } return title - }, [conversationId, account, conversations]) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [conversationId, conversations]) + + const notifications = useMemo(() => { + let _notifications = {} + if (accountId && accountId.toLowerCase() in usersNotifications) { + _notifications = usersNotifications[accountId.toLowerCase()] + } + return _notifications + }, [accountId, usersNotifications]) useInterval(async () => { await getMessageNotifs() @@ -335,6 +389,7 @@ function OrbisProvider({ children }: { children: ReactNode }): ReactElement { useEffect(() => { if (account) { getConversations(account?.did) + getMessageNotifs() } // eslint-disable-next-line react-hooks/exhaustive-deps }, [account]) @@ -349,7 +404,7 @@ function OrbisProvider({ children }: { children: ReactNode }): ReactElement { conversationId, conversations, conversationTitle, - unreadMessages, + notifications, connectOrbis, disconnectOrbis, checkOrbisConnection, @@ -357,6 +412,8 @@ function OrbisProvider({ children }: { children: ReactNode }): ReactElement { setOpenConversations, setConversationId, createConversation, + clearMessageNotifs, + getConversationTitle, getDid }} > diff --git a/src/components/@shared/Orbis/DirectMessages/Conversation.tsx b/src/components/@shared/Orbis/DirectMessages/Conversation.tsx index 2a4f352e9..8cc3d4606 100644 --- a/src/components/@shared/Orbis/DirectMessages/Conversation.tsx +++ b/src/components/@shared/Orbis/DirectMessages/Conversation.tsx @@ -10,7 +10,14 @@ import Postbox from './Postbox' import styles from './Conversation.module.css' export default function DmConversation() { - const { orbis, account, conversationId, hasLit, connectLit } = useOrbis() + const { + orbis, + account, + conversationId, + hasLit, + connectLit, + clearMessageNotifs + } = useOrbis() const isMounted = useIsMounted() const messagesWrapper = useRef(null) @@ -47,7 +54,10 @@ export default function DmConversation() { setHasMore(data.length >= 50) const _messages = [...data, ...messages] setMessages(_messages) - if (currentPage === 0) scrollToBottom() + if (currentPage === 0) { + clearMessageNotifs(conversationId) + scrollToBottom() + } setCurrentPage((prev) => prev + 1) } else { const unique = data.filter( @@ -97,6 +107,7 @@ export default function DmConversation() { Math.ceil(el.scrollTop) >= Math.floor(el.scrollHeight - el.offsetHeight) ) { setNewMessages(0) + clearMessageNotifs(conversationId) } // Remove scroll listener diff --git a/src/components/@shared/Orbis/DirectMessages/Header.module.css b/src/components/@shared/Orbis/DirectMessages/Header.module.css index 3ff54375d..12356d785 100644 --- a/src/components/@shared/Orbis/DirectMessages/Header.module.css +++ b/src/components/@shared/Orbis/DirectMessages/Header.module.css @@ -11,6 +11,10 @@ cursor: pointer; } +.header > * { + pointer-events: none; +} + .icon { width: 1.5rem; fill: var(--font-color-text); @@ -28,6 +32,7 @@ display: flex; align-items: center; justify-content: center; + pointer-events: auto; } .btnBack:hover { diff --git a/src/components/@shared/Orbis/DirectMessages/Header.tsx b/src/components/@shared/Orbis/DirectMessages/Header.tsx index 6c626ca49..f219d885f 100644 --- a/src/components/@shared/Orbis/DirectMessages/Header.tsx +++ b/src/components/@shared/Orbis/DirectMessages/Header.tsx @@ -10,14 +10,17 @@ export default function Header() { conversationId, openConversations, conversationTitle, - unreadMessages, + notifications, setOpenConversations, setConversationId } = useOrbis() - const handleToggle = (e: any) => { + const handleToggle = ( + e: React.MouseEvent<HTMLDivElement | HTMLButtonElement> + ) => { e.preventDefault() - if (e.target.type === 'button') { + const target = e.target as HTMLElement + if (target.tagName === 'BUTTON') { setConversationId(null) } else { setOpenConversations(!openConversations) @@ -32,9 +35,9 @@ export default function Header() { <ChatBubble role="img" aria-label="Chat" className={styles.icon} /> </div> <span>Direct Messages</span> - {unreadMessages.length > 0 && ( + {Object.values(notifications).flat().length > 0 && ( <span className={styles.notificationCount}> - {unreadMessages.length} + {Object.values(notifications).flat().length} </span> )} </> diff --git a/src/components/@shared/Orbis/DirectMessages/List.tsx b/src/components/@shared/Orbis/DirectMessages/List.tsx index 22b446d9a..d3431150d 100644 --- a/src/components/@shared/Orbis/DirectMessages/List.tsx +++ b/src/components/@shared/Orbis/DirectMessages/List.tsx @@ -5,13 +5,10 @@ import ChatBubble from '@images/chatbubble.svg' import styles from './List.module.css' export default function List() { - const { conversations, unreadMessages, setConversationId } = useOrbis() + const { conversations, notifications, setConversationId } = useOrbis() const getConversationUnreads = (conversationId: string) => { - const _unreads = unreadMessages.filter( - (o) => o.content.conversation_id === conversationId - ) - return _unreads.length + return notifications[conversationId]?.length || 0 } return ( diff --git a/src/components/@shared/Orbis/DirectMessages/ListItem.tsx b/src/components/@shared/Orbis/DirectMessages/ListItem.tsx index 6e18d944e..7e1670493 100644 --- a/src/components/@shared/Orbis/DirectMessages/ListItem.tsx +++ b/src/components/@shared/Orbis/DirectMessages/ListItem.tsx @@ -1,7 +1,6 @@ import React, { useState, useEffect } from 'react' import { useCancelToken } from '@hooks/useCancelToken' import { useOrbis } from '@context/Orbis' -import { accountTruncate } from '@utils/web3' import { didToAddress } from '@utils/orbis' import Avatar from '@shared/atoms/Avatar' import Time from '@shared/atoms/Time' @@ -16,34 +15,19 @@ export default function ConversationItem({ unreads: number setConversationId: (value: string) => void }) { - const { account } = useOrbis() + const { account, getConversationTitle } = useOrbis() const newCancelToken = useCancelToken() - const [name, setName] = useState(null) + const name = getConversationTitle(conversation) const [address, setAddress] = useState(null) useEffect(() => { const getProfile = async () => { - const details = conversation.recipients_details.find( - (o) => o.did !== account.did - ) const did = conversation.recipients.find((o) => o !== account.did) const _address = didToAddress(did) setAddress(_address) - - if (details) { - if (details?.metadata?.ensName) { - setName(details?.metadata?.ensName) - } else if (details?.profile?.username) { - setName(details?.profile?.username) - } else { - setName(accountTruncate(_address)) - } - } else { - setName(accountTruncate(_address)) - } } if (conversation && account) {