mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
revisions
This commit is contained in:
parent
9c364e34e6
commit
06b0244172
@ -11,14 +11,13 @@ import { useInterval } from '@hooks/useInterval'
|
||||
import { Orbis } from '@orbisclub/orbis-sdk'
|
||||
import { useWeb3 } from './Web3'
|
||||
import { accountTruncate } from '@utils/web3'
|
||||
import { didToAddress } from '@utils/orbis'
|
||||
import { didToAddress, sleep } from '@utils/orbis'
|
||||
import { getEnsName } from '@utils/ens'
|
||||
import usePrevious from '@hooks/usePrevious'
|
||||
import useLocalStorage from '@hooks/useLocalStorage'
|
||||
import DirectMessages from '@shared/Orbis/DirectMessages'
|
||||
|
||||
interface INewConversation {
|
||||
name: string | null
|
||||
recipients: string[]
|
||||
}
|
||||
|
||||
@ -51,8 +50,8 @@ type IOrbisProvider = {
|
||||
setNewConversation: (newConversation: INewConversation) => void
|
||||
setOpenConversations: (open: boolean) => void
|
||||
setConversationId: (conversationId: string) => void
|
||||
getConversation: (userDid: string) => Promise<IOrbisConversation>
|
||||
createConversation: (userDid: string) => Promise<void>
|
||||
getConversationByDid: (userDid: string) => Promise<IOrbisConversation>
|
||||
createConversation: (recipients: string[]) => Promise<IOrbisConversation>
|
||||
clearMessageNotifs: (conversationId: string) => void
|
||||
getConversationTitle: (conversationId: string) => Promise<string>
|
||||
getDid: (address: string) => Promise<string>
|
||||
@ -62,7 +61,8 @@ const OrbisContext = createContext({} as IOrbisProvider)
|
||||
|
||||
const orbis: IOrbis = new Orbis()
|
||||
const NOTIFICATION_REFRESH_INTERVAL = 5000
|
||||
const CONVERSATION_CONTEXT = 'ocean_market' // Can be changed to whatever
|
||||
const CONVERSATION_CONTEXT =
|
||||
process.env.NEXT_PUBLIC_ORBIS_CONTEXT || 'ocean_market' // Can be changed to whatever
|
||||
|
||||
function OrbisProvider({ children }: { children: ReactNode }): ReactElement {
|
||||
const { web3Provider, accountId } = useWeb3()
|
||||
@ -312,16 +312,23 @@ function OrbisProvider({ children }: { children: ReactNode }): ReactElement {
|
||||
return filteredConversations
|
||||
}
|
||||
|
||||
const getConversation = async (userDid: string) => {
|
||||
if (!account || !userDid) return null
|
||||
const getConversation = async (conversationId: string) => {
|
||||
if (!conversationId) return null
|
||||
const { data, error } = await orbis.getConversation(conversationId)
|
||||
if (error || !data) {
|
||||
await sleep(2000)
|
||||
await getConversation(conversationId)
|
||||
} else {
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
console.log('has account and target userDid')
|
||||
const getConversationByDid = async (userDid: string) => {
|
||||
if (!account || !userDid) return null
|
||||
|
||||
const _conversations = await getConversations(account?.did)
|
||||
if (!_conversations.length) return null
|
||||
|
||||
console.log('has conversations')
|
||||
|
||||
const filteredConversations = _conversations.filter(
|
||||
(conversation: IOrbisConversation) => {
|
||||
return (
|
||||
@ -333,67 +340,28 @@ function OrbisProvider({ children }: { children: ReactNode }): ReactElement {
|
||||
|
||||
if (!filteredConversations.length) return null
|
||||
|
||||
console.log('has filtered conversations')
|
||||
|
||||
return filteredConversations[0]
|
||||
}
|
||||
|
||||
const createConversation = async (userDid: string) => {
|
||||
let _account = account
|
||||
if (!_account) {
|
||||
// Check connection and force connect
|
||||
_account = await checkOrbisConnection({
|
||||
address: accountId,
|
||||
autoConnect: true
|
||||
})
|
||||
}
|
||||
const createConversation = async (recipients: string[]) => {
|
||||
if (!recipients.length) return null
|
||||
|
||||
if (!hasLit) {
|
||||
const res = await connectLit()
|
||||
if (res.status !== 200) {
|
||||
alert('Error connecting to Lit.')
|
||||
return
|
||||
}
|
||||
}
|
||||
const res = await orbis.createConversation({
|
||||
recipients,
|
||||
context: CONVERSATION_CONTEXT
|
||||
})
|
||||
|
||||
let existingConversation = conversations.find(
|
||||
(conversation: IOrbisConversation) => {
|
||||
return conversation.recipients.includes(userDid)
|
||||
}
|
||||
)
|
||||
|
||||
// Refetch to make sure we have the latest conversations
|
||||
if (!existingConversation) {
|
||||
const _conversations = await getConversations(_account?.did)
|
||||
existingConversation = _conversations.find(
|
||||
(conversation: IOrbisConversation) => {
|
||||
return conversation.recipients.includes(userDid)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (existingConversation) {
|
||||
setConversationId(existingConversation.stream_id)
|
||||
setOpenConversations(true)
|
||||
} else {
|
||||
const res = await orbis.createConversation({
|
||||
recipients: [userDid],
|
||||
context: CONVERSATION_CONTEXT
|
||||
})
|
||||
if (res.status === 200) {
|
||||
setTimeout(async () => {
|
||||
const { data, error } = await orbis.getConversation(res.doc)
|
||||
if (!error && data) {
|
||||
setConversations([data, ...conversations])
|
||||
}
|
||||
setConversationId(res.doc)
|
||||
setOpenConversations(true)
|
||||
}, 2000)
|
||||
if (res.status === 200) {
|
||||
await sleep(2000)
|
||||
const _newConversation = await getConversation(res.doc)
|
||||
if (_newConversation) {
|
||||
setConversations([_newConversation, ...conversations])
|
||||
return _newConversation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// const createConversationV2 = async (userDid: string) => {
|
||||
// const createConversation = async (userDid: string) => {
|
||||
// let _account = account
|
||||
// if (!_account) {
|
||||
// // Check connection and force connect
|
||||
@ -411,14 +379,22 @@ function OrbisProvider({ children }: { children: ReactNode }): ReactElement {
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Refetch to make sure we have the latest conversations
|
||||
// const _conversations = await getConversations(_account?.did)
|
||||
// const existingConversation = _conversations.find(
|
||||
// let existingConversation = conversations.find(
|
||||
// (conversation: IOrbisConversation) => {
|
||||
// return conversation.recipients.includes(userDid)
|
||||
// }
|
||||
// )
|
||||
|
||||
// // Refetch to make sure we have the latest conversations
|
||||
// if (!existingConversation) {
|
||||
// const _conversations = await getConversations(_account?.did)
|
||||
// existingConversation = _conversations.find(
|
||||
// (conversation: IOrbisConversation) => {
|
||||
// return conversation.recipients.includes(userDid)
|
||||
// }
|
||||
// )
|
||||
// }
|
||||
|
||||
// if (existingConversation) {
|
||||
// setConversationId(existingConversation.stream_id)
|
||||
// setOpenConversations(true)
|
||||
@ -492,10 +468,6 @@ function OrbisProvider({ children }: { children: ReactNode }): ReactElement {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [account])
|
||||
|
||||
useEffect(() => {
|
||||
console.log({ newConversation, activeConversationTitle })
|
||||
}, [newConversation, activeConversationTitle])
|
||||
|
||||
return (
|
||||
<OrbisContext.Provider
|
||||
value={{
|
||||
@ -516,7 +488,7 @@ function OrbisProvider({ children }: { children: ReactNode }): ReactElement {
|
||||
setNewConversation,
|
||||
setOpenConversations,
|
||||
setConversationId,
|
||||
getConversation,
|
||||
getConversationByDid,
|
||||
createConversation,
|
||||
clearMessageNotifs,
|
||||
getConversationTitle,
|
||||
|
@ -91,3 +91,6 @@ export function formatMessage(
|
||||
|
||||
return body
|
||||
}
|
||||
|
||||
export const sleep = (ms: number) =>
|
||||
new Promise((resolve) => setTimeout(resolve, ms))
|
||||
|
@ -45,6 +45,10 @@
|
||||
font-size: var(--font-size-mini);
|
||||
}
|
||||
|
||||
.btnOptions {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.blockies {
|
||||
aspect-ratio: 1/1;
|
||||
flex-shrink: 0;
|
||||
|
@ -142,8 +142,8 @@ export default function Post({
|
||||
<Avatar accountId={address} className={styles.blockies} />
|
||||
<div className={styles.content}>
|
||||
<div className={styles.profile}>
|
||||
<Link href={`/profile/${address}`}>
|
||||
<a className={styles.name}>{accountTruncate(address)}</a>
|
||||
<Link href={`/profile/${address}`} className={styles.name}>
|
||||
{accountTruncate(address)}
|
||||
</Link>
|
||||
<span>•</span>
|
||||
<div className={styles.metadata}>
|
||||
@ -168,6 +168,7 @@ export default function Post({
|
||||
context={postClone?.context}
|
||||
editPost={postClone}
|
||||
callback={callbackEdit}
|
||||
cancelEdit={() => setIsEditing(false)}
|
||||
/>
|
||||
)}
|
||||
{hideOverflow && (
|
||||
@ -219,15 +220,12 @@ export default function Post({
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
trigger="click"
|
||||
zIndex={21}
|
||||
placement={'top'}
|
||||
className={styles.btnOptions}
|
||||
>
|
||||
<button
|
||||
title="Options"
|
||||
onClick={() => console.log('clicked')}
|
||||
>
|
||||
<Ellipsis role="img" aria-label="Options" />
|
||||
</button>
|
||||
<Ellipsis role="img" aria-label="Options" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
|
@ -38,6 +38,27 @@
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.postbox .editActions {
|
||||
display: flex;
|
||||
gap: calc(var(--spacer) / 4);
|
||||
}
|
||||
|
||||
.postbox .editActions button {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.postbox .cancelEdit {
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.postbox .saveEdit {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.connectWallet {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useRef, useState, KeyboardEvent } from 'react'
|
||||
import React, { useRef, useState, useEffect, KeyboardEvent } from 'react'
|
||||
import styles from './Postbox.module.css'
|
||||
import walletStyles from '../../../Header/Wallet/Account.module.css'
|
||||
import { EmojiClickData } from 'emoji-picker-react'
|
||||
@ -16,6 +16,7 @@ export default function Postbox({
|
||||
editPost = null,
|
||||
enterToSend = false,
|
||||
cancelReplyTo,
|
||||
cancelEdit,
|
||||
callback
|
||||
}: {
|
||||
context: string
|
||||
@ -24,6 +25,7 @@ export default function Postbox({
|
||||
editPost?: IOrbisPost
|
||||
enterToSend?: boolean
|
||||
cancelReplyTo?: () => void
|
||||
cancelEdit?: () => void
|
||||
callback: (value: IOrbisPost | IOrbisPost['content']) => void
|
||||
}) {
|
||||
const { orbis, account, checkOrbisConnection } = useOrbis()
|
||||
@ -135,6 +137,12 @@ export default function Postbox({
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (editPost) {
|
||||
postBoxArea.current.innerText = editPost.content.body
|
||||
}
|
||||
}, [editPost])
|
||||
|
||||
if (!accountId) {
|
||||
return (
|
||||
<div className={styles.postbox}>
|
||||
@ -194,17 +202,33 @@ export default function Postbox({
|
||||
/>
|
||||
<EmojiPicker onEmojiClick={onEmojiClick} />
|
||||
</div>
|
||||
<div className={styles.sendButtonWrap}>
|
||||
<Button
|
||||
style="primary"
|
||||
type="submit"
|
||||
size="small"
|
||||
disabled={false}
|
||||
onClick={share}
|
||||
>
|
||||
Send
|
||||
</Button>
|
||||
</div>
|
||||
{editPost ? (
|
||||
<div className={styles.editActions}>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.cancelEdit}
|
||||
onClick={cancelEdit}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<span>·</span>
|
||||
<button type="submit" className={styles.saveEdit} onClick={share}>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.sendButtonWrap}>
|
||||
<Button
|
||||
style="primary"
|
||||
type="submit"
|
||||
size="small"
|
||||
disabled={false}
|
||||
onClick={share}
|
||||
>
|
||||
Send
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import React, { useState, useEffect, useRef } from 'react'
|
||||
import { useOrbis } from '@context/Orbis'
|
||||
import { useIsMounted } from '@hooks/useIsMounted'
|
||||
import { useInterval } from '@hooks/useInterval'
|
||||
import { throttle } from '@utils/throttle'
|
||||
import Time from '@shared/atoms/Time'
|
||||
@ -18,9 +17,9 @@ export default function DmConversation() {
|
||||
connectLit,
|
||||
clearMessageNotifs
|
||||
} = useOrbis()
|
||||
const isMounted = useIsMounted()
|
||||
|
||||
const messagesWrapper = useRef(null)
|
||||
const [isInitialized, setIsInitialized] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [messages, setMessages] = useState<IOrbisMessage[]>([])
|
||||
const [currentPage, setCurrentPage] = useState(0)
|
||||
@ -36,12 +35,16 @@ export default function DmConversation() {
|
||||
}, 300)
|
||||
}
|
||||
|
||||
const getMessages = async (polling = false) => {
|
||||
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 ? 0 : currentPage
|
||||
const _page = polling || reset ? 0 : currentPage
|
||||
let _messages = reset ? [] : [...messages]
|
||||
const { data, error } = await orbis.getMessages(conversationId, _page)
|
||||
|
||||
if (error) {
|
||||
@ -52,30 +55,34 @@ export default function DmConversation() {
|
||||
data.reverse()
|
||||
if (!polling) {
|
||||
setHasMore(data.length >= 50)
|
||||
const _messages = [...data, ...messages]
|
||||
_messages = [...data, ..._messages]
|
||||
setMessages(_messages)
|
||||
if (currentPage === 0) {
|
||||
clearMessageNotifs(conversationId)
|
||||
scrollToBottom()
|
||||
}
|
||||
setCurrentPage((prev) => prev + 1)
|
||||
setCurrentPage(_page + 1)
|
||||
} else {
|
||||
const unique = data.filter(
|
||||
(a) => !messages.some((b) => a.stream_id === b.stream_id)
|
||||
(a) => !_messages.some((b) => a.stream_id === b.stream_id)
|
||||
)
|
||||
setNewMessages(unique.length)
|
||||
setMessages([...messages, ...unique])
|
||||
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(true)
|
||||
getMessages({ polling: true })
|
||||
},
|
||||
isLoading || !hasLit || !messages.length ? false : 15000
|
||||
!isLoading && hasLit && isInitialized ? 5000 : false
|
||||
)
|
||||
|
||||
const showTime = (streamId: string): boolean => {
|
||||
@ -122,20 +129,18 @@ export default function DmConversation() {
|
||||
}, 1000)
|
||||
|
||||
useEffect(() => {
|
||||
if (isMounted) {
|
||||
if (
|
||||
conversationId &&
|
||||
!conversationId.startsWith('new-') &&
|
||||
orbis &&
|
||||
hasLit
|
||||
) {
|
||||
getMessages()
|
||||
} else {
|
||||
setMessages([])
|
||||
}
|
||||
setIsInitialized(false)
|
||||
setMessages([])
|
||||
if (
|
||||
conversationId &&
|
||||
!conversationId.startsWith('new-') &&
|
||||
orbis &&
|
||||
hasLit
|
||||
) {
|
||||
getMessages({ reset: true })
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [conversationId, orbis, hasLit, isMounted])
|
||||
}, [conversationId, orbis, hasLit])
|
||||
|
||||
useEffect(() => {
|
||||
const el = messagesWrapper.current
|
||||
@ -210,7 +215,7 @@ export default function DmConversation() {
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<Postbox conversationId={conversationId} callback={callback} />
|
||||
<Postbox callback={callback} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
@ -29,7 +29,6 @@ export default function Header() {
|
||||
) => {
|
||||
e.preventDefault()
|
||||
const target = e.target as HTMLElement
|
||||
console.log(target)
|
||||
const { role } = target.dataset
|
||||
if (role) {
|
||||
if (role === 'back-button') {
|
||||
@ -38,17 +37,14 @@ export default function Header() {
|
||||
} else {
|
||||
let _address = ''
|
||||
if (newConversation) {
|
||||
console.log(newConversation.recipients)
|
||||
_address = didToAddress(newConversation.recipients[0])
|
||||
} else {
|
||||
console.log(accountId)
|
||||
const conversation = conversations.find(
|
||||
(c) => c.stream_id === conversationId
|
||||
)
|
||||
const recipients = conversation.recipients.filter(
|
||||
(r) => didToAddress(r) !== accountId.toLowerCase()
|
||||
)
|
||||
console.log(recipients)
|
||||
_address = didToAddress(recipients[0])
|
||||
}
|
||||
navigator.clipboard.writeText(_address)
|
||||
@ -96,7 +92,6 @@ export default function Header() {
|
||||
aria-label="button"
|
||||
data-role="back-button"
|
||||
className={styles.btnBack}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<ArrowBack
|
||||
role="img"
|
||||
@ -109,7 +104,7 @@ export default function Header() {
|
||||
<>
|
||||
<span>{activeConversationTitle}</span>
|
||||
<button
|
||||
onClick={handleClick}
|
||||
type="button"
|
||||
data-role="copy-address"
|
||||
title="Copy Address"
|
||||
className={styles.btnCopy}
|
||||
|
@ -4,24 +4,31 @@ import { EmojiClickData } from 'emoji-picker-react'
|
||||
import { useOrbis } from '@context/Orbis'
|
||||
import EmojiPicker from '../EmojiPicker'
|
||||
import { accountTruncate } from '@utils/web3'
|
||||
import { didToAddress } from '@utils/orbis'
|
||||
import { didToAddress, sleep } from '@utils/orbis'
|
||||
|
||||
export default function Postbox({
|
||||
conversationId,
|
||||
replyTo = null,
|
||||
cancelReplyTo,
|
||||
callback
|
||||
}: {
|
||||
conversationId: string
|
||||
replyTo?: IOrbisMessage
|
||||
cancelReplyTo?: () => void
|
||||
callback: (value: IOrbisMessage) => void
|
||||
}) {
|
||||
const [focusOffset, setFocusOffset] = useState<number | undefined>()
|
||||
const [focusNode, setFocusNode] = useState<Node | undefined>()
|
||||
const [isSending, setIsSending] = useState<boolean>(false)
|
||||
|
||||
const postBoxArea = useRef(null)
|
||||
const { orbis, account } = useOrbis()
|
||||
const {
|
||||
orbis,
|
||||
account,
|
||||
conversationId,
|
||||
newConversation,
|
||||
createConversation,
|
||||
setConversationId,
|
||||
setNewConversation
|
||||
} = useOrbis()
|
||||
|
||||
const saveCaretPos = () => {
|
||||
const sel = document.getSelection()
|
||||
@ -38,7 +45,22 @@ export default function Postbox({
|
||||
}
|
||||
|
||||
const share = async () => {
|
||||
if (!account) return false
|
||||
if (!account || isSending) return false
|
||||
|
||||
setIsSending(true)
|
||||
|
||||
let _conversationId = conversationId
|
||||
if (_conversationId.startsWith('new-') && newConversation) {
|
||||
const _newConversation = await createConversation(
|
||||
newConversation.recipients
|
||||
)
|
||||
if (_newConversation) {
|
||||
_conversationId = _newConversation.stream_id
|
||||
setConversationId(_conversationId)
|
||||
setNewConversation(null)
|
||||
await sleep(1000)
|
||||
}
|
||||
}
|
||||
|
||||
const body = postBoxArea.current.innerText
|
||||
|
||||
@ -72,7 +94,7 @@ export default function Postbox({
|
||||
postBoxArea.current.innerText = ''
|
||||
|
||||
const res = await orbis.sendMessage({
|
||||
conversation_id: conversationId,
|
||||
conversation_id: _conversationId,
|
||||
body
|
||||
})
|
||||
|
||||
@ -80,12 +102,14 @@ export default function Postbox({
|
||||
_callbackContent.stream_id = res.doc
|
||||
if (callback) callback(_callbackContent)
|
||||
}
|
||||
|
||||
setIsSending(false)
|
||||
}
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {
|
||||
if (!e.key) return
|
||||
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
if (e.key === 'Enter' && !e.shiftKey && !isSending) {
|
||||
// Don't generate a new line
|
||||
e.preventDefault()
|
||||
share()
|
||||
|
@ -28,7 +28,7 @@ const DmButton = ({ accountId }: { accountId: string }) => {
|
||||
const { accountId: ownAccountId, connect } = useWeb3()
|
||||
const {
|
||||
checkOrbisConnection,
|
||||
getConversation,
|
||||
getConversationByDid,
|
||||
setNewConversation,
|
||||
setConversationId,
|
||||
setOpenConversations,
|
||||
@ -72,7 +72,7 @@ const DmButton = ({ accountId }: { accountId: string }) => {
|
||||
handleActivation()
|
||||
} else {
|
||||
setIsCreatingConversation(true)
|
||||
const conversation = await getConversation(userDid)
|
||||
const conversation = await getConversationByDid(userDid)
|
||||
if (conversation) {
|
||||
setConversationId(conversation.stream_id)
|
||||
} else {
|
||||
@ -80,11 +80,10 @@ const DmButton = ({ accountId }: { accountId: string }) => {
|
||||
const suffix =
|
||||
profile && profile?.name
|
||||
? profile?.name
|
||||
: accountTruncate(accountId)
|
||||
: accountTruncate(accountId.toLowerCase())
|
||||
|
||||
setConversationId(`new-${suffix}`)
|
||||
setNewConversation({
|
||||
name: suffix,
|
||||
recipients: [userDid]
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user