feat: implemented the logic for handling reactions on mods
This commit is contained in:
parent
a85314f0a7
commit
2dd2992810
@ -36,3 +36,18 @@ export const LANDING_PAGE_DATA = {
|
||||
}
|
||||
]
|
||||
}
|
||||
// we use this object to check is a user has reacted positively or negatively to a post
|
||||
// reactions are kind 7 events and their content is either emoji icon or emoji shortcode
|
||||
// Extend the following object as per need to include more emojis and shortcodes
|
||||
// NOTE: In following object emojis and shortcode array are not interlinked.
|
||||
// Both of these arrays can have separate items
|
||||
export const REACTIONS = {
|
||||
positive: {
|
||||
emojis: ['+', '❤️'],
|
||||
shortCodes: [':star_struck:', ':red_heart:']
|
||||
},
|
||||
negative: {
|
||||
emojis: ['-', '🥵'],
|
||||
shortCodes: [':hot_face:', ':woozy_face:', ':face_with_thermometer:']
|
||||
}
|
||||
}
|
||||
|
@ -62,62 +62,77 @@ export class RelayController {
|
||||
/**
|
||||
* Publishes an event to multiple relays.
|
||||
*
|
||||
* This method connects to the application relay and a set of write relays
|
||||
* obtained from the `MetadataController`. It then publishes the event to
|
||||
* all connected relays and returns a list of relays where the event was successfully published.
|
||||
* This method establishes a connection to the application relay specified by
|
||||
* an environment variable and a set of relays obtained from the
|
||||
* `MetadataController`. It attempts to publish the event to all connected
|
||||
* relays and returns a list of URLs of relays where the event was successfully
|
||||
* published.
|
||||
*
|
||||
* If the process of finding relays or publishing the event takes too long,
|
||||
* it handles the timeout to prevent blocking the operation.
|
||||
*
|
||||
* @param event - The event to be published.
|
||||
* @returns A promise that resolves to an array of URLs of relays where the event was published,
|
||||
* or an empty array if no relays were connected or the event could not be published.
|
||||
* @param userHexKey - The user's hexadecimal public key, used to retrieve their relays.
|
||||
* If not provided, the event's public key will be used.
|
||||
* @param userRelaysType - The type of relays to be retrieved (e.g., write relays).
|
||||
* Defaults to `UserRelaysType.Write`.
|
||||
* @returns A promise that resolves to an array of URLs of relays where the event
|
||||
* was published, or an empty array if no relays were connected or the
|
||||
* event could not be published.
|
||||
*/
|
||||
publish = async (event: Event): Promise<string[]> => {
|
||||
// Connect to the application relay specified by environment variable
|
||||
publish = async (
|
||||
event: Event,
|
||||
userHexKey?: string,
|
||||
userRelaysType?: UserRelaysType
|
||||
): Promise<string[]> => {
|
||||
// Connect to the application relay specified by an environment variable
|
||||
const appRelayPromise = this.connectRelay(import.meta.env.VITE_APP_RELAY)
|
||||
|
||||
// todo: window.nostr.getRelays() is not implemented yet in nostr-login, implement the logic once its done
|
||||
// TODO: Implement logic to retrieve relays using `window.nostr.getRelays()` once it becomes available in nostr-login.
|
||||
|
||||
// Retrieve an instance of MetadataController to find user relays
|
||||
const metadataController = await MetadataController.getInstance()
|
||||
|
||||
// Retrieve the list of write relays for the event's public key
|
||||
// Use a timeout to handle cases where retrieving write relays takes too long
|
||||
const writeRelaysPromise = metadataController.findUserRelays(
|
||||
event.pubkey,
|
||||
UserRelaysType.Write
|
||||
// Retrieve the list of relays for the specified user's public key
|
||||
// A timeout is used to prevent long waits if the relay retrieval is delayed
|
||||
const relaysPromise = metadataController.findUserRelays(
|
||||
userHexKey || event.pubkey,
|
||||
userRelaysType || UserRelaysType.Write
|
||||
)
|
||||
|
||||
log(this.debug, LogType.Info, `ℹ Finding user's write relays`)
|
||||
|
||||
// Use Promise.race to either get the write relay URLs or timeout
|
||||
const writeRelayUrls = await Promise.race([
|
||||
writeRelaysPromise,
|
||||
timeout() // This is a custom timeout function that rejects the promise after a specified time
|
||||
// Use Promise.race to either get the relay URLs or handle the timeout
|
||||
const relayUrls = await Promise.race([
|
||||
relaysPromise,
|
||||
timeout() // Custom timeout function that rejects after a specified time
|
||||
]).catch((err) => {
|
||||
log(this.debug, LogType.Error, err)
|
||||
return [] as string[] // Return an empty array if an error occurs
|
||||
})
|
||||
|
||||
// push admin relay urls obtained from metadata controller to writeRelayUrls list
|
||||
// Add admin relay URLs from the metadata controller to the list of relay URLs
|
||||
metadataController.adminRelays.forEach((url) => {
|
||||
writeRelayUrls.push(url)
|
||||
relayUrls.push(url)
|
||||
})
|
||||
|
||||
// Connect to all write relays obtained from MetadataController
|
||||
const relayPromises = writeRelayUrls.map((relayUrl) =>
|
||||
// Attempt to connect to all write relays obtained from MetadataController
|
||||
const relayPromises = relayUrls.map((relayUrl) =>
|
||||
this.connectRelay(relayUrl)
|
||||
)
|
||||
|
||||
// Wait for all relay connections to settle (either fulfilled or rejected)
|
||||
// Wait for all relay connection attempts to settle (either fulfilled or rejected)
|
||||
await Promise.allSettled([appRelayPromise, ...relayPromises])
|
||||
|
||||
// Check if any relays are connected; if not, log an error and return null
|
||||
// If no relays are connected, log an error and return an empty array
|
||||
if (this.connectedRelays.length === 0) {
|
||||
log(this.debug, LogType.Error, 'No relay is connected!')
|
||||
return []
|
||||
}
|
||||
|
||||
const publishedOnRelays: string[] = [] // List to track which relays successfully published the event
|
||||
const publishedOnRelays: string[] = [] // Track relays where the event was successfully published
|
||||
|
||||
// Create a promise for publishing the event to each connected relay
|
||||
// Create promises to publish the event to each connected relay
|
||||
const publishPromises = this.connectedRelays.map((relay) => {
|
||||
log(
|
||||
this.debug,
|
||||
@ -128,7 +143,7 @@ export class RelayController {
|
||||
|
||||
return Promise.race([
|
||||
relay.publish(event), // Publish the event to the relay
|
||||
timeout(30000) // Set a timeout to handle cases where publishing takes too long
|
||||
timeout(30000) // Set a timeout to handle slow publishing operations
|
||||
])
|
||||
.then((res) => {
|
||||
log(
|
||||
@ -137,7 +152,7 @@ export class RelayController {
|
||||
`⬆️ nostr (${relay.url}): Publish result:`,
|
||||
res
|
||||
)
|
||||
publishedOnRelays.push(relay.url) // Add the relay URL to the list of successfully published relays
|
||||
publishedOnRelays.push(relay.url) // Add successful relay URL to the list
|
||||
})
|
||||
.catch((err) => {
|
||||
log(
|
||||
@ -153,16 +168,15 @@ export class RelayController {
|
||||
await Promise.allSettled(publishPromises)
|
||||
|
||||
if (publishedOnRelays.length > 0) {
|
||||
// if the event was successfully published to relays then check if it contains the `aTag`
|
||||
// if so, then cache the event
|
||||
|
||||
// If the event was successfully published to any relays, check if it contains an `aTag`
|
||||
// If the `aTag` is present, cache the event locally
|
||||
const aTag = event.tags.find((item) => item[0] === 'a')
|
||||
if (aTag && aTag[1]) {
|
||||
this.events.set(aTag[1], event)
|
||||
}
|
||||
}
|
||||
|
||||
// Return the list of relay URLs where the event was published
|
||||
// Return the list of relay URLs where the event was successfully published
|
||||
return publishedOnRelays
|
||||
}
|
||||
|
||||
@ -378,28 +392,19 @@ export class RelayController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches an event from the user's relays based on a specified filter.
|
||||
* The function first retrieves the user's relays, and then fetches the event using the provided filter.
|
||||
* Asynchronously retrieves multiple events from the user's relays based on a specified filter.
|
||||
* The function first retrieves the user's relays, and then fetches the events using the provided filter.
|
||||
*
|
||||
* @param filter - The event filter to use when fetching the event (e.g., kinds, authors).
|
||||
* @param hexKey - The hexadecimal representation of the user's public key.
|
||||
* @param userRelaysType - The type of relays to search (e.g., write, read).
|
||||
* @returns A promise that resolves to the fetched event or null if the operation fails.
|
||||
* @returns A promise that resolves with an array of events.
|
||||
*/
|
||||
fetchEventFromUserRelays = async (
|
||||
fetchEventsFromUserRelays = async (
|
||||
filter: Filter,
|
||||
hexKey: string,
|
||||
userRelaysType: UserRelaysType
|
||||
) => {
|
||||
// first check if event is present in cached map then return that
|
||||
// otherwise query relays
|
||||
if (filter['#a']) {
|
||||
const aTag = filter['#a'][0]
|
||||
const cachedEvent = this.events.get(aTag)
|
||||
|
||||
if (cachedEvent) return cachedEvent
|
||||
}
|
||||
|
||||
): Promise<Event[]> => {
|
||||
// Get an instance of the MetadataController, which manages user metadata and relays
|
||||
const metadataController = await MetadataController.getInstance()
|
||||
|
||||
@ -423,7 +428,55 @@ export class RelayController {
|
||||
})
|
||||
|
||||
// Fetch the event from the user's relays using the provided filter and relay URLs
|
||||
return this.fetchEvent(filter, relayUrls)
|
||||
return this.fetchEvents(filter, relayUrls)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches an event from the user's relays based on a specified filter.
|
||||
* The function first retrieves the user's relays, and then fetches the event using the provided filter.
|
||||
*
|
||||
* @param filter - The event filter to use when fetching the event (e.g., kinds, authors).
|
||||
* @param hexKey - The hexadecimal representation of the user's public key.
|
||||
* @param userRelaysType - The type of relays to search (e.g., write, read).
|
||||
* @returns A promise that resolves to the fetched event or null if the operation fails.
|
||||
*/
|
||||
fetchEventFromUserRelays = async (
|
||||
filter: Filter,
|
||||
hexKey: string,
|
||||
userRelaysType: UserRelaysType
|
||||
): Promise<Event | null> => {
|
||||
// first check if event is present in cached map then return that
|
||||
// otherwise query relays
|
||||
if (filter['#a']) {
|
||||
const aTag = filter['#a'][0]
|
||||
const cachedEvent = this.events.get(aTag)
|
||||
|
||||
if (cachedEvent) return cachedEvent
|
||||
}
|
||||
|
||||
const events = await this.fetchEventsFromUserRelays(
|
||||
filter,
|
||||
hexKey,
|
||||
userRelaysType
|
||||
)
|
||||
// Sort events by creation date in descending order
|
||||
events.sort((a, b) => b.created_at - a.created_at)
|
||||
|
||||
if (events.length > 0) {
|
||||
const event = events[0]
|
||||
|
||||
// if the aTag was specified in filter then cache the fetched event before returning
|
||||
if (filter['#a']) {
|
||||
const aTag = filter['#a'][0]
|
||||
this.events.set(aTag, event)
|
||||
}
|
||||
|
||||
// return the event
|
||||
return event
|
||||
}
|
||||
|
||||
// return null if event array is empty
|
||||
return null
|
||||
}
|
||||
|
||||
getTotalZapAmount = async (
|
||||
|
@ -3,8 +3,8 @@ import { EditorContent, useEditor } from '@tiptap/react'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import { formatDate } from 'date-fns'
|
||||
import FsLightbox from 'fslightbox-react'
|
||||
import { Filter, kinds, nip19, UnsignedEvent } from 'nostr-tools'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { Filter, kinds, nip19, UnsignedEvent, Event } from 'nostr-tools'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { toast } from 'react-toastify'
|
||||
import { BlogCard } from '../components/BlogCard'
|
||||
@ -45,6 +45,7 @@ import {
|
||||
signAndPublish,
|
||||
unformatNumber
|
||||
} from '../utils'
|
||||
import { REACTIONS } from '../constants'
|
||||
|
||||
export const ModPage = () => {
|
||||
const { naddr } = useParams()
|
||||
@ -1042,48 +1043,7 @@ const Interactions = ({ modDetails }: InteractionsProps) => {
|
||||
</div>
|
||||
</a>
|
||||
<Zap modDetails={modDetails} />
|
||||
<div
|
||||
id='reactUp'
|
||||
className='IBMSMSMBSS_Details_Card IBMSMSMBSS_D_CReactUp IBMSMSMBSS_D_CRUActive'
|
||||
>
|
||||
<div className='IBMSMSMBSS_Details_CardVisual'>
|
||||
<svg
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
viewBox='0 0 512 512'
|
||||
width='1em'
|
||||
height='1em'
|
||||
fill='currentColor'
|
||||
className='IBMSMSMBSS_Details_CardVisualIcon'
|
||||
>
|
||||
<path d='M0 190.9V185.1C0 115.2 50.52 55.58 119.4 44.1C164.1 36.51 211.4 51.37 244 84.02L256 96L267.1 84.02C300.6 51.37 347 36.51 392.6 44.1C461.5 55.58 512 115.2 512 185.1V190.9C512 232.4 494.8 272.1 464.4 300.4L283.7 469.1C276.2 476.1 266.3 480 256 480C245.7 480 235.8 476.1 228.3 469.1L47.59 300.4C17.23 272.1 .0003 232.4 .0003 190.9L0 190.9z'></path>
|
||||
</svg>
|
||||
</div>
|
||||
<p className='IBMSMSMBSS_Details_CardText'>4.2k</p>
|
||||
<div className='IBMSMSMBSSCL_CAElementLoadWrapper'>
|
||||
<div className='IBMSMSMBSSCL_CAElementLoad'></div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id='reactDown'
|
||||
className='IBMSMSMBSS_Details_Card IBMSMSMBSS_D_CReactDown'
|
||||
>
|
||||
<div className='IBMSMSMBSS_Details_CardVisual'>
|
||||
<svg
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
viewBox='0 0 512 512'
|
||||
width='1em'
|
||||
height='1em'
|
||||
fill='currentColor'
|
||||
className='IBMSMSMBSS_Details_CardVisualIcon'
|
||||
>
|
||||
<path d='M512 440.1C512 479.9 479.7 512 439.1 512H71.92C32.17 512 0 479.8 0 440c0-35.88 26.19-65.35 60.56-70.85C43.31 356 32 335.4 32 312C32 272.2 64.25 240 104 240h13.99C104.5 228.2 96 211.2 96 192c0-35.38 28.56-64 63.94-64h16C220.1 128 256 92.12 256 48c0-17.38-5.784-33.35-15.16-46.47C245.8 .7754 250.9 0 256 0c53 0 96 43 96 96c0 11.25-2.288 22-5.913 32h5.879C387.3 128 416 156.6 416 192c0 19.25-8.59 36.25-22.09 48H408C447.8 240 480 272.2 480 312c0 23.38-11.38 44.01-28.63 57.14C485.7 374.6 512 404.3 512 440.1z'></path>
|
||||
</svg>
|
||||
</div>
|
||||
<p className='IBMSMSMBSS_Details_CardText'>69</p>
|
||||
<div className='IBMSMSMBSSCL_CAElementLoadWrapper'>
|
||||
<div className='IBMSMSMBSSCL_CAElementLoad'></div>
|
||||
</div>
|
||||
</div>
|
||||
<Reactions modDetails={modDetails} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@ -1819,3 +1779,207 @@ const ZapSite = () => {
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
type ReactionsProps = {
|
||||
modDetails: ModDetails
|
||||
}
|
||||
|
||||
const Reactions = ({ modDetails }: ReactionsProps) => {
|
||||
const [isReactionInProgress, setIsReactionInProgress] = useState(false)
|
||||
const [isDataLoaded, setIsDataLoaded] = useState(false)
|
||||
const [reactionEvents, setReactionEvents] = useState<Event[]>([])
|
||||
|
||||
const userState = useAppSelector((state) => state.user)
|
||||
|
||||
useDidMount(() => {
|
||||
const filter: Filter = {
|
||||
kinds: [kinds.Reaction],
|
||||
'#a': [modDetails.aTag]
|
||||
}
|
||||
|
||||
RelayController.getInstance()
|
||||
.fetchEventsFromUserRelays(filter, modDetails.author, UserRelaysType.Read)
|
||||
.then((events) => {
|
||||
setReactionEvents(events)
|
||||
})
|
||||
.finally(() => {
|
||||
setIsDataLoaded(true)
|
||||
})
|
||||
})
|
||||
|
||||
const checkHasPositiveReaction = () => {
|
||||
return (
|
||||
!!userState.auth &&
|
||||
reactionEvents.some(
|
||||
(event) =>
|
||||
event.pubkey === userState.user?.pubkey &&
|
||||
(REACTIONS.positive.emojis.includes(event.content) ||
|
||||
REACTIONS.positive.shortCodes.includes(event.content))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
const checkHasNegativeReaction = () => {
|
||||
return (
|
||||
!!userState.auth &&
|
||||
reactionEvents.some(
|
||||
(event) =>
|
||||
event.pubkey === userState.user?.pubkey &&
|
||||
(REACTIONS.negative.emojis.includes(event.content) ||
|
||||
REACTIONS.negative.shortCodes.includes(event.content))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
const getPubkey = async () => {
|
||||
let hexPubkey: string
|
||||
|
||||
if (userState.auth && userState.user?.pubkey) {
|
||||
hexPubkey = userState.user.pubkey as string
|
||||
} else {
|
||||
hexPubkey = (await window.nostr?.getPublicKey()) as string
|
||||
}
|
||||
|
||||
if (!hexPubkey) {
|
||||
toast.error('Could not get pubkey')
|
||||
return null
|
||||
}
|
||||
|
||||
return hexPubkey
|
||||
}
|
||||
|
||||
const handleReaction = async (isPositive?: boolean) => {
|
||||
if (
|
||||
!isDataLoaded ||
|
||||
checkHasPositiveReaction() ||
|
||||
checkHasNegativeReaction()
|
||||
)
|
||||
return
|
||||
|
||||
// Check if the voting process is already in progress
|
||||
if (isReactionInProgress) return
|
||||
|
||||
// Set the flag to indicate that the voting process has started
|
||||
setIsReactionInProgress(true)
|
||||
|
||||
try {
|
||||
const pubkey = await getPubkey()
|
||||
if (!pubkey) return
|
||||
|
||||
const unsignedEvent: UnsignedEvent = {
|
||||
kind: kinds.Reaction,
|
||||
created_at: now(),
|
||||
content: isPositive ? '+' : '-',
|
||||
pubkey,
|
||||
tags: [
|
||||
['e', modDetails.id],
|
||||
['p', modDetails.author],
|
||||
['a', modDetails.aTag]
|
||||
]
|
||||
}
|
||||
|
||||
const signedEvent = await window.nostr
|
||||
?.signEvent(unsignedEvent)
|
||||
.then((event) => event as Event)
|
||||
.catch((err) => {
|
||||
toast.error('Failed to sign the event!')
|
||||
log(true, LogType.Error, 'Failed to sign the event!', err)
|
||||
return null
|
||||
})
|
||||
|
||||
if (!signedEvent) return
|
||||
|
||||
const publishedOnRelays = await RelayController.getInstance().publish(
|
||||
signedEvent as Event,
|
||||
modDetails.author,
|
||||
UserRelaysType.Read
|
||||
)
|
||||
|
||||
if (publishedOnRelays.length === 0) {
|
||||
toast.error('Failed to publish reaction event on any relay')
|
||||
return
|
||||
}
|
||||
|
||||
setReactionEvents((prev) => [...prev, signedEvent])
|
||||
} finally {
|
||||
setIsReactionInProgress(false)
|
||||
}
|
||||
}
|
||||
|
||||
const { likesCount, disLikesCount } = useMemo(() => {
|
||||
let positiveCount = 0
|
||||
let negativeCount = 0
|
||||
reactionEvents.forEach((event) => {
|
||||
if (
|
||||
REACTIONS.positive.emojis.includes(event.content) ||
|
||||
REACTIONS.positive.shortCodes.includes(event.content)
|
||||
) {
|
||||
positiveCount++
|
||||
} else if (
|
||||
REACTIONS.negative.emojis.includes(event.content) ||
|
||||
REACTIONS.negative.shortCodes.includes(event.content)
|
||||
) {
|
||||
negativeCount++
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
likesCount: abbreviateNumber(positiveCount),
|
||||
disLikesCount: abbreviateNumber(negativeCount)
|
||||
}
|
||||
}, [reactionEvents])
|
||||
|
||||
const hasReactedPositively = checkHasPositiveReaction()
|
||||
const hasReactedNegatively = checkHasNegativeReaction()
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`IBMSMSMBSS_Details_Card IBMSMSMBSS_D_CReactUp ${
|
||||
hasReactedPositively ? 'IBMSMSMBSS_D_CRUActive' : ''
|
||||
}`}
|
||||
onClick={() => handleReaction(true)}
|
||||
>
|
||||
<div className='IBMSMSMBSS_Details_CardVisual'>
|
||||
<svg
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
viewBox='0 0 512 512'
|
||||
width='1em'
|
||||
height='1em'
|
||||
fill='currentColor'
|
||||
className='IBMSMSMBSS_Details_CardVisualIcon'
|
||||
>
|
||||
<path d='M0 190.9V185.1C0 115.2 50.52 55.58 119.4 44.1C164.1 36.51 211.4 51.37 244 84.02L256 96L267.1 84.02C300.6 51.37 347 36.51 392.6 44.1C461.5 55.58 512 115.2 512 185.1V190.9C512 232.4 494.8 272.1 464.4 300.4L283.7 469.1C276.2 476.1 266.3 480 256 480C245.7 480 235.8 476.1 228.3 469.1L47.59 300.4C17.23 272.1 .0003 232.4 .0003 190.9L0 190.9z'></path>
|
||||
</svg>
|
||||
</div>
|
||||
<p className='IBMSMSMBSS_Details_CardText'>{likesCount}</p>
|
||||
<div className='IBMSMSMBSSCL_CAElementLoadWrapper'>
|
||||
<div className='IBMSMSMBSSCL_CAElementLoad'></div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`IBMSMSMBSS_Details_Card IBMSMSMBSS_D_CReactDown ${
|
||||
hasReactedNegatively ? 'IBMSMSMBSS_D_CRDActive' : ''
|
||||
}`}
|
||||
onClick={() => handleReaction()}
|
||||
>
|
||||
<div className='IBMSMSMBSS_Details_CardVisual'>
|
||||
<svg
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
viewBox='0 0 512 512'
|
||||
width='1em'
|
||||
height='1em'
|
||||
fill='currentColor'
|
||||
className='IBMSMSMBSS_Details_CardVisualIcon'
|
||||
>
|
||||
<path d='M512 440.1C512 479.9 479.7 512 439.1 512H71.92C32.17 512 0 479.8 0 440c0-35.88 26.19-65.35 60.56-70.85C43.31 356 32 335.4 32 312C32 272.2 64.25 240 104 240h13.99C104.5 228.2 96 211.2 96 192c0-35.38 28.56-64 63.94-64h16C220.1 128 256 92.12 256 48c0-17.38-5.784-33.35-15.16-46.47C245.8 .7754 250.9 0 256 0c53 0 96 43 96 96c0 11.25-2.288 22-5.913 32h5.879C387.3 128 416 156.6 416 192c0 19.25-8.59 36.25-22.09 48H408C447.8 240 480 272.2 480 312c0 23.38-11.38 44.01-28.63 57.14C485.7 374.6 512 404.3 512 440.1z'></path>
|
||||
</svg>
|
||||
</div>
|
||||
<p className='IBMSMSMBSS_Details_CardText'>{disLikesCount}</p>
|
||||
<div className='IBMSMSMBSSCL_CAElementLoadWrapper'>
|
||||
<div className='IBMSMSMBSSCL_CAElementLoad'></div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user