mirror of
https://github.com/Permissionless-Software-Foundation/bch-dex-taker-v2.git
synced 2026-09-21 16:52:01 -07:00
feat(nostr): Added functionality to message button in profile
This commit is contained in:
@@ -9,6 +9,7 @@ import React from 'react'
|
||||
import ChatHeader from './chat-header'
|
||||
import MessageList from './message-list'
|
||||
import MessageInput from './message-input'
|
||||
import { Spinner } from 'react-bootstrap'
|
||||
|
||||
function ChatMain (props) {
|
||||
const { selectedChannel, messages } = props
|
||||
@@ -16,9 +17,9 @@ function ChatMain (props) {
|
||||
return (
|
||||
<>
|
||||
{!selectedChannel && (
|
||||
<div className='h-100 d-flex align-items-center justify-content-center' style={{ backgroundColor: '#f8f9fa' }}>
|
||||
<div className='h-100 d-flex align-items-center justify-content-center' style={{ backgroundColor: '#f8f9fa', minHeight: '200px' }}>
|
||||
<div className='text-center text-muted'>
|
||||
<h4>Select a channel to start chatting</h4>
|
||||
<Spinner animation='border' size='sm' />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faUser } from '@fortawesome/free-solid-svg-icons'
|
||||
import { Spinner } from 'react-bootstrap'
|
||||
|
||||
export default function DMItem (props) {
|
||||
const { dm, profiles, selectedChannel, onChangeChannel } = props
|
||||
@@ -97,6 +98,7 @@ export default function DMItem (props) {
|
||||
style={{ fontSize: '14px' }}
|
||||
>
|
||||
{getShortName(profile?.name) || 'Unknown User'}
|
||||
{!profile?.name && <Spinner animation='border' size='sm' style={{ marginLeft: '5px' }} />}
|
||||
</div>
|
||||
{/* <div
|
||||
className={`small ${isSelected ? 'text-white-50' : 'text-muted'} ${dm.unreadCount > 0 ? 'fw-bold' : ''}`}
|
||||
|
||||
@@ -14,7 +14,8 @@ import config from '../../../config'
|
||||
|
||||
function NostrChat (props) {
|
||||
const { appData } = props
|
||||
const { nostrQueries, bchWalletState } = appData
|
||||
const { nostrQueries, bchWalletState, startChannelChat } = appData
|
||||
|
||||
const [messages, setMessages] = useState([])
|
||||
const [loadedMessages, setLoadedMessages] = useState(false)
|
||||
const [profiles, setProfiles] = useState({})
|
||||
@@ -24,12 +25,86 @@ function NostrChat (props) {
|
||||
const [dmChannels, setDmChannels] = useState([])
|
||||
const [dmListLoaded, setDmListLoaded] = useState(false)
|
||||
|
||||
const [selectedChannel, setSelectedChannel] = useState(config.chatsId[0])
|
||||
const [selectedChannel, setSelectedChannel] = useState(null)
|
||||
const [selectedChannelIsDm, setSelectedChannelIsDm] = useState(false)
|
||||
|
||||
const profilesRef = useRef({})
|
||||
const dmChannelsRef = useRef([])
|
||||
|
||||
// Reset states on change channel
|
||||
const onChangeChannel = useCallback((ch) => {
|
||||
if (selectedChannel === ch) return
|
||||
const profiles = profilesRef.current
|
||||
setSelectedChannelIsDm(!!profiles[ch])
|
||||
setMessages([])
|
||||
setLoadedMessages(false)
|
||||
setSelectedChannel(ch)
|
||||
}, [selectedChannel])
|
||||
|
||||
// Add a new DM to the list
|
||||
const addPrivateMessage = useCallback(async (profile) => {
|
||||
try {
|
||||
const exist = dmChannelsRef.current.find(val => val === profile.pubKey)
|
||||
setMessages([])
|
||||
setLoadedMessages(false)
|
||||
onChangeChannel(profile.pubKey)
|
||||
if (exist) return
|
||||
setDmChannels(currentChs => {
|
||||
let newChs = [...currentChs]
|
||||
newChs.push(profile.pubKey)
|
||||
newChs = newChs.filter((val, i, list) => {
|
||||
const existingIndex = list.findIndex(value => value === val)
|
||||
return existingIndex === i
|
||||
})
|
||||
dmChannelsRef.current = newChs
|
||||
return newChs
|
||||
})
|
||||
setChannelsData(currentChs => {
|
||||
const newChs = { ...currentChs }
|
||||
newChs[profile.pubKey] = profile
|
||||
return newChs
|
||||
})
|
||||
} catch (error) {
|
||||
console.warn(error)
|
||||
}
|
||||
}, [onChangeChannel])
|
||||
|
||||
// Define starter chat
|
||||
useEffect(() => {
|
||||
// Start dm if a initial chat is provided from props
|
||||
const startDM = async (pubKey) => {
|
||||
const npub = await appData.nostrQueries.hexToNpub(pubKey)
|
||||
|
||||
const defaultProfile = { name: npub } // default profile
|
||||
profilesRef.current[pubKey] = defaultProfile // update ref , to prevent fetch this profile again.
|
||||
// Fetch profile
|
||||
const nostrProfile = await appData.nostrQueries.getProfile(pubKey)
|
||||
|
||||
const profile = nostrProfile || defaultProfile
|
||||
// add public key formats to profile object
|
||||
profile.pubKey = pubKey
|
||||
profile.npub = npub
|
||||
// Update profiles state
|
||||
setProfiles(currentProfiles => {
|
||||
const newProfiles = { ...currentProfiles }
|
||||
newProfiles[pubKey] = profile
|
||||
profilesRef.current = newProfiles
|
||||
return newProfiles
|
||||
})
|
||||
await addPrivateMessage(profile)
|
||||
}
|
||||
|
||||
if (selectedChannel) return
|
||||
if (!startChannelChat) {
|
||||
// Set group chat as initial chat
|
||||
setSelectedChannel(config.chatsId[0])
|
||||
setSelectedChannelIsDm(false)
|
||||
} else if (dmListLoaded) {
|
||||
// Set provided profile as initial chat
|
||||
startDM(startChannelChat)
|
||||
}
|
||||
}, [appData, startChannelChat, dmListLoaded, addPrivateMessage, selectedChannel])
|
||||
|
||||
// Handle read messages
|
||||
const onMsgRead = useCallback(async (ev) => {
|
||||
try {
|
||||
@@ -127,7 +202,7 @@ function NostrChat (props) {
|
||||
})
|
||||
|
||||
pool.on('event', (relay, subId, ev) => {
|
||||
console.log('post retrieved from ', relay.url, ev.content)
|
||||
console.log('Group post retrieved from ', relay.url, ev.content)
|
||||
onMsgRead(ev)
|
||||
})
|
||||
|
||||
@@ -141,13 +216,11 @@ function NostrChat (props) {
|
||||
// Handle nostr pool for dm channels
|
||||
useEffect(() => {
|
||||
// fetch messages when channel selected and channel metadata are loaded
|
||||
console.log('selectedChannel', selectedChannel)
|
||||
console.log('selectedChannelIsDm', selectedChannelIsDm)
|
||||
|
||||
if (!selectedChannel || !selectedChannelIsDm) return
|
||||
|
||||
const { nostrKeyPair } = bchWalletState
|
||||
const dmPubKey = selectedChannel
|
||||
console.log('dmPubKey', selectedChannel)
|
||||
|
||||
// Load messages for group channel
|
||||
const relays = nostrQueries.relays
|
||||
@@ -172,7 +245,7 @@ function NostrChat (props) {
|
||||
})
|
||||
|
||||
pool.on('event', (relay, subId, ev) => {
|
||||
console.log('post retrieved from ', relay.url, ev.content)
|
||||
console.log('DM post retrieved from ', relay.url, ev.content)
|
||||
// decrpt message
|
||||
if (ev.pubkey === nostrKeyPair.pubHex) {
|
||||
// Sent messages
|
||||
@@ -211,6 +284,7 @@ function NostrChat (props) {
|
||||
setDmChannels(currentChs => {
|
||||
const newChs = [...currentChs]
|
||||
newChs.push(profile.pubKey)
|
||||
|
||||
dmChannelsRef.current = newChs
|
||||
return newChs
|
||||
})
|
||||
@@ -254,18 +328,30 @@ function NostrChat (props) {
|
||||
// Load Dm channels
|
||||
useEffect(() => {
|
||||
const loadCurrentDms = async () => {
|
||||
const { nostrQueries, bchWalletState } = appData
|
||||
const { nostrKeyPair } = bchWalletState
|
||||
const dms = await appData.nostrQueries.getDms(nostrKeyPair.pubHex)
|
||||
setDmChannels(currentChs => {
|
||||
let newChs = [...currentChs, ...dms]
|
||||
newChs = newChs.filter((val, i, list) => {
|
||||
const existingIndex = list.findIndex(value => value === val)
|
||||
return existingIndex === i
|
||||
})
|
||||
dmChannelsRef.current = newChs
|
||||
return newChs
|
||||
})
|
||||
setDmListLoaded(true)
|
||||
|
||||
console.log('dm list', dms)
|
||||
|
||||
for (let i = 0; i < dms.length; i++) {
|
||||
const pubKey = dms[i]
|
||||
const npub = await appData.nostrQueries.hexToNpub(pubKey)
|
||||
const npub = await nostrQueries.hexToNpub(pubKey)
|
||||
|
||||
const defaultProfile = { name: npub } // default profile
|
||||
profilesRef.current[pubKey] = defaultProfile // update ref , to prevent fetch this profile again.
|
||||
// Fetch profile
|
||||
const nostrProfile = await appData.nostrQueries.getProfile(pubKey)
|
||||
const nostrProfile = await nostrQueries.getProfile(pubKey)
|
||||
|
||||
const profile = nostrProfile || defaultProfile
|
||||
// add public key formats to profile object
|
||||
@@ -279,13 +365,10 @@ function NostrChat (props) {
|
||||
return newProfiles
|
||||
})
|
||||
}
|
||||
setDmChannels(dms)
|
||||
setDmListLoaded(true)
|
||||
dmChannelsRef.current = dms
|
||||
}
|
||||
|
||||
loadCurrentDms()
|
||||
}, [bchWalletState, appData.nostrQueries])
|
||||
if (!dmListLoaded) loadCurrentDms()
|
||||
}, [appData, dmListLoaded])
|
||||
|
||||
// Load public channels data
|
||||
useEffect(() => {
|
||||
@@ -313,42 +396,9 @@ function NostrChat (props) {
|
||||
}
|
||||
}
|
||||
|
||||
// loadMessages()
|
||||
loadChData()
|
||||
}, [selectedChannel, appData, groupChannels])
|
||||
|
||||
const addPrivateMessage = async (profile) => {
|
||||
try {
|
||||
const exist = dmChannels.find(val => val === profile.pubKey)
|
||||
setMessages([])
|
||||
setLoadedMessages(false)
|
||||
onChangeChannel(profile.pubKey)
|
||||
if (exist) return
|
||||
setDmChannels(currentChs => {
|
||||
const newChs = [...currentChs]
|
||||
newChs.push(profile.pubKey)
|
||||
dmChannelsRef.current = newChs
|
||||
return newChs
|
||||
})
|
||||
setChannelsData(currentChs => {
|
||||
const newChs = { ...currentChs }
|
||||
newChs[profile.pubKey] = profile
|
||||
return newChs
|
||||
})
|
||||
} catch (error) {
|
||||
console.warn(error)
|
||||
}
|
||||
}
|
||||
|
||||
// Reset states on change channel
|
||||
const onChangeChannel = useCallback((ch) => {
|
||||
if (selectedChannel === ch) return
|
||||
setSelectedChannelIsDm(!!profiles[ch])
|
||||
setMessages([])
|
||||
setLoadedMessages(false)
|
||||
setSelectedChannel(ch)
|
||||
}, [selectedChannel, profiles])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container fluid className='h-100 p-0 mb-5 '>
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Spinner } from 'react-bootstrap'
|
||||
|
||||
function MessageList (props) {
|
||||
const { messages, loadedMessages } = props
|
||||
console.log('loadedMessages', loadedMessages)
|
||||
const [groupedMessages, setGroupedMessages] = useState([])
|
||||
const msgContainerRef = useRef()
|
||||
// Group messages by date
|
||||
|
||||
@@ -10,6 +10,7 @@ import CopyOnClick from '../../bch-wallet/copy-on-click.js'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faUser, faGlobe } from '@fortawesome/free-solid-svg-icons'
|
||||
import * as nip19 from 'nostr-tools/nip19'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
// Local libraries
|
||||
import NostrFormat from '../nostr-format'
|
||||
@@ -18,14 +19,17 @@ function ProfileRead (props) {
|
||||
const { npub, appData } = props
|
||||
const { onProfileRead } = props
|
||||
const [profile, setProfile] = useState({})
|
||||
const [pubKey, setPubKey] = useState({})
|
||||
|
||||
const [loaded, setLoaded] = useState(false)
|
||||
const [imageError, setImageError] = useState({ picture: false, banner: false })
|
||||
const navigate = useNavigate()
|
||||
|
||||
useEffect(() => {
|
||||
const start = async () => {
|
||||
const pubHexData = nip19.decode(npub)
|
||||
const pubHex = pubHexData.data
|
||||
|
||||
setPubKey(pubHex)
|
||||
const profile = await appData.nostrQueries.getProfile(pubHex)
|
||||
if (profile) {
|
||||
onProfileRead(profile)
|
||||
@@ -48,6 +52,13 @@ function ProfileRead (props) {
|
||||
setImageError(prev => ({ ...prev, [type]: false }))
|
||||
}
|
||||
|
||||
const handleMessage = () => {
|
||||
console.log('appData', appData)
|
||||
const { setStartChannelChat } = appData
|
||||
setStartChannelChat(pubKey)
|
||||
navigate('/nostr-chat')
|
||||
}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
{/* Banner Section */}
|
||||
@@ -146,6 +157,8 @@ function ProfileRead (props) {
|
||||
variant='primary'
|
||||
className='px-3 px-md-4 py-2 rounded-pill fw-semibold'
|
||||
style={{ minWidth: '120px' }}
|
||||
onClick={handleMessage}
|
||||
disabled={!loaded}
|
||||
>
|
||||
<i className='bi bi-chat-dots me-2' />
|
||||
Message
|
||||
|
||||
+6
-2
@@ -58,6 +58,9 @@ function useAppState () {
|
||||
// Nostr queries service
|
||||
const nostrQueriesRef = useRef(new NostrQueries({ relays: readRelays }))
|
||||
|
||||
// ProfileDM
|
||||
const [startChannelChat, setStartChannelChat] = useState('')
|
||||
|
||||
// The wallet state makes this a true progressive web app (PWA). As
|
||||
// balances, UTXOs, and tokens are retrieved, this state is updated.
|
||||
// properties are enumerated here for the purpose of documentation.
|
||||
@@ -205,8 +208,9 @@ function useAppState () {
|
||||
updateRelaysData,
|
||||
restoreRelaysData,
|
||||
readRelays,
|
||||
writeRelays
|
||||
|
||||
writeRelays,
|
||||
startChannelChat,
|
||||
setStartChannelChat
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -401,7 +401,7 @@ export default class NostrQueries {
|
||||
})
|
||||
|
||||
pool.on('event', (relay, subId, ev) => {
|
||||
console.log('post retrieved from ', relay.url, ev.sig)
|
||||
// console.log('post retrieved from ', relay.url, ev.sig)
|
||||
if (ev.pubkey === pubKey) {
|
||||
const pk = ev.tags[0][1]
|
||||
list = [...list, pk]
|
||||
|
||||
Reference in New Issue
Block a user