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) {