mirror of
https://github.com/Permissionless-Software-Foundation/bch-dex-taker-v2.git
synced 2026-09-21 16:52:01 -07:00
feat(nostr): Implemented 'Like' buttons functionality
This commit is contained in:
@@ -5,22 +5,87 @@
|
|||||||
import React, { useCallback, useEffect, useState } from 'react'
|
import React, { useCallback, useEffect, useState } from 'react'
|
||||||
import { Card, Spinner } from 'react-bootstrap'
|
import { Card, Spinner } from 'react-bootstrap'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import { faUser } from '@fortawesome/free-solid-svg-icons'
|
import { faUser, faHeart as faHeartSolid } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { faHeart } from '@fortawesome/free-regular-svg-icons'
|
||||||
import * as nip19 from 'nostr-tools/nip19'
|
import * as nip19 from 'nostr-tools/nip19'
|
||||||
import NostrFormat from '../nostr-format'
|
import NostrFormat from '../nostr-format'
|
||||||
|
import { finalizeEvent } from 'nostr-tools/pure'
|
||||||
|
import { Relay } from 'nostr-tools/relay'
|
||||||
|
import { hexToBytes } from '@noble/hashes/utils' // already an installed dependency
|
||||||
|
import { RelayPool } from 'nostr'
|
||||||
|
|
||||||
function FeedCard (props) {
|
function FeedCard (props) {
|
||||||
const { post, appData, profiles } = props
|
const { post, appData, profiles } = props
|
||||||
|
const { nostrKeyPair } = appData.bchWalletState
|
||||||
|
|
||||||
const [profile, setProfile] = useState(profiles[post.pubkey])
|
const [profile, setProfile] = useState(profiles[post.pubkey])
|
||||||
|
|
||||||
const [npub, setNpub] = useState('')
|
const [npub, setNpub] = useState('')
|
||||||
const [isClicked, setIsClicked] = useState(false)
|
const [isClicked, setIsClicked] = useState(false)
|
||||||
|
const [isLiked, setIsLiked] = useState(false)
|
||||||
|
const [likesCount, setLikesCount] = useState(null)
|
||||||
|
const [likesFetched, setLikesFetched] = useState(false)
|
||||||
|
|
||||||
|
// function to fetch a post likes reaction
|
||||||
|
const handleLikes = useCallback(async (post, userPubKey) => {
|
||||||
|
try {
|
||||||
|
const psf = 'wss://nostr-relay.psfoundation.info'
|
||||||
|
const res = await new Promise((resolve) => {
|
||||||
|
let likesCnt = 0
|
||||||
|
let userLikedCnt = 0
|
||||||
|
const pool = RelayPool([psf])
|
||||||
|
pool.on('open', relay => {
|
||||||
|
relay.subscribe('subid', { kinds: [7], '#e': [post.id] })
|
||||||
|
})
|
||||||
|
|
||||||
|
pool.on('eose', relay => {
|
||||||
|
relay.close()
|
||||||
|
resolve({
|
||||||
|
count: likesCnt,
|
||||||
|
userLiked: userLikedCnt > 0
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
pool.on('event', (relay, subId, ev) => {
|
||||||
|
try {
|
||||||
|
// Count likes
|
||||||
|
if (ev.content === '+') {
|
||||||
|
likesCnt++
|
||||||
|
// Count user likes
|
||||||
|
if (ev.pubkey === userPubKey) {
|
||||||
|
userLikedCnt++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Count dislikes
|
||||||
|
if (ev.content === '-') {
|
||||||
|
likesCnt--
|
||||||
|
// Count user dislikes.
|
||||||
|
if (ev.pubkey === userPubKey) {
|
||||||
|
userLikedCnt--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// skip error
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
setLikesCount(res.count)
|
||||||
|
setIsLiked(res.userLiked)
|
||||||
|
setLikesFetched(true)
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(error)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
// Get npub from pubkey
|
// Get npub from pubkey
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const npub = nip19.npubEncode(post.pubkey)
|
const npub = nip19.npubEncode(post.pubkey)
|
||||||
setNpub(npub)
|
setNpub(npub)
|
||||||
}, [post])
|
|
||||||
|
// Handle post likes
|
||||||
|
if (!likesFetched && nostrKeyPair.pubHex) {
|
||||||
|
handleLikes(post, nostrKeyPair.pubHex)
|
||||||
|
}
|
||||||
|
}, [post, nostrKeyPair, handleLikes, likesFetched])
|
||||||
|
|
||||||
// Verify if pubkey profile exists into the profiles state
|
// Verify if pubkey profile exists into the profiles state
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -41,6 +106,58 @@ function FeedCard (props) {
|
|||||||
return npub.slice(0, 8) + '...' + npub.slice(-5)
|
return npub.slice(0, 8) + '...' + npub.slice(-5)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const submitLike = async (e) => {
|
||||||
|
// Post on nostr network
|
||||||
|
e.preventDefault()
|
||||||
|
try {
|
||||||
|
const { nostrKeyPair } = appData.bchWalletState
|
||||||
|
|
||||||
|
setLikesFetched(false)
|
||||||
|
// Convert private key to binary
|
||||||
|
const privateKeyBin = hexToBytes(nostrKeyPair.privHex)
|
||||||
|
|
||||||
|
// Relay list
|
||||||
|
const psf = 'wss://nostr-relay.psfoundation.info'
|
||||||
|
|
||||||
|
// Define if like or dislike
|
||||||
|
let content = '+'
|
||||||
|
if (isLiked) { content = '-' }
|
||||||
|
|
||||||
|
// Generate a post.
|
||||||
|
const eventTemplate = {
|
||||||
|
kind: 7,
|
||||||
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
|
tags: [
|
||||||
|
['e', post.id, psf],
|
||||||
|
['p', psf, nostrKeyPair.pubHex]
|
||||||
|
],
|
||||||
|
content
|
||||||
|
}
|
||||||
|
console.log(`eventTemplate: ${JSON.stringify(eventTemplate, null, 2)}`)
|
||||||
|
|
||||||
|
// Sign the post
|
||||||
|
const signedEvent = finalizeEvent(eventTemplate, privateKeyBin)
|
||||||
|
console.log('signedEvent: ', signedEvent)
|
||||||
|
|
||||||
|
// Connect to a relay.
|
||||||
|
const relay = await Relay.connect(psf)
|
||||||
|
console.log(`connected to ${relay.url}`)
|
||||||
|
|
||||||
|
// Publish the message to the relay.
|
||||||
|
const result = await relay.publish(signedEvent)
|
||||||
|
console.log('result: ', result)
|
||||||
|
|
||||||
|
// Close the connection to the relay.
|
||||||
|
relay.close()
|
||||||
|
|
||||||
|
await handleLikes(post, nostrKeyPair.pubHex)
|
||||||
|
setLikesFetched(true)
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(error)
|
||||||
|
setLikesFetched(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className='mb-4 bg-light rounded-4 shadow-sm border-0'>
|
<Card className='mb-4 bg-light rounded-4 shadow-sm border-0'>
|
||||||
<Card.Body className='p-3'>
|
<Card.Body className='p-3'>
|
||||||
@@ -91,6 +208,50 @@ function FeedCard (props) {
|
|||||||
<NostrFormat content={post.content} />
|
<NostrFormat content={post.content} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className='d-flex justify-content-end'>
|
||||||
|
{likesFetched && (
|
||||||
|
<button
|
||||||
|
onClick={submitLike}
|
||||||
|
className='btn btn-link text-decoration-none p-0 d-flex align-items-center gap-2'
|
||||||
|
style={{
|
||||||
|
border: 'none',
|
||||||
|
background: 'transparent',
|
||||||
|
transition: 'all 0.2s ease',
|
||||||
|
color: isLiked ? '#e31b23' : '#536471',
|
||||||
|
fontSize: '14px',
|
||||||
|
fontWeight: '400'
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.target.style.transform = 'scale(1.05)'
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.target.style.transform = 'scale(1)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={isLiked ? faHeartSolid : faHeart}
|
||||||
|
style={{
|
||||||
|
fontSize: '16px',
|
||||||
|
color: isLiked ? '#e31b23' : '#536471',
|
||||||
|
transition: 'all 0.2s ease'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: '14px',
|
||||||
|
fontWeight: '400',
|
||||||
|
color: isLiked ? '#e31b23' : '#536471',
|
||||||
|
transition: 'all 0.2s ease'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{likesCount || 0}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{!likesFetched && (
|
||||||
|
<Spinner animation='border' size='sm' className='ms-2' />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</Card.Body>
|
</Card.Body>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user