oceanprotocol/market

View on GitHub
src/components/@shared/DirectMessages/Conversation.tsx

Summary

Maintainability
C
7 hrs
Test Coverage
F
0%
import React, { useState, useEffect, useRef } from 'react'
import { useOrbis } from '@context/DirectMessages'
import { useInterval } from '@hooks/useInterval'
import { throttle } from '@utils/throttle'
import Time from '@shared/atoms/Time'
import Button from '@shared/atoms/Button'
import DecryptedMessage from './DecryptedMessage'
import Postbox from './Postbox'
import styles from './Conversation.module.css'
import { LoggerInstance } from '@oceanprotocol/lib'
import { IOrbisMessage } from '@context/DirectMessages/_types'

export default function DmConversation() {
  const {
    orbis,
    account,
    conversationId,
    hasLit,
    connectLit,
    clearConversationNotifs
  } = useOrbis()

  const messagesWrapper = useRef(null)
  const [isInitialized, setIsInitialized] = useState(false)
  const [isLoading, setIsLoading] = useState(false)
  const [messages, setMessages] = useState<IOrbisMessage[]>([])
  const [currentPage, setCurrentPage] = useState(0)
  const [hasMore, setHasMore] = useState(true)
  const [newMessages, setNewMessages] = useState(0)

  const scrollToBottom = (smooth = false) => {
    setTimeout(() => {
      messagesWrapper.current.scrollTo({
        top: messagesWrapper.current.scrollHeight,
        behavior: smooth ? 'smooth' : 'auto'
      })
    }, 300)
  }

  const getMessages: (options?: {
    polling?: boolean
    reset?: boolean
  }) => Promise<void> = async ({ polling = false, reset = false }) => {
    if (isLoading || !hasLit) return

    if (!polling) setIsLoading(true)

    const _page = polling || reset ? 0 : currentPage
    let _messages = reset ? [] : [...messages]
    const { data, error } = await orbis.getMessages(conversationId, _page)

    if (error) {
      LoggerInstance.error(error)
    }

    if (data.length) {
      data.reverse()
      if (!polling) {
        setHasMore(data.length >= 50)
        _messages = [...data, ..._messages]
        setMessages(_messages)
        if (currentPage === 0) {
          clearConversationNotifs(conversationId)
          scrollToBottom()
        }
        setCurrentPage(_page + 1)
      } else {
        const unique = data.filter(
          (a) => !_messages.some((b) => a.stream_id === b.stream_id)
        )
        setMessages([..._messages, ...unique])
        const el = messagesWrapper.current
        if (el && el.scrollHeight > el.offsetHeight) {
          setNewMessages((prev) => prev + unique.length)
        }
      }
    }

    setIsInitialized(true)
    setIsLoading(false)
  }

  useInterval(
    async () => {
      getMessages({ polling: true })
    },
    !isLoading && hasLit && isInitialized ? 5000 : false
  )

  const showTime = (streamId: string): boolean => {
    const index = messages.findIndex((o) => o.stream_id === streamId)

    if (index < -1) return true

    const nextMessage = messages[index + 1]
    if (!nextMessage || messages[index].creator !== nextMessage.creator)
      return true

    return nextMessage.timestamp - messages[index].timestamp > 60
  }

  const callback = (nMessage: IOrbisMessage) => {
    const _messages = [...messages, nMessage]
    setMessages(_messages)
    scrollToBottom()
  }

  const onScrollMessages = throttle(() => {
    const el = messagesWrapper.current

    if (!el) return

    if (hasMore && el.scrollTop <= 50) {
      getMessages()
    }

    if (
      Math.ceil(el.scrollTop) >= Math.floor(el.scrollHeight - el.offsetHeight)
    ) {
      setNewMessages(0)
      clearConversationNotifs(conversationId)
    }

    // Remove scroll listener
    messagesWrapper.current.removeEventListener('scroll', onScrollMessages)

    // Readd scroll listener
    setTimeout(() => {
      messagesWrapper.current.addEventListener('scroll', onScrollMessages)
    }, 100)
  }, 1000)

  useEffect(() => {
    setIsInitialized(false)
    setMessages([])
    if (
      conversationId &&
      !conversationId.startsWith('new-') &&
      orbis &&
      hasLit
    ) {
      getMessages({ reset: true })
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [conversationId, orbis, hasLit])

  useEffect(() => {
    const el = messagesWrapper.current
    el?.addEventListener('scroll', onScrollMessages)

    return () => {
      el?.removeEventListener('scroll', onScrollMessages)
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [messages])

  return (
    <div className={styles.conversation}>
      {!hasLit ? (
        <div className={styles.connectLit}>
          <p>
            You need to configure your private account to access your private
            conversations.
          </p>
          <Button
            style="primary"
            size="small"
            disabled={false}
            onClick={() => connectLit()}
          >
            Generate private account
          </Button>
        </div>
      ) : (
        <>
          {isLoading && <div className={styles.loading}>Loading...</div>}
          <div className={styles.messages}>
            {!isLoading && messages.length === 0 ? (
              <div className={styles.noMessages}>No message yet</div>
            ) : (
              <div ref={messagesWrapper} className={styles.scrollContent}>
                {messages.map((message) => (
                  <div
                    key={message.stream_id}
                    className={`${styles.message} ${
                      message.stream_id.startsWith('new_post--')
                        ? styles.pulse
                        : ''
                    } ${
                      account?.did === message.creator_details.did
                        ? styles.right
                        : styles.left
                    } ${showTime(message.stream_id) && styles.showTime}`}
                  >
                    <div className={styles.chatBubble}>
                      <DecryptedMessage
                        content={message.content}
                        position={
                          account?.did === message.creator_details.did
                            ? 'left'
                            : 'right'
                        }
                      />
                    </div>
                    <div className={styles.time}>
                      <Time
                        date={message.timestamp.toString()}
                        isUnix={true}
                        relative={false}
                        displayFormat="MMM d, yyyy, h:mm aa"
                      />
                    </div>
                  </div>
                ))}
              </div>
            )}

            {newMessages > 0 && (
              <button
                className={styles.newMessagesBadge}
                onClick={() => scrollToBottom(true)}
              >
                {newMessages} new {newMessages > 1 ? 'messages' : 'message'}
              </button>
            )}
          </div>
          <Postbox callback={callback} />
        </>
      )}
    </div>
  )
}