mirror of
https://github.com/Qortal/q-blog.git
synced 2025-01-30 14:52:19 +00:00
fix id issue
This commit is contained in:
parent
7391940b13
commit
a1d8f706b8
@ -15,7 +15,6 @@ import GlobalWrapper from './wrappers/GlobalWrapper'
|
|||||||
import DownloadWrapper from './wrappers/DownloadWrapper'
|
import DownloadWrapper from './wrappers/DownloadWrapper'
|
||||||
import Notification from './components/common/Notification/Notification'
|
import Notification from './components/common/Notification/Notification'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { Mail } from './pages/Mail/Mail'
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const themeColor = window._qdnTheme
|
const themeColor = window._qdnTheme
|
||||||
@ -54,7 +53,6 @@ function App() {
|
|||||||
path="/subscriptions"
|
path="/subscriptions"
|
||||||
element={<BlogList mode="subscriptions" />}
|
element={<BlogList mode="subscriptions" />}
|
||||||
/>
|
/>
|
||||||
<Route path="/mail" element={<Mail />} />
|
|
||||||
<Route path="/" element={<BlogList />} />
|
<Route path="/" element={<BlogList />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</GlobalWrapper>
|
</GlobalWrapper>
|
||||||
|
@ -1,469 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
|
||||||
import {
|
|
||||||
addPosts,
|
|
||||||
addToHashMap,
|
|
||||||
BlogPost,
|
|
||||||
populateFavorites,
|
|
||||||
setCountNewPosts,
|
|
||||||
upsertFilteredPosts,
|
|
||||||
upsertPosts,
|
|
||||||
upsertPostsBeginning,
|
|
||||||
upsertSubscriptionPosts
|
|
||||||
} from '../state/features/blogSlice'
|
|
||||||
import {
|
|
||||||
setCurrentBlog,
|
|
||||||
setIsLoadingGlobal,
|
|
||||||
setUserAvatarHash
|
|
||||||
} from '../state/features/globalSlice'
|
|
||||||
import { RootState } from '../state/store'
|
|
||||||
import { fetchAndEvaluatePosts } from '../utils/fetchPosts'
|
|
||||||
import { fetchAndEvaluateMail } from '../utils/fetchMail'
|
|
||||||
import {
|
|
||||||
addToHashMapMail,
|
|
||||||
upsertMessages,
|
|
||||||
upsertMessagesBeginning
|
|
||||||
} from '../state/features/mailSlice'
|
|
||||||
import { MAIL_SERVICE_TYPE } from '../constants/mail'
|
|
||||||
|
|
||||||
export const useFetchMail = () => {
|
|
||||||
const dispatch = useDispatch()
|
|
||||||
const hashMapPosts = useSelector(
|
|
||||||
(state: RootState) => state.blog.hashMapPosts
|
|
||||||
)
|
|
||||||
const hashMapMailMessages = useSelector(
|
|
||||||
(state: RootState) => state.mail.hashMapMailMessages
|
|
||||||
)
|
|
||||||
const posts = useSelector((state: RootState) => state.blog.posts)
|
|
||||||
const mailMessages = useSelector(
|
|
||||||
(state: RootState) => state.mail.mailMessages
|
|
||||||
)
|
|
||||||
|
|
||||||
const filteredPosts = useSelector(
|
|
||||||
(state: RootState) => state.blog.filteredPosts
|
|
||||||
)
|
|
||||||
const favoritesLocal = useSelector(
|
|
||||||
(state: RootState) => state.blog.favoritesLocal
|
|
||||||
)
|
|
||||||
const favorites = useSelector((state: RootState) => state.blog.favorites)
|
|
||||||
const subscriptionPosts = useSelector(
|
|
||||||
(state: RootState) => state.blog.subscriptionPosts
|
|
||||||
)
|
|
||||||
const subscriptions = useSelector(
|
|
||||||
(state: RootState) => state.blog.subscriptions
|
|
||||||
)
|
|
||||||
|
|
||||||
const checkAndUpdatePost = React.useCallback(
|
|
||||||
(post: BlogPost) => {
|
|
||||||
// Check if the post exists in hashMapPosts
|
|
||||||
const existingPost = hashMapPosts[post.id]
|
|
||||||
if (!existingPost) {
|
|
||||||
// If the post doesn't exist, add it to hashMapPosts
|
|
||||||
return true
|
|
||||||
} else if (
|
|
||||||
post?.updated &&
|
|
||||||
existingPost?.updated &&
|
|
||||||
(!existingPost?.updated || post?.updated) > existingPost?.updated
|
|
||||||
) {
|
|
||||||
// If the post exists and its updated is more recent than the existing post's updated, update it in hashMapPosts
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[hashMapPosts]
|
|
||||||
)
|
|
||||||
|
|
||||||
const getBlogPost = async (user: string, postId: string, content: any) => {
|
|
||||||
const res = await fetchAndEvaluatePosts({
|
|
||||||
user,
|
|
||||||
postId,
|
|
||||||
content
|
|
||||||
})
|
|
||||||
|
|
||||||
dispatch(addToHashMap(res))
|
|
||||||
}
|
|
||||||
|
|
||||||
const getMailMessage = async (user: string, postId: string, content: any) => {
|
|
||||||
const res = await fetchAndEvaluateMail({
|
|
||||||
user,
|
|
||||||
postId,
|
|
||||||
content
|
|
||||||
})
|
|
||||||
|
|
||||||
dispatch(addToHashMapMail(res))
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkNewMessages = React.useCallback(
|
|
||||||
async (recipientName: string, recipientAddress: string) => {
|
|
||||||
try {
|
|
||||||
const query = `qortal_qmail_${recipientName.slice(
|
|
||||||
0,
|
|
||||||
20
|
|
||||||
)}_${recipientAddress.slice(-6)}_mail_`
|
|
||||||
const url = `/arbitrary/resources/search?mode=ALL&service=${MAIL_SERVICE_TYPE}&query=${query}&limit=20&includemetadata=true&reverse=true&excludeblocked=true`
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const responseData = await response.json()
|
|
||||||
const latestPost = mailMessages[0]
|
|
||||||
if (!latestPost) return
|
|
||||||
const findPost = responseData?.findIndex(
|
|
||||||
(item: any) => item?.identifier === latestPost?.id
|
|
||||||
)
|
|
||||||
if (findPost === -1) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const newArray = responseData.slice(0, findPost)
|
|
||||||
const structureData = newArray.map((post: any): BlogPost => {
|
|
||||||
return {
|
|
||||||
title: post?.metadata?.title,
|
|
||||||
category: post?.metadata?.category,
|
|
||||||
categoryName: post?.metadata?.categoryName,
|
|
||||||
tags: post?.metadata?.tags || [],
|
|
||||||
description: post?.metadata?.description,
|
|
||||||
createdAt: post?.created,
|
|
||||||
updated: post?.updated,
|
|
||||||
user: post.name,
|
|
||||||
id: post.identifier
|
|
||||||
}
|
|
||||||
})
|
|
||||||
dispatch(upsertMessagesBeginning(structureData))
|
|
||||||
return
|
|
||||||
} catch (error) {}
|
|
||||||
},
|
|
||||||
[mailMessages]
|
|
||||||
)
|
|
||||||
|
|
||||||
const getNewPosts = React.useCallback(async () => {
|
|
||||||
try {
|
|
||||||
dispatch(setIsLoadingGlobal(true))
|
|
||||||
dispatch(setCountNewPosts(0))
|
|
||||||
const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_POST&query=q-blog-&limit=20&includemetadata=true&reverse=true&excludeblocked=true`
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const responseData = await response.json()
|
|
||||||
const latestPost = posts[0]
|
|
||||||
if (!latestPost) return
|
|
||||||
const findPost = responseData?.findIndex(
|
|
||||||
(item: any) => item?.identifier === latestPost?.id
|
|
||||||
)
|
|
||||||
let fetchAll = responseData
|
|
||||||
let willFetchAll = true
|
|
||||||
if (findPost !== -1) {
|
|
||||||
willFetchAll = false
|
|
||||||
fetchAll = responseData.slice(0, findPost)
|
|
||||||
}
|
|
||||||
|
|
||||||
const structureData = fetchAll.map((post: any): BlogPost => {
|
|
||||||
return {
|
|
||||||
title: post?.metadata?.title,
|
|
||||||
category: post?.metadata?.category,
|
|
||||||
categoryName: post?.metadata?.categoryName,
|
|
||||||
tags: post?.metadata?.tags || [],
|
|
||||||
description: post?.metadata?.description,
|
|
||||||
createdAt: post?.created,
|
|
||||||
updated: post?.updated,
|
|
||||||
user: post.name,
|
|
||||||
postImage: '',
|
|
||||||
id: post.identifier
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (!willFetchAll) {
|
|
||||||
dispatch(upsertPostsBeginning(structureData))
|
|
||||||
}
|
|
||||||
if (willFetchAll) {
|
|
||||||
dispatch(addPosts(structureData))
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const content of structureData) {
|
|
||||||
if (content.user && content.id) {
|
|
||||||
const res = checkAndUpdatePost(content)
|
|
||||||
if (res) {
|
|
||||||
getBlogPost(content.user, content.id, content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
} finally {
|
|
||||||
dispatch(setIsLoadingGlobal(false))
|
|
||||||
}
|
|
||||||
}, [posts, hashMapPosts])
|
|
||||||
|
|
||||||
const getBlogPosts = React.useCallback(async () => {
|
|
||||||
try {
|
|
||||||
const offset = posts.length
|
|
||||||
|
|
||||||
dispatch(setIsLoadingGlobal(true))
|
|
||||||
const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_POST&query=q-blog-&limit=20&includemetadata=true&offset=${offset}&reverse=true&excludeblocked=true`
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const responseData = await response.json()
|
|
||||||
const structureData = responseData.map((post: any): BlogPost => {
|
|
||||||
return {
|
|
||||||
title: post?.metadata?.title,
|
|
||||||
category: post?.metadata?.category,
|
|
||||||
categoryName: post?.metadata?.categoryName,
|
|
||||||
tags: post?.metadata?.tags || [],
|
|
||||||
description: post?.metadata?.description,
|
|
||||||
createdAt: post?.created,
|
|
||||||
updated: post?.updated,
|
|
||||||
user: post.name,
|
|
||||||
postImage: '',
|
|
||||||
id: post.identifier
|
|
||||||
}
|
|
||||||
})
|
|
||||||
dispatch(upsertPosts(structureData))
|
|
||||||
|
|
||||||
for (const content of structureData) {
|
|
||||||
if (content.user && content.id) {
|
|
||||||
const res = checkAndUpdatePost(content)
|
|
||||||
if (res) {
|
|
||||||
getBlogPost(content.user, content.id, content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
} finally {
|
|
||||||
dispatch(setIsLoadingGlobal(false))
|
|
||||||
}
|
|
||||||
}, [posts, hashMapPosts])
|
|
||||||
|
|
||||||
const getAvatar = async (user: string) => {
|
|
||||||
try {
|
|
||||||
let url = await qortalRequest({
|
|
||||||
action: 'GET_QDN_RESOURCE_URL',
|
|
||||||
name: user,
|
|
||||||
service: 'THUMBNAIL',
|
|
||||||
identifier: 'qortal_avatar'
|
|
||||||
})
|
|
||||||
dispatch(
|
|
||||||
setUserAvatarHash({
|
|
||||||
name: user,
|
|
||||||
url
|
|
||||||
})
|
|
||||||
)
|
|
||||||
} catch (error) {}
|
|
||||||
}
|
|
||||||
const getMailMessages = React.useCallback(
|
|
||||||
async (recipientName: string, recipientAddress: string) => {
|
|
||||||
try {
|
|
||||||
const offset = mailMessages.length
|
|
||||||
|
|
||||||
dispatch(setIsLoadingGlobal(true))
|
|
||||||
const query = `qortal_qmail_${recipientName.slice(
|
|
||||||
0,
|
|
||||||
20
|
|
||||||
)}_${recipientAddress.slice(-6)}_mail_`
|
|
||||||
const url = `/arbitrary/resources/search?mode=ALL&service=${MAIL_SERVICE_TYPE}&query=${query}&limit=20&includemetadata=true&offset=${offset}&reverse=true&excludeblocked=true`
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const responseData = await response.json()
|
|
||||||
const structureData = responseData.map((post: any): BlogPost => {
|
|
||||||
return {
|
|
||||||
title: post?.metadata?.title,
|
|
||||||
category: post?.metadata?.category,
|
|
||||||
categoryName: post?.metadata?.categoryName,
|
|
||||||
tags: post?.metadata?.tags || [],
|
|
||||||
description: post?.metadata?.description,
|
|
||||||
createdAt: post?.created,
|
|
||||||
updated: post?.updated,
|
|
||||||
user: post.name,
|
|
||||||
id: post.identifier
|
|
||||||
}
|
|
||||||
})
|
|
||||||
dispatch(upsertMessages(structureData))
|
|
||||||
|
|
||||||
for (const content of structureData) {
|
|
||||||
if (content.user && content.id) {
|
|
||||||
getAvatar(content.user)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
} finally {
|
|
||||||
dispatch(setIsLoadingGlobal(false))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[mailMessages, hashMapMailMessages]
|
|
||||||
)
|
|
||||||
const getBlogFilteredPosts = React.useCallback(
|
|
||||||
async (filterValue: string) => {
|
|
||||||
try {
|
|
||||||
const offset = filteredPosts.length
|
|
||||||
|
|
||||||
dispatch(setIsLoadingGlobal(true))
|
|
||||||
const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_POST&query=q-blog-&limit=20&includemetadata=true&offset=${offset}&reverse=true&excludeblocked=true&name=${filterValue}`
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const responseData = await response.json()
|
|
||||||
const structureData = responseData.map((post: any): BlogPost => {
|
|
||||||
return {
|
|
||||||
title: post?.metadata?.title,
|
|
||||||
category: post?.metadata?.category,
|
|
||||||
categoryName: post?.metadata?.categoryName,
|
|
||||||
tags: post?.metadata?.tags || [],
|
|
||||||
description: post?.metadata?.description,
|
|
||||||
createdAt: post?.created,
|
|
||||||
updated: post?.updated,
|
|
||||||
user: post.name,
|
|
||||||
postImage: '',
|
|
||||||
id: post.identifier
|
|
||||||
}
|
|
||||||
})
|
|
||||||
dispatch(upsertFilteredPosts(structureData))
|
|
||||||
|
|
||||||
for (const content of structureData) {
|
|
||||||
if (content.user && content.id) {
|
|
||||||
const res = checkAndUpdatePost(content)
|
|
||||||
if (res) {
|
|
||||||
getBlogPost(content.user, content.id, content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
} finally {
|
|
||||||
dispatch(setIsLoadingGlobal(false))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[filteredPosts, hashMapPosts]
|
|
||||||
)
|
|
||||||
|
|
||||||
const getBlogPostsSubscriptions = React.useCallback(
|
|
||||||
async (username: string) => {
|
|
||||||
try {
|
|
||||||
const offset = subscriptionPosts.length
|
|
||||||
dispatch(setIsLoadingGlobal(true))
|
|
||||||
const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_POST&query=q-blog-&limit=20&includemetadata=true&offset=${offset}&reverse=true&namefilter=q-blog-subscriptions-${username}&excludeblocked=true`
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const responseData = await response.json()
|
|
||||||
const structureData = responseData.map((post: any): BlogPost => {
|
|
||||||
return {
|
|
||||||
title: post?.metadata?.title,
|
|
||||||
category: post?.metadata?.category,
|
|
||||||
categoryName: post?.metadata?.categoryName,
|
|
||||||
tags: post?.metadata?.tags || [],
|
|
||||||
description: post?.metadata?.description,
|
|
||||||
createdAt: '',
|
|
||||||
user: post.name,
|
|
||||||
postImage: '',
|
|
||||||
id: post.identifier
|
|
||||||
}
|
|
||||||
})
|
|
||||||
dispatch(upsertSubscriptionPosts(structureData))
|
|
||||||
|
|
||||||
for (const content of structureData) {
|
|
||||||
if (content.user && content.id) {
|
|
||||||
const res = checkAndUpdatePost(content)
|
|
||||||
if (res) {
|
|
||||||
getBlogPost(content.user, content.id, content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
} finally {
|
|
||||||
dispatch(setIsLoadingGlobal(false))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[subscriptionPosts, hashMapPosts, subscriptions]
|
|
||||||
)
|
|
||||||
|
|
||||||
const getBlogPostsFavorites = React.useCallback(async () => {
|
|
||||||
try {
|
|
||||||
const offset = favorites.length
|
|
||||||
const favSlice = (favoritesLocal || []).slice(offset, 20)
|
|
||||||
let favs = []
|
|
||||||
for (const item of favSlice) {
|
|
||||||
try {
|
|
||||||
// await qortalRequest({
|
|
||||||
// action: "SEARCH_QDN_RESOURCES",
|
|
||||||
// service: "THUMBNAIL",
|
|
||||||
// query: "search query goes here", // Optional - searches both "identifier" and "name" fields
|
|
||||||
// identifier: "search query goes here", // Optional - searches only the "identifier" field
|
|
||||||
// name: "search query goes here", // Optional - searches only the "name" field
|
|
||||||
// prefix: false, // Optional - if true, only the beginning of fields are matched in all of the above filters
|
|
||||||
// default: false, // Optional - if true, only resources without identifiers are returned
|
|
||||||
// includeStatus: false, // Optional - will take time to respond, so only request if necessary
|
|
||||||
// includeMetadata: false, // Optional - will take time to respond, so only request if necessary
|
|
||||||
// limit: 100,
|
|
||||||
// offset: 0,
|
|
||||||
// reverse: true
|
|
||||||
// });
|
|
||||||
//TODO - NAME SHOULD BE EXACT
|
|
||||||
const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_POST&identifier=${item.id}&exactmatchnames=true&name=${item.user}&limit=20&includemetadata=true&reverse=true&excludeblocked=true`
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const data = await response.json()
|
|
||||||
//
|
|
||||||
if (data.length > 0) {
|
|
||||||
favs.push(data[0])
|
|
||||||
}
|
|
||||||
} catch (error) {}
|
|
||||||
}
|
|
||||||
const structureData = favs.map((post: any): BlogPost => {
|
|
||||||
return {
|
|
||||||
title: post?.metadata?.title,
|
|
||||||
category: post?.metadata?.category,
|
|
||||||
categoryName: post?.metadata?.categoryName,
|
|
||||||
tags: post?.metadata?.tags || [],
|
|
||||||
description: post?.metadata?.description,
|
|
||||||
createdAt: '',
|
|
||||||
user: post.name,
|
|
||||||
postImage: '',
|
|
||||||
id: post.identifier
|
|
||||||
}
|
|
||||||
})
|
|
||||||
dispatch(populateFavorites(structureData))
|
|
||||||
|
|
||||||
for (const content of structureData) {
|
|
||||||
if (content.user && content.id) {
|
|
||||||
const res = checkAndUpdatePost(content)
|
|
||||||
if (res) {
|
|
||||||
getBlogPost(content.user, content.id, content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
} finally {
|
|
||||||
}
|
|
||||||
}, [hashMapPosts, favoritesLocal])
|
|
||||||
return {
|
|
||||||
getBlogPosts,
|
|
||||||
getBlogPostsFavorites,
|
|
||||||
getBlogPostsSubscriptions,
|
|
||||||
checkAndUpdatePost,
|
|
||||||
getBlogPost,
|
|
||||||
hashMapPosts,
|
|
||||||
checkNewMessages,
|
|
||||||
getNewPosts,
|
|
||||||
getBlogFilteredPosts,
|
|
||||||
getMailMessages
|
|
||||||
}
|
|
||||||
}
|
|
@ -41,7 +41,7 @@ export const useFetchPosts = () => {
|
|||||||
const checkAndUpdatePost = React.useCallback(
|
const checkAndUpdatePost = React.useCallback(
|
||||||
(post: BlogPost) => {
|
(post: BlogPost) => {
|
||||||
// Check if the post exists in hashMapPosts
|
// Check if the post exists in hashMapPosts
|
||||||
const existingPost = hashMapPosts[post.id]
|
const existingPost = hashMapPosts[post.id + "-" + post.user]
|
||||||
if (!existingPost) {
|
if (!existingPost) {
|
||||||
// If the post doesn't exist, add it to hashMapPosts
|
// If the post doesn't exist, add it to hashMapPosts
|
||||||
return true
|
return true
|
||||||
|
@ -134,6 +134,8 @@ export const BlogIndividualProfile = () => {
|
|||||||
await getBlogPosts()
|
await getBlogPosts()
|
||||||
}, [getBlogPosts])
|
}, [getBlogPosts])
|
||||||
|
|
||||||
|
console.log({blogPosts})
|
||||||
|
|
||||||
const subscribe = async () => {
|
const subscribe = async () => {
|
||||||
try {
|
try {
|
||||||
if (!user?.name) return
|
if (!user?.name) return
|
||||||
@ -233,7 +235,7 @@ export const BlogIndividualProfile = () => {
|
|||||||
style={{ backgroundColor: theme.palette.background.default }}
|
style={{ backgroundColor: theme.palette.background.default }}
|
||||||
>
|
>
|
||||||
{blogPosts.map((post, index) => {
|
{blogPosts.map((post, index) => {
|
||||||
const existingPost = hashMapPosts[post.id]
|
let existingPost = hashMapPosts[post.id + "-" + post.user]
|
||||||
let blogPost = post
|
let blogPost = post
|
||||||
if (existingPost) {
|
if (existingPost) {
|
||||||
blogPost = existingPost
|
blogPost = existingPost
|
||||||
|
@ -155,7 +155,7 @@ export const BlogList = ({ mode }: BlogListProps) => {
|
|||||||
columnClassName="my-masonry-grid_column"
|
columnClassName="my-masonry-grid_column"
|
||||||
>
|
>
|
||||||
{posts.map((post, index) => {
|
{posts.map((post, index) => {
|
||||||
const existingPost = hashMapPosts[post.id]
|
const existingPost = hashMapPosts[post.id + "-" + post.user]
|
||||||
let blogPost = post
|
let blogPost = post
|
||||||
if (existingPost) {
|
if (existingPost) {
|
||||||
blogPost = existingPost
|
blogPost = existingPost
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Box, Button, Typography } from '@mui/material'
|
import { Box, Button, Typography } from '@mui/material'
|
||||||
import React, { useMemo, useState } from 'react'
|
import React, { useEffect, useMemo, useState } from 'react'
|
||||||
import { ReusableModal } from '../../components/modals/ReusableModal'
|
import { ReusableModal } from '../../components/modals/ReusableModal'
|
||||||
import { CreatePostBuilder } from './CreatePostBuilder'
|
import { CreatePostBuilder } from './CreatePostBuilder'
|
||||||
import { CreatePostMinimal } from './CreatePostMinimal'
|
import { CreatePostMinimal } from './CreatePostMinimal'
|
||||||
@ -8,7 +8,7 @@ import HourglassFullRoundedIcon from '@mui/icons-material/HourglassFullRounded'
|
|||||||
import { display } from '@mui/system'
|
import { display } from '@mui/system'
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
import { setIsLoadingGlobal } from '../../state/features/globalSlice'
|
import { setIsLoadingGlobal } from '../../state/features/globalSlice'
|
||||||
import { useParams } from 'react-router-dom'
|
import { useNavigate, useParams } from 'react-router-dom'
|
||||||
import { checkStructure } from '../../utils/checkStructure'
|
import { checkStructure } from '../../utils/checkStructure'
|
||||||
import { RootState } from '../../state/store'
|
import { RootState } from '../../state/store'
|
||||||
import {
|
import {
|
||||||
@ -28,7 +28,7 @@ export const CreatePost = ({ mode }: CreatePostProps) => {
|
|||||||
const formPostId = buildIdentifierFromCreateTitleIdAndId(formBlogId, postId)
|
const formPostId = buildIdentifierFromCreateTitleIdAndId(formBlogId, postId)
|
||||||
return formPostId
|
return formPostId
|
||||||
}, [blog, postId, mode])
|
}, [blog, postId, mode])
|
||||||
const { user } = useSelector((state: RootState) => state.auth)
|
const user = useSelector((state: RootState) => state.auth?.user)
|
||||||
|
|
||||||
const [toggleEditorType, setToggleEditorType] = useState<EditorType | null>(
|
const [toggleEditorType, setToggleEditorType] = useState<EditorType | null>(
|
||||||
null
|
null
|
||||||
@ -38,6 +38,8 @@ export const CreatePost = ({ mode }: CreatePostProps) => {
|
|||||||
const [editType, setEditType] = useState<EditorType | null>(null)
|
const [editType, setEditType] = useState<EditorType | null>(null)
|
||||||
const [isOpen, setIsOpen] = useState<boolean>(false)
|
const [isOpen, setIsOpen] = useState<boolean>(false)
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!toggleEditorType && mode !== 'edit') {
|
if (!toggleEditorType && mode !== 'edit') {
|
||||||
setIsOpen(true)
|
setIsOpen(true)
|
||||||
@ -48,6 +50,14 @@ export const CreatePost = ({ mode }: CreatePostProps) => {
|
|||||||
setIsOpen(true)
|
setIsOpen(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(()=> {
|
||||||
|
if(username && user?.name && mode === 'edit'){
|
||||||
|
if(username !== user?.name){
|
||||||
|
navigate('/')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [user, username, mode])
|
||||||
|
|
||||||
const getBlogPost = React.useCallback(async () => {
|
const getBlogPost = React.useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
dispatch(setIsLoadingGlobal(true))
|
dispatch(setIsLoadingGlobal(true))
|
||||||
|
@ -1,279 +0,0 @@
|
|||||||
import React, {
|
|
||||||
FC,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useRef,
|
|
||||||
useState
|
|
||||||
} from 'react'
|
|
||||||
import { useNavigate } from 'react-router-dom'
|
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
|
||||||
import { RootState } from '../../state/store'
|
|
||||||
import EditIcon from '@mui/icons-material/Edit'
|
|
||||||
import { Box, Button, Input, Typography, useTheme } from '@mui/material'
|
|
||||||
import { useFetchPosts } from '../../hooks/useFetchPosts'
|
|
||||||
import LazyLoad from '../../components/common/LazyLoad'
|
|
||||||
import { removePrefix } from '../../utils/blogIdformats'
|
|
||||||
import { NewMessage } from './NewMessage'
|
|
||||||
import Tabs from '@mui/material/Tabs'
|
|
||||||
import Tab from '@mui/material/Tab'
|
|
||||||
import { useFetchMail } from '../../hooks/useFetchMail'
|
|
||||||
import { ShowMessage } from './ShowMessage'
|
|
||||||
import { fetchAndEvaluateMail } from '../../utils/fetchMail'
|
|
||||||
import { addToHashMapMail } from '../../state/features/mailSlice'
|
|
||||||
import {
|
|
||||||
setIsLoadingGlobal,
|
|
||||||
setUserAvatarHash
|
|
||||||
} from '../../state/features/globalSlice'
|
|
||||||
import SimpleTable from './MailTable'
|
|
||||||
import { MAIL_SERVICE_TYPE } from '../../constants/mail'
|
|
||||||
import { BlogPost } from '../../state/features/blogSlice'
|
|
||||||
|
|
||||||
interface AliasMailProps {
|
|
||||||
value: string
|
|
||||||
}
|
|
||||||
export const AliasMail = ({ value }: AliasMailProps) => {
|
|
||||||
const theme = useTheme()
|
|
||||||
const { user } = useSelector((state: RootState) => state.auth)
|
|
||||||
const [isOpen, setIsOpen] = useState<boolean>(false)
|
|
||||||
const [message, setMessage] = useState<any>(null)
|
|
||||||
const [replyTo, setReplyTo] = useState<any>(null)
|
|
||||||
const [valueTab, setValueTab] = React.useState(0)
|
|
||||||
const [aliasValue, setAliasValue] = useState('')
|
|
||||||
const [alias, setAlias] = useState<string[]>([])
|
|
||||||
const hashMapPosts = useSelector(
|
|
||||||
(state: RootState) => state.blog.hashMapPosts
|
|
||||||
)
|
|
||||||
const [mailMessages, setMailMessages] = useState<any[]>([])
|
|
||||||
const hashMapMailMessages = useSelector(
|
|
||||||
(state: RootState) => state.mail.hashMapMailMessages
|
|
||||||
)
|
|
||||||
|
|
||||||
const fullMailMessages = useMemo(() => {
|
|
||||||
return mailMessages.map((msg) => {
|
|
||||||
let message = msg
|
|
||||||
const existingMessage = hashMapMailMessages[msg.id]
|
|
||||||
if (existingMessage) {
|
|
||||||
message = existingMessage
|
|
||||||
}
|
|
||||||
return message
|
|
||||||
})
|
|
||||||
}, [mailMessages, hashMapMailMessages])
|
|
||||||
const dispatch = useDispatch()
|
|
||||||
const navigate = useNavigate()
|
|
||||||
|
|
||||||
const getAvatar = async (user: string) => {
|
|
||||||
try {
|
|
||||||
let url = await qortalRequest({
|
|
||||||
action: 'GET_QDN_RESOURCE_URL',
|
|
||||||
name: user,
|
|
||||||
service: 'THUMBNAIL',
|
|
||||||
identifier: 'qortal_avatar'
|
|
||||||
})
|
|
||||||
dispatch(
|
|
||||||
setUserAvatarHash({
|
|
||||||
name: user,
|
|
||||||
url
|
|
||||||
})
|
|
||||||
)
|
|
||||||
} catch (error) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkNewMessages = React.useCallback(
|
|
||||||
async (recipientName: string, recipientAddress: string) => {
|
|
||||||
try {
|
|
||||||
const query = `qortal_qmail_${value}_mail`
|
|
||||||
const url = `/arbitrary/resources/search?mode=ALL&service=${MAIL_SERVICE_TYPE}&query=${query}&limit=20&includemetadata=true&reverse=true&excludeblocked=true`
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const responseData = await response.json()
|
|
||||||
const latestPost = mailMessages[0]
|
|
||||||
if (!latestPost) return
|
|
||||||
const findPost = responseData?.findIndex(
|
|
||||||
(item: any) => item?.identifier === latestPost?.id
|
|
||||||
)
|
|
||||||
if (findPost === -1) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const newArray = responseData.slice(0, findPost)
|
|
||||||
const structureData = newArray.map((post: any): BlogPost => {
|
|
||||||
return {
|
|
||||||
title: post?.metadata?.title,
|
|
||||||
category: post?.metadata?.category,
|
|
||||||
categoryName: post?.metadata?.categoryName,
|
|
||||||
tags: post?.metadata?.tags || [],
|
|
||||||
description: post?.metadata?.description,
|
|
||||||
createdAt: post?.created,
|
|
||||||
updated: post?.updated,
|
|
||||||
user: post.name,
|
|
||||||
id: post.identifier
|
|
||||||
}
|
|
||||||
})
|
|
||||||
setMailMessages((prev) => {
|
|
||||||
const updatedMessages = [...prev]
|
|
||||||
|
|
||||||
structureData.forEach((newMessage: any) => {
|
|
||||||
const existingIndex = updatedMessages.findIndex(
|
|
||||||
(prevMessage) => prevMessage.id === newMessage.id
|
|
||||||
)
|
|
||||||
|
|
||||||
if (existingIndex !== -1) {
|
|
||||||
// Replace existing message
|
|
||||||
updatedMessages[existingIndex] = newMessage
|
|
||||||
} else {
|
|
||||||
// Add new message
|
|
||||||
updatedMessages.unshift(newMessage)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return updatedMessages
|
|
||||||
})
|
|
||||||
return
|
|
||||||
} catch (error) {}
|
|
||||||
},
|
|
||||||
[mailMessages]
|
|
||||||
)
|
|
||||||
|
|
||||||
const getMailMessages = React.useCallback(
|
|
||||||
async (recipientName: string, recipientAddress: string) => {
|
|
||||||
try {
|
|
||||||
const offset = mailMessages.length
|
|
||||||
|
|
||||||
dispatch(setIsLoadingGlobal(true))
|
|
||||||
const query = `qortal_qmail_${value}_mail`
|
|
||||||
const url = `/arbitrary/resources/search?mode=ALL&service=${MAIL_SERVICE_TYPE}&query=${query}&limit=20&includemetadata=true&offset=${offset}&reverse=true&excludeblocked=true`
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const responseData = await response.json()
|
|
||||||
const structureData = responseData.map((post: any): BlogPost => {
|
|
||||||
return {
|
|
||||||
title: post?.metadata?.title,
|
|
||||||
category: post?.metadata?.category,
|
|
||||||
categoryName: post?.metadata?.categoryName,
|
|
||||||
tags: post?.metadata?.tags || [],
|
|
||||||
description: post?.metadata?.description,
|
|
||||||
createdAt: post?.created,
|
|
||||||
updated: post?.updated,
|
|
||||||
user: post.name,
|
|
||||||
id: post.identifier
|
|
||||||
}
|
|
||||||
})
|
|
||||||
setMailMessages((prev) => {
|
|
||||||
const updatedMessages = [...prev]
|
|
||||||
|
|
||||||
structureData.forEach((newMessage: any) => {
|
|
||||||
const existingIndex = updatedMessages.findIndex(
|
|
||||||
(prevMessage) => prevMessage.id === newMessage.id
|
|
||||||
)
|
|
||||||
|
|
||||||
if (existingIndex !== -1) {
|
|
||||||
// Replace existing message
|
|
||||||
updatedMessages[existingIndex] = newMessage
|
|
||||||
} else {
|
|
||||||
// Add new message
|
|
||||||
updatedMessages.push(newMessage)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return updatedMessages
|
|
||||||
})
|
|
||||||
|
|
||||||
for (const content of structureData) {
|
|
||||||
if (content.user && content.id) {
|
|
||||||
getAvatar(content.user)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
} finally {
|
|
||||||
dispatch(setIsLoadingGlobal(false))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[mailMessages, hashMapMailMessages]
|
|
||||||
)
|
|
||||||
const getMessages = React.useCallback(async () => {
|
|
||||||
if (!user?.name || !user?.address) return
|
|
||||||
await getMailMessages(user.name, user.address)
|
|
||||||
}, [getMailMessages, user])
|
|
||||||
|
|
||||||
const interval = useRef<any>(null)
|
|
||||||
|
|
||||||
const checkNewMessagesFunc = useCallback(() => {
|
|
||||||
if (!user?.name || !user?.address) return
|
|
||||||
let isCalling = false
|
|
||||||
interval.current = setInterval(async () => {
|
|
||||||
if (isCalling || !user?.name || !user?.address) return
|
|
||||||
isCalling = true
|
|
||||||
const res = await checkNewMessages(user?.name, user.address)
|
|
||||||
isCalling = false
|
|
||||||
}, 30000)
|
|
||||||
}, [checkNewMessages, user])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
checkNewMessagesFunc()
|
|
||||||
return () => {
|
|
||||||
if (interval?.current) {
|
|
||||||
clearInterval(interval.current)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [checkNewMessagesFunc])
|
|
||||||
|
|
||||||
const openMessage = async (
|
|
||||||
user: string,
|
|
||||||
messageIdentifier: string,
|
|
||||||
content: any
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
const existingMessage = hashMapMailMessages[messageIdentifier]
|
|
||||||
if (existingMessage) {
|
|
||||||
setMessage(existingMessage)
|
|
||||||
}
|
|
||||||
dispatch(setIsLoadingGlobal(true))
|
|
||||||
const res = await fetchAndEvaluateMail({
|
|
||||||
user,
|
|
||||||
messageIdentifier,
|
|
||||||
content,
|
|
||||||
otherUser: user
|
|
||||||
})
|
|
||||||
setMessage(res)
|
|
||||||
dispatch(addToHashMapMail(res))
|
|
||||||
setIsOpen(true)
|
|
||||||
} catch (error) {
|
|
||||||
} finally {
|
|
||||||
dispatch(setIsLoadingGlobal(false))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const firstMount = useRef(false)
|
|
||||||
useEffect(() => {
|
|
||||||
if (user?.name && !firstMount.current) {
|
|
||||||
getMessages()
|
|
||||||
firstMount.current = true
|
|
||||||
}
|
|
||||||
}, [user])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<NewMessage replyTo={replyTo} setReplyTo={setReplyTo} alias={value} />
|
|
||||||
<ShowMessage
|
|
||||||
isOpen={isOpen}
|
|
||||||
setIsOpen={setIsOpen}
|
|
||||||
message={message}
|
|
||||||
setReplyTo={setReplyTo}
|
|
||||||
alias={value}
|
|
||||||
/>
|
|
||||||
<SimpleTable
|
|
||||||
openMessage={openMessage}
|
|
||||||
data={fullMailMessages}
|
|
||||||
></SimpleTable>
|
|
||||||
<LazyLoad onLoadMore={getMessages}></LazyLoad>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,342 +0,0 @@
|
|||||||
import React, {
|
|
||||||
FC,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useRef,
|
|
||||||
useState
|
|
||||||
} from 'react'
|
|
||||||
import { useNavigate } from 'react-router-dom'
|
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
|
||||||
import { RootState } from '../../state/store'
|
|
||||||
import EditIcon from '@mui/icons-material/Edit'
|
|
||||||
import CloseIcon from '@mui/icons-material/Close'
|
|
||||||
import {
|
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
Input,
|
|
||||||
Typography,
|
|
||||||
useTheme,
|
|
||||||
IconButton
|
|
||||||
} from '@mui/material'
|
|
||||||
import { useFetchPosts } from '../../hooks/useFetchPosts'
|
|
||||||
import LazyLoad from '../../components/common/LazyLoad'
|
|
||||||
import { removePrefix } from '../../utils/blogIdformats'
|
|
||||||
import { NewMessage } from './NewMessage'
|
|
||||||
import Tabs from '@mui/material/Tabs'
|
|
||||||
import Tab from '@mui/material/Tab'
|
|
||||||
import { useFetchMail } from '../../hooks/useFetchMail'
|
|
||||||
import { ShowMessage } from './ShowMessage'
|
|
||||||
import { fetchAndEvaluateMail } from '../../utils/fetchMail'
|
|
||||||
import { addToHashMapMail } from '../../state/features/mailSlice'
|
|
||||||
import { setIsLoadingGlobal } from '../../state/features/globalSlice'
|
|
||||||
import SimpleTable from './MailTable'
|
|
||||||
import { AliasMail } from './AliasMail'
|
|
||||||
|
|
||||||
export const Mail = () => {
|
|
||||||
const theme = useTheme()
|
|
||||||
const { user } = useSelector((state: RootState) => state.auth)
|
|
||||||
const [isOpen, setIsOpen] = useState<boolean>(false)
|
|
||||||
const [message, setMessage] = useState<any>(null)
|
|
||||||
const [replyTo, setReplyTo] = useState<any>(null)
|
|
||||||
const [valueTab, setValueTab] = React.useState(0)
|
|
||||||
const [aliasValue, setAliasValue] = useState('')
|
|
||||||
const [alias, setAlias] = useState<string[]>([])
|
|
||||||
const hashMapPosts = useSelector(
|
|
||||||
(state: RootState) => state.blog.hashMapPosts
|
|
||||||
)
|
|
||||||
const hashMapMailMessages = useSelector(
|
|
||||||
(state: RootState) => state.mail.hashMapMailMessages
|
|
||||||
)
|
|
||||||
const mailMessages = useSelector(
|
|
||||||
(state: RootState) => state.mail.mailMessages
|
|
||||||
)
|
|
||||||
|
|
||||||
const fullMailMessages = useMemo(() => {
|
|
||||||
return mailMessages.map((msg) => {
|
|
||||||
let message = msg
|
|
||||||
const existingMessage = hashMapMailMessages[msg.id]
|
|
||||||
if (existingMessage) {
|
|
||||||
message = existingMessage
|
|
||||||
}
|
|
||||||
return message
|
|
||||||
})
|
|
||||||
}, [mailMessages, hashMapMailMessages])
|
|
||||||
const dispatch = useDispatch()
|
|
||||||
const navigate = useNavigate()
|
|
||||||
|
|
||||||
const { getMailMessages, checkNewMessages } = useFetchMail()
|
|
||||||
const getMessages = React.useCallback(async () => {
|
|
||||||
if (!user?.name || !user?.address) return
|
|
||||||
await getMailMessages(user.name, user.address)
|
|
||||||
}, [getMailMessages, user])
|
|
||||||
|
|
||||||
const interval = useRef<any>(null)
|
|
||||||
|
|
||||||
const checkNewMessagesFunc = useCallback(() => {
|
|
||||||
if (!user?.name || !user?.address) return
|
|
||||||
let isCalling = false
|
|
||||||
interval.current = setInterval(async () => {
|
|
||||||
if (isCalling || !user?.name || !user?.address) return
|
|
||||||
isCalling = true
|
|
||||||
const res = await checkNewMessages(user?.name, user.address)
|
|
||||||
isCalling = false
|
|
||||||
}, 30000)
|
|
||||||
}, [checkNewMessages, user])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
checkNewMessagesFunc()
|
|
||||||
return () => {
|
|
||||||
if (interval?.current) {
|
|
||||||
clearInterval(interval.current)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [checkNewMessagesFunc])
|
|
||||||
|
|
||||||
const openMessage = async (
|
|
||||||
user: string,
|
|
||||||
messageIdentifier: string,
|
|
||||||
content: any
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
const existingMessage = hashMapMailMessages[messageIdentifier]
|
|
||||||
if (existingMessage) {
|
|
||||||
setMessage(existingMessage)
|
|
||||||
}
|
|
||||||
dispatch(setIsLoadingGlobal(true))
|
|
||||||
const res = await fetchAndEvaluateMail({
|
|
||||||
user,
|
|
||||||
messageIdentifier,
|
|
||||||
content,
|
|
||||||
otherUser: user
|
|
||||||
})
|
|
||||||
setMessage(res)
|
|
||||||
dispatch(addToHashMapMail(res))
|
|
||||||
setIsOpen(true)
|
|
||||||
} catch (error) {
|
|
||||||
} finally {
|
|
||||||
dispatch(setIsLoadingGlobal(false))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const firstMount = useRef(false)
|
|
||||||
useEffect(() => {
|
|
||||||
if (user?.name && !firstMount.current) {
|
|
||||||
getMessages()
|
|
||||||
firstMount.current = true
|
|
||||||
}
|
|
||||||
}, [user])
|
|
||||||
|
|
||||||
function a11yProps(index: number) {
|
|
||||||
return {
|
|
||||||
id: `mail-tabs-${index}`,
|
|
||||||
'aria-controls': `mail-tabs-${index}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
|
|
||||||
setValueTab(newValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
function CustomTabLabel({ index, label }: any) {
|
|
||||||
return (
|
|
||||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
|
||||||
<span>{label}</span>
|
|
||||||
<IconButton
|
|
||||||
edge="end"
|
|
||||||
color="inherit"
|
|
||||||
size="small"
|
|
||||||
onClick={(event) => {
|
|
||||||
setValueTab(0)
|
|
||||||
const newList = [...alias]
|
|
||||||
|
|
||||||
newList.splice(index, 1)
|
|
||||||
|
|
||||||
setAlias(newList)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CloseIcon fontSize="inherit" />
|
|
||||||
</IconButton>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
width: '100%',
|
|
||||||
flexDirection: 'column',
|
|
||||||
backgroundColor: 'background.paper'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
borderBottom: 1,
|
|
||||||
borderColor: 'divider',
|
|
||||||
display: 'flex',
|
|
||||||
width: '100%',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'flex-start'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Tabs
|
|
||||||
value={valueTab}
|
|
||||||
onChange={handleChange}
|
|
||||||
aria-label="basic tabs example"
|
|
||||||
>
|
|
||||||
<Tab label={user?.name} {...a11yProps(0)} />
|
|
||||||
{alias.map((alia, index) => {
|
|
||||||
return (
|
|
||||||
<Tab
|
|
||||||
sx={{
|
|
||||||
'&.Mui-selected': {
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
fontWeight: theme.typography.fontWeightMedium
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
key={alia}
|
|
||||||
label={<CustomTabLabel index={index} label={alia} />}
|
|
||||||
{...a11yProps(1 + index)}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</Tabs>
|
|
||||||
<Input
|
|
||||||
id="standard-adornment-alias"
|
|
||||||
onChange={(e) => {
|
|
||||||
setAliasValue(e.target.value)
|
|
||||||
}}
|
|
||||||
value={aliasValue}
|
|
||||||
placeholder="Type in alias"
|
|
||||||
sx={{
|
|
||||||
marginLeft: '20px',
|
|
||||||
'&&:before': {
|
|
||||||
borderBottom: 'none'
|
|
||||||
},
|
|
||||||
'&&:after': {
|
|
||||||
borderBottom: 'none'
|
|
||||||
},
|
|
||||||
'&&:hover:before': {
|
|
||||||
borderBottom: 'none'
|
|
||||||
},
|
|
||||||
'&&.Mui-focused:before': {
|
|
||||||
borderBottom: 'none'
|
|
||||||
},
|
|
||||||
'&&.Mui-focused': {
|
|
||||||
outline: 'none'
|
|
||||||
},
|
|
||||||
fontSize: '18px'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setAlias((prev) => [...prev, aliasValue])
|
|
||||||
setAliasValue('')
|
|
||||||
}}
|
|
||||||
variant="contained"
|
|
||||||
>
|
|
||||||
+ alias
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
<NewMessage replyTo={replyTo} setReplyTo={setReplyTo} />
|
|
||||||
<ShowMessage
|
|
||||||
isOpen={isOpen}
|
|
||||||
setIsOpen={setIsOpen}
|
|
||||||
message={message}
|
|
||||||
setReplyTo={setReplyTo}
|
|
||||||
/>
|
|
||||||
{/* {countNewPosts > 0 && (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography>
|
|
||||||
{countNewPosts === 1
|
|
||||||
? `There is ${countNewPosts} new message`
|
|
||||||
: `There are ${countNewPosts} new messages`}
|
|
||||||
</Typography>
|
|
||||||
<Button
|
|
||||||
sx={{
|
|
||||||
backgroundColor: theme.palette.primary.light,
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
fontFamily: 'Arial'
|
|
||||||
}}
|
|
||||||
onClick={getNewPosts}
|
|
||||||
>
|
|
||||||
Load new Posts
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
)} */}
|
|
||||||
<TabPanel value={valueTab} index={0}>
|
|
||||||
<SimpleTable
|
|
||||||
openMessage={openMessage}
|
|
||||||
data={fullMailMessages}
|
|
||||||
></SimpleTable>
|
|
||||||
<LazyLoad onLoadMore={getMessages}></LazyLoad>
|
|
||||||
</TabPanel>
|
|
||||||
{alias.map((alia, index) => {
|
|
||||||
return (
|
|
||||||
<TabPanel key={alia} value={valueTab} index={1 + index}>
|
|
||||||
<AliasMail value={alia} />
|
|
||||||
</TabPanel>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
|
|
||||||
{/* <Box>
|
|
||||||
{mailMessages.map((message, index) => {
|
|
||||||
const existingMessage = hashMapMailMessages[message.id]
|
|
||||||
let mailMessage = message
|
|
||||||
if (existingMessage) {
|
|
||||||
mailMessage = existingMessage
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
gap: 1,
|
|
||||||
alignItems: 'center',
|
|
||||||
width: 'auto',
|
|
||||||
position: 'relative',
|
|
||||||
' @media (max-width: 450px)': {
|
|
||||||
width: '100%'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
key={mailMessage.id}
|
|
||||||
>
|
|
||||||
hello
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</Box> */}
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TabPanelProps {
|
|
||||||
children?: React.ReactNode
|
|
||||||
index: number
|
|
||||||
value: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export function TabPanel(props: TabPanelProps) {
|
|
||||||
const { children, value, index, ...other } = props
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
role="tabpanel"
|
|
||||||
hidden={value !== index}
|
|
||||||
id={`mail-tabs-${index}`}
|
|
||||||
aria-labelledby={`mail-tabs-${index}`}
|
|
||||||
{...other}
|
|
||||||
style={{
|
|
||||||
width: '100%'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{value === index && children}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,190 +0,0 @@
|
|||||||
import * as React from 'react'
|
|
||||||
import Table from '@mui/material/Table'
|
|
||||||
import TableBody from '@mui/material/TableBody'
|
|
||||||
import TableCell from '@mui/material/TableCell'
|
|
||||||
import TableContainer from '@mui/material/TableContainer'
|
|
||||||
import TableHead from '@mui/material/TableHead'
|
|
||||||
import TableRow from '@mui/material/TableRow'
|
|
||||||
import Paper from '@mui/material/Paper'
|
|
||||||
import { Avatar, Box } from '@mui/material'
|
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import { RootState } from '../../state/store'
|
|
||||||
import { formatTimestamp } from '../../utils/time'
|
|
||||||
|
|
||||||
const tableCellFontSize = '16px'
|
|
||||||
|
|
||||||
interface Data {
|
|
||||||
name: string
|
|
||||||
description: string
|
|
||||||
createdAt: number
|
|
||||||
user: string
|
|
||||||
id: string
|
|
||||||
tags: string[]
|
|
||||||
subject?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ColumnData {
|
|
||||||
dataKey: keyof Data
|
|
||||||
label: string
|
|
||||||
numeric?: boolean
|
|
||||||
width?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
const columns: ColumnData[] = [
|
|
||||||
{
|
|
||||||
label: 'Sender',
|
|
||||||
dataKey: 'user',
|
|
||||||
width: 200
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Subject',
|
|
||||||
dataKey: 'description'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Date',
|
|
||||||
dataKey: 'createdAt',
|
|
||||||
numeric: true,
|
|
||||||
width: 200
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
// Replace this with your own data
|
|
||||||
const rows: Data[] = [
|
|
||||||
{
|
|
||||||
name: 'Sample 1',
|
|
||||||
description: 'Sample description 1',
|
|
||||||
createdAt: 1682857406070,
|
|
||||||
user: 'tester1',
|
|
||||||
id: 'qortal_qmail_Phil_ViVrF2_mail_NnHcWj',
|
|
||||||
tags: ['attach: 0']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Sample 2',
|
|
||||||
description: 'Sample description 2',
|
|
||||||
createdAt: 1682857406071,
|
|
||||||
user: 'tester2',
|
|
||||||
id: 'qortal_qmail_Phil_ViVrF2_mail_NnHcWk',
|
|
||||||
tags: ['attach: 1']
|
|
||||||
}
|
|
||||||
// Add more rows as needed
|
|
||||||
]
|
|
||||||
|
|
||||||
function fixedHeaderContent() {
|
|
||||||
return (
|
|
||||||
<TableRow>
|
|
||||||
{columns.map((column) => {
|
|
||||||
return (
|
|
||||||
<TableCell
|
|
||||||
key={column.dataKey}
|
|
||||||
variant="head"
|
|
||||||
align={column.numeric || false ? 'right' : 'left'}
|
|
||||||
style={{ width: column.width }}
|
|
||||||
sx={{
|
|
||||||
backgroundColor: 'background.paper',
|
|
||||||
fontSize: tableCellFontSize,
|
|
||||||
padding: '7px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{column.label}
|
|
||||||
</TableCell>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</TableRow>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function rowContent(_index: number, row: Data, openMessage: any) {
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
{columns.map((column) => {
|
|
||||||
let subject = '-'
|
|
||||||
if (column.dataKey === 'description' && row['subject']) {
|
|
||||||
subject = row['subject']
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<TableCell
|
|
||||||
onClick={() => openMessage(row?.user, row?.id, row)}
|
|
||||||
key={column.dataKey}
|
|
||||||
align={column.numeric || false ? 'right' : 'left'}
|
|
||||||
style={{ width: column.width, cursor: 'pointer' }}
|
|
||||||
sx={{
|
|
||||||
fontSize: tableCellFontSize,
|
|
||||||
padding: '7px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{column.dataKey === 'user' && (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
gap: '5px',
|
|
||||||
width: '100%',
|
|
||||||
alignItems: 'center',
|
|
||||||
flexWrap: 'wrap',
|
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
overflow: 'hidden',
|
|
||||||
whiteSpace: 'nowrap'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<AvatarWrapper user={row?.user}></AvatarWrapper>
|
|
||||||
{row[column.dataKey]}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
{column.dataKey !== 'user' && (
|
|
||||||
<>
|
|
||||||
{column.dataKey === 'createdAt'
|
|
||||||
? formatTimestamp(row[column.dataKey])
|
|
||||||
: column.dataKey === 'description'
|
|
||||||
? subject
|
|
||||||
: row[column.dataKey]}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</TableCell>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</React.Fragment>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SimpleTableProps {
|
|
||||||
openMessage: (user: string, messageIdentifier: string, content: any) => void
|
|
||||||
data: Data[]
|
|
||||||
children?: React.ReactNode
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function SimpleTable({
|
|
||||||
openMessage,
|
|
||||||
data,
|
|
||||||
children
|
|
||||||
}: SimpleTableProps) {
|
|
||||||
return (
|
|
||||||
<Paper style={{ width: '100%' }}>
|
|
||||||
<TableContainer component={Paper}>
|
|
||||||
<Table>
|
|
||||||
<TableHead>{fixedHeaderContent()}</TableHead>
|
|
||||||
<TableBody>
|
|
||||||
{data.map((row, index) => (
|
|
||||||
<TableRow key={index}>
|
|
||||||
{rowContent(index, row, openMessage)}
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
{children}
|
|
||||||
</Paper>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const AvatarWrapper = ({ user }: any) => {
|
|
||||||
const userAvatarHash = useSelector(
|
|
||||||
(state: RootState) => state.global.userAvatarHash
|
|
||||||
)
|
|
||||||
const avatarLink = React.useMemo(() => {
|
|
||||||
if (!user || !userAvatarHash) return ''
|
|
||||||
const findUserAvatar = userAvatarHash[user]
|
|
||||||
if (!findUserAvatar) return ''
|
|
||||||
return findUserAvatar
|
|
||||||
}, [userAvatarHash, user])
|
|
||||||
|
|
||||||
return <Avatar src={avatarLink} alt={`${user}'s avatar`} />
|
|
||||||
}
|
|
@ -1,315 +0,0 @@
|
|||||||
import * as React from 'react'
|
|
||||||
import { styled } from '@mui/material/styles'
|
|
||||||
import ArrowForwardIosSharpIcon from '@mui/icons-material/ArrowForwardIosSharp'
|
|
||||||
import MuiAccordion, { AccordionProps } from '@mui/material/Accordion'
|
|
||||||
import MuiAccordionSummary, {
|
|
||||||
AccordionSummaryProps
|
|
||||||
} from '@mui/material/AccordionSummary'
|
|
||||||
import MuiAccordionDetails from '@mui/material/AccordionDetails'
|
|
||||||
import Typography from '@mui/material/Typography'
|
|
||||||
import { Box, CircularProgress } from '@mui/material'
|
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
|
||||||
import { RootState } from '../../state/store'
|
|
||||||
import { formatTimestamp } from '../../utils/time'
|
|
||||||
import ReadOnlySlate from '../../components/editor/ReadOnlySlate'
|
|
||||||
import { fetchAndEvaluateMail } from '../../utils/fetchMail'
|
|
||||||
import { addToHashMapMail } from '../../state/features/mailSlice'
|
|
||||||
import { AvatarWrapper } from './MailTable'
|
|
||||||
import FileElement from '../../components/FileElement'
|
|
||||||
import AttachFileIcon from '@mui/icons-material/AttachFile'
|
|
||||||
import { MAIL_SERVICE_TYPE } from '../../constants/mail'
|
|
||||||
|
|
||||||
const Accordion = styled((props: AccordionProps) => (
|
|
||||||
<MuiAccordion disableGutters elevation={0} square {...props} />
|
|
||||||
))(({ theme }) => ({
|
|
||||||
border: `1px solid ${theme.palette.divider}`,
|
|
||||||
'&:not(:last-child)': {
|
|
||||||
borderBottom: 0
|
|
||||||
},
|
|
||||||
'&:before': {
|
|
||||||
display: 'none'
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
const AccordionSummary = styled((props: AccordionSummaryProps) => (
|
|
||||||
<MuiAccordionSummary
|
|
||||||
expandIcon={<ArrowForwardIosSharpIcon sx={{ fontSize: '16px' }} />}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))(({ theme }) => ({
|
|
||||||
backgroundColor:
|
|
||||||
theme.palette.mode === 'dark'
|
|
||||||
? 'rgba(255, 255, 255, .05)'
|
|
||||||
: 'rgba(0, 0, 0, .03)',
|
|
||||||
flexDirection: 'row-reverse',
|
|
||||||
'& .MuiAccordionSummary-expandIconWrapper.Mui-expanded': {
|
|
||||||
transform: 'rotate(90deg)'
|
|
||||||
},
|
|
||||||
'& .MuiAccordionSummary-content': {
|
|
||||||
marginLeft: theme.spacing(1)
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
const AccordionDetails = styled(MuiAccordionDetails)(({ theme }) => ({
|
|
||||||
padding: theme.spacing(2),
|
|
||||||
borderTop: '1px solid rgba(0, 0, 0, .125)'
|
|
||||||
}))
|
|
||||||
|
|
||||||
interface IThread {
|
|
||||||
identifier: string
|
|
||||||
service: string
|
|
||||||
name: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function MailThread({
|
|
||||||
thread,
|
|
||||||
users,
|
|
||||||
otherUser
|
|
||||||
}: {
|
|
||||||
thread: IThread[]
|
|
||||||
users: string[]
|
|
||||||
otherUser: string
|
|
||||||
}) {
|
|
||||||
const [expanded, setExpanded] = React.useState<string | false>('panel1')
|
|
||||||
const [isLoading, setIsLoading] = React.useState<boolean>(false)
|
|
||||||
const dispatch = useDispatch()
|
|
||||||
const hashMapMailMessages = useSelector(
|
|
||||||
(state: RootState) => state.mail.hashMapMailMessages
|
|
||||||
)
|
|
||||||
const handleChange =
|
|
||||||
(panel: string) => (event: React.SyntheticEvent, newExpanded: boolean) => {
|
|
||||||
setExpanded(newExpanded ? panel : false)
|
|
||||||
}
|
|
||||||
const getThreadMessages = async () => {
|
|
||||||
setIsLoading(true)
|
|
||||||
try {
|
|
||||||
for (const msgId of thread) {
|
|
||||||
const existingMessage = hashMapMailMessages[msgId?.identifier]
|
|
||||||
if (existingMessage) {
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
const query = msgId?.identifier
|
|
||||||
const url = `/arbitrary/resources/search?mode=ALL&service=${MAIL_SERVICE_TYPE}&query=${query}&limit=20&includemetadata=true&offset=0&reverse=true&excludeblocked=true&name=${msgId?.name}&exactmatchnames=true&`
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const responseData = await response.json()
|
|
||||||
if (responseData.length !== 0) {
|
|
||||||
const data = responseData[0]
|
|
||||||
const content = {
|
|
||||||
title: data?.metadata?.title,
|
|
||||||
category: data?.metadata?.category,
|
|
||||||
categoryName: data?.metadata?.categoryName,
|
|
||||||
tags: data?.metadata?.tags || [],
|
|
||||||
description: data?.metadata?.description,
|
|
||||||
createdAt: data?.created,
|
|
||||||
updated: data?.updated,
|
|
||||||
user: data.name,
|
|
||||||
id: data.identifier
|
|
||||||
}
|
|
||||||
const res = await fetchAndEvaluateMail({
|
|
||||||
user: data.name,
|
|
||||||
messageIdentifier: data.identifier,
|
|
||||||
content,
|
|
||||||
otherUser
|
|
||||||
})
|
|
||||||
|
|
||||||
dispatch(addToHashMapMail(res))
|
|
||||||
}
|
|
||||||
} catch (error) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {}
|
|
||||||
setIsLoading(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
getThreadMessages()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
if (isLoading) return <CircularProgress color="secondary" />
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
width: '100%'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{thread?.map((message: any) => {
|
|
||||||
const findMessage: any = hashMapMailMessages[message?.identifier]
|
|
||||||
if (!findMessage) return null
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Accordion
|
|
||||||
expanded={expanded === message?.identifier}
|
|
||||||
onChange={handleChange(message?.identifier)}
|
|
||||||
>
|
|
||||||
<AccordionSummary
|
|
||||||
aria-controls="panel1d-content"
|
|
||||||
id="panel1d-header"
|
|
||||||
sx={{
|
|
||||||
fontSize: '16px',
|
|
||||||
height: '36px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
width: '100%',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'space-between'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '10px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<AvatarWrapper user={findMessage?.user} />
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
fontSize: '16px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{findMessage?.user}
|
|
||||||
</Typography>
|
|
||||||
<Typography>{findMessage?.description}</Typography>
|
|
||||||
</Box>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
fontSize: '16px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{formatTimestamp(findMessage?.createdAt)}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</AccordionSummary>
|
|
||||||
<AccordionDetails>
|
|
||||||
<>
|
|
||||||
{findMessage?.attachments?.length > 0 && (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
width: '100%',
|
|
||||||
marginTop: '10px',
|
|
||||||
marginBottom: '20px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{findMessage?.attachments.map((file: any) => {
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'flex-start',
|
|
||||||
width: '100%'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '5px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
width: 'auto'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FileElement
|
|
||||||
fileInfo={file}
|
|
||||||
title={file?.filename}
|
|
||||||
mode="mail"
|
|
||||||
otherUser={otherUser}
|
|
||||||
>
|
|
||||||
<AttachFileIcon
|
|
||||||
sx={{
|
|
||||||
height: '16px',
|
|
||||||
width: 'auto'
|
|
||||||
}}
|
|
||||||
></AttachFileIcon>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
fontSize: '16px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{file?.originalFilename || file?.filename}
|
|
||||||
</Typography>
|
|
||||||
</FileElement>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
{findMessage?.textContent && (
|
|
||||||
<ReadOnlySlate
|
|
||||||
content={findMessage.textContent}
|
|
||||||
mode="mail"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
</AccordionDetails>
|
|
||||||
</Accordion>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
{/* <Accordion
|
|
||||||
expanded={expanded === 'panel1'}
|
|
||||||
onChange={handleChange('panel1')}
|
|
||||||
>
|
|
||||||
<AccordionSummary aria-controls="panel1d-content" id="panel1d-header">
|
|
||||||
<Typography>Collapsible Group Item #1</Typography>
|
|
||||||
</AccordionSummary>
|
|
||||||
<AccordionDetails>
|
|
||||||
<Typography>
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse
|
|
||||||
malesuada lacus ex, sit amet blandit leo lobortis eget. Lorem ipsum
|
|
||||||
dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada
|
|
||||||
lacus ex, sit amet blandit leo lobortis eget.
|
|
||||||
</Typography>
|
|
||||||
</AccordionDetails>
|
|
||||||
</Accordion>
|
|
||||||
<Accordion
|
|
||||||
expanded={expanded === 'panel2'}
|
|
||||||
onChange={handleChange('panel2')}
|
|
||||||
>
|
|
||||||
<AccordionSummary aria-controls="panel2d-content" id="panel2d-header">
|
|
||||||
<Typography>Collapsible Group Item #2</Typography>
|
|
||||||
</AccordionSummary>
|
|
||||||
<AccordionDetails>
|
|
||||||
<Typography>
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse
|
|
||||||
malesuada lacus ex, sit amet blandit leo lobortis eget. Lorem ipsum
|
|
||||||
dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada
|
|
||||||
lacus ex, sit amet blandit leo lobortis eget.
|
|
||||||
</Typography>
|
|
||||||
</AccordionDetails>
|
|
||||||
</Accordion>
|
|
||||||
<Accordion
|
|
||||||
expanded={expanded === 'panel3'}
|
|
||||||
onChange={handleChange('panel3')}
|
|
||||||
>
|
|
||||||
<AccordionSummary aria-controls="panel3d-content" id="panel3d-header">
|
|
||||||
<Typography>Collapsible Group Item #3</Typography>
|
|
||||||
</AccordionSummary>
|
|
||||||
<AccordionDetails>
|
|
||||||
<Typography>
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse
|
|
||||||
malesuada lacus ex, sit amet blandit leo lobortis eget. Lorem ipsum
|
|
||||||
dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada
|
|
||||||
lacus ex, sit amet blandit leo lobortis eget.
|
|
||||||
</Typography>
|
|
||||||
</AccordionDetails>
|
|
||||||
</Accordion> */}
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,425 +0,0 @@
|
|||||||
import React, { Dispatch, useEffect, useState } from 'react'
|
|
||||||
import { ReusableModal } from '../../components/modals/ReusableModal'
|
|
||||||
import { Box, Input, Typography } from '@mui/material'
|
|
||||||
import { BuilderButton } from '../CreatePost/CreatePost-styles'
|
|
||||||
import BlogEditor from '../../components/editor/BlogEditor'
|
|
||||||
import EmailIcon from '@mui/icons-material/Email'
|
|
||||||
import { Descendant } from 'slate'
|
|
||||||
import ShortUniqueId from 'short-unique-id'
|
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
|
||||||
import { RootState } from '../../state/store'
|
|
||||||
import { useDropzone } from 'react-dropzone'
|
|
||||||
import AttachFileIcon from '@mui/icons-material/AttachFile'
|
|
||||||
import CloseIcon from '@mui/icons-material/Close'
|
|
||||||
|
|
||||||
import { setNotification } from '../../state/features/notificationsSlice'
|
|
||||||
import {
|
|
||||||
objectToBase64,
|
|
||||||
objectToUint8Array,
|
|
||||||
objectToUint8ArrayFromResponse,
|
|
||||||
processFileInChunks,
|
|
||||||
toBase64,
|
|
||||||
uint8ArrayToBase64
|
|
||||||
} from '../../utils/toBase64'
|
|
||||||
import {
|
|
||||||
MAIL_ATTACHMENT_SERVICE_TYPE,
|
|
||||||
MAIL_SERVICE_TYPE
|
|
||||||
} from '../../constants/mail'
|
|
||||||
const initialValue: Descendant[] = [
|
|
||||||
{
|
|
||||||
type: 'paragraph',
|
|
||||||
children: [{ text: '' }]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
const uid = new ShortUniqueId()
|
|
||||||
|
|
||||||
interface NewMessageProps {
|
|
||||||
replyTo?: any
|
|
||||||
setReplyTo: React.Dispatch<any>
|
|
||||||
alias?: string
|
|
||||||
}
|
|
||||||
const maxSize = 25 * 1024 * 1024 // 25 MB in bytes
|
|
||||||
export const NewMessage = ({ setReplyTo, replyTo, alias }: NewMessageProps) => {
|
|
||||||
const [isOpen, setIsOpen] = useState<boolean>(false)
|
|
||||||
const [value, setValue] = useState(initialValue)
|
|
||||||
const [title, setTitle] = useState<string>('')
|
|
||||||
const [attachments, setAttachments] = useState<any[]>([])
|
|
||||||
const [description, setDescription] = useState<string>('')
|
|
||||||
const [subject, setSubject] = useState<string>('')
|
|
||||||
const [destinationName, setDestinationName] = useState('')
|
|
||||||
const [aliasValue, setAliasValue] = useState<string>('')
|
|
||||||
const { user } = useSelector((state: RootState) => state.auth)
|
|
||||||
const dispatch = useDispatch()
|
|
||||||
const { getRootProps, getInputProps } = useDropzone({
|
|
||||||
maxSize,
|
|
||||||
onDrop: (acceptedFiles) => {
|
|
||||||
setAttachments((prev) => [...prev, ...acceptedFiles])
|
|
||||||
},
|
|
||||||
onDropRejected: (rejectedFiles) => {
|
|
||||||
dispatch(
|
|
||||||
setNotification({
|
|
||||||
msg: 'One of your files is over the 25mb limit',
|
|
||||||
alertType: 'error'
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (alias) {
|
|
||||||
setAliasValue(alias)
|
|
||||||
}
|
|
||||||
}, [alias])
|
|
||||||
|
|
||||||
const openModal = () => {
|
|
||||||
setIsOpen(true)
|
|
||||||
|
|
||||||
setReplyTo(null)
|
|
||||||
}
|
|
||||||
const closeModal = () => {
|
|
||||||
setAttachments([])
|
|
||||||
setSubject('')
|
|
||||||
setDestinationName('')
|
|
||||||
|
|
||||||
setValue(initialValue)
|
|
||||||
setReplyTo(null)
|
|
||||||
setIsOpen(false)
|
|
||||||
if (!alias) {
|
|
||||||
setAliasValue('')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
useEffect(() => {
|
|
||||||
if (replyTo) {
|
|
||||||
setIsOpen(true)
|
|
||||||
setDestinationName(replyTo?.user || '')
|
|
||||||
}
|
|
||||||
}, [replyTo])
|
|
||||||
async function publishQDNResource() {
|
|
||||||
let address: string = ''
|
|
||||||
let name: string = ''
|
|
||||||
let errorMsg = ''
|
|
||||||
|
|
||||||
address = user?.address || ''
|
|
||||||
name = user?.name || ''
|
|
||||||
|
|
||||||
const missingFields: string[] = []
|
|
||||||
if (!address) {
|
|
||||||
errorMsg = "Cannot send: your address isn't available"
|
|
||||||
}
|
|
||||||
if (!name) {
|
|
||||||
errorMsg = 'Cannot send a message without a access to your name'
|
|
||||||
}
|
|
||||||
if (!destinationName) {
|
|
||||||
errorMsg = 'Cannot send a message without a recipient name'
|
|
||||||
}
|
|
||||||
// if (!description) missingFields.push('subject')
|
|
||||||
if (missingFields.length > 0) {
|
|
||||||
const missingFieldsString = missingFields.join(', ')
|
|
||||||
const errMsg = `Missing: ${missingFieldsString}`
|
|
||||||
errorMsg = errMsg
|
|
||||||
}
|
|
||||||
|
|
||||||
if (errorMsg) {
|
|
||||||
dispatch(
|
|
||||||
setNotification({
|
|
||||||
msg: errorMsg,
|
|
||||||
alertType: 'error'
|
|
||||||
})
|
|
||||||
)
|
|
||||||
throw new Error(errorMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
const mailObject: any = {
|
|
||||||
title,
|
|
||||||
// description,
|
|
||||||
subject,
|
|
||||||
createdAt: Date.now(),
|
|
||||||
version: 1,
|
|
||||||
attachments,
|
|
||||||
textContent: value,
|
|
||||||
generalData: {
|
|
||||||
thread: []
|
|
||||||
},
|
|
||||||
recipient: destinationName
|
|
||||||
}
|
|
||||||
if (replyTo?.id) {
|
|
||||||
const previousTread = Array.isArray(replyTo?.generalData?.thread)
|
|
||||||
? replyTo?.generalData?.thread
|
|
||||||
: []
|
|
||||||
mailObject.generalData.thread = [
|
|
||||||
...previousTread,
|
|
||||||
{
|
|
||||||
identifier: replyTo.id,
|
|
||||||
name: replyTo.user,
|
|
||||||
service: MAIL_SERVICE_TYPE
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!destinationName) return
|
|
||||||
const id = uid()
|
|
||||||
const recipientName = destinationName
|
|
||||||
const resName = await qortalRequest({
|
|
||||||
action: 'GET_NAME_DATA',
|
|
||||||
name: recipientName
|
|
||||||
})
|
|
||||||
if (!resName?.owner) return
|
|
||||||
|
|
||||||
const recipientAddress = resName.owner
|
|
||||||
const resAddress = await qortalRequest({
|
|
||||||
action: 'GET_ACCOUNT_DATA',
|
|
||||||
address: recipientAddress
|
|
||||||
})
|
|
||||||
if (!resAddress?.publicKey) return
|
|
||||||
const recipientPublicKey = resAddress.publicKey
|
|
||||||
|
|
||||||
// START OF ATTACHMENT LOGIC
|
|
||||||
|
|
||||||
const attachmentArray = []
|
|
||||||
for (const attachment of attachments) {
|
|
||||||
const fileBase64 = await toBase64(attachment)
|
|
||||||
if (typeof fileBase64 !== 'string' || !fileBase64)
|
|
||||||
throw new Error('Could not convert file to base64')
|
|
||||||
const base64String = fileBase64.split(',')[1]
|
|
||||||
|
|
||||||
const id = uid()
|
|
||||||
const id2 = uid()
|
|
||||||
const identifier = `attachments_qmail_${id}_${id2}`
|
|
||||||
const fileExtension = attachment?.name?.split('.')?.pop()
|
|
||||||
if (!fileExtension) {
|
|
||||||
throw new Error('One of your attachments does not have an extension')
|
|
||||||
}
|
|
||||||
const obj = {
|
|
||||||
name: name,
|
|
||||||
service: MAIL_ATTACHMENT_SERVICE_TYPE,
|
|
||||||
filename: `${id}.${fileExtension}`,
|
|
||||||
identifier,
|
|
||||||
data64: base64String
|
|
||||||
}
|
|
||||||
|
|
||||||
attachmentArray.push(obj)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attachmentArray?.length > 0) {
|
|
||||||
mailObject.attachments = attachmentArray.map((item) => {
|
|
||||||
return {
|
|
||||||
identifier: item.identifier,
|
|
||||||
name,
|
|
||||||
service: MAIL_ATTACHMENT_SERVICE_TYPE,
|
|
||||||
filename: item.filename
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const multiplePublish = {
|
|
||||||
action: 'PUBLISH_MULTIPLE_QDN_RESOURCES',
|
|
||||||
resources: [...attachmentArray],
|
|
||||||
encrypt: true,
|
|
||||||
recipientPublicKey
|
|
||||||
}
|
|
||||||
await qortalRequest(multiplePublish)
|
|
||||||
}
|
|
||||||
|
|
||||||
//END OF ATTACHMENT LOGIC
|
|
||||||
|
|
||||||
const blogPostToBase64 = await objectToBase64(mailObject)
|
|
||||||
let identifier = `qortal_qmail_${recipientName.slice(
|
|
||||||
0,
|
|
||||||
20
|
|
||||||
)}_${recipientAddress.slice(-6)}_mail_${id}`
|
|
||||||
|
|
||||||
if (aliasValue) {
|
|
||||||
identifier = `qortal_qmail_${aliasValue}_mail_${id}`
|
|
||||||
}
|
|
||||||
|
|
||||||
let requestBody: any = {
|
|
||||||
action: 'PUBLISH_QDN_RESOURCE',
|
|
||||||
name: name,
|
|
||||||
service: MAIL_SERVICE_TYPE,
|
|
||||||
data64: blogPostToBase64,
|
|
||||||
title: title,
|
|
||||||
// description: description,
|
|
||||||
identifier,
|
|
||||||
encrypt: true,
|
|
||||||
recipientPublicKey
|
|
||||||
}
|
|
||||||
|
|
||||||
await qortalRequest(requestBody)
|
|
||||||
dispatch(
|
|
||||||
setNotification({
|
|
||||||
msg: 'Message sent',
|
|
||||||
alertType: 'success'
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
closeModal()
|
|
||||||
} catch (error: any) {
|
|
||||||
let notificationObj = null
|
|
||||||
if (typeof error === 'string') {
|
|
||||||
notificationObj = {
|
|
||||||
msg: error || 'Failed to send message',
|
|
||||||
alertType: 'error'
|
|
||||||
}
|
|
||||||
} else if (typeof error?.error === 'string') {
|
|
||||||
notificationObj = {
|
|
||||||
msg: error?.error || 'Failed to send message',
|
|
||||||
alertType: 'error'
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
notificationObj = {
|
|
||||||
msg: error?.message || 'Failed to send message',
|
|
||||||
alertType: 'error'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!notificationObj) return
|
|
||||||
dispatch(setNotification(notificationObj))
|
|
||||||
|
|
||||||
throw new Error('Failed to send message')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const sendMail = () => {
|
|
||||||
publishQDNResource()
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'flex-end',
|
|
||||||
width: '100%'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{!alias && (
|
|
||||||
<EmailIcon
|
|
||||||
sx={{
|
|
||||||
cursor: 'pointer',
|
|
||||||
margin: '15px'
|
|
||||||
}}
|
|
||||||
onClick={openModal}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<ReusableModal open={isOpen}>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
flexDirection: 'column',
|
|
||||||
gap: 1
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'flex-start',
|
|
||||||
flexDirection: 'column',
|
|
||||||
gap: 2,
|
|
||||||
width: '100%'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
id="standard-adornment-name"
|
|
||||||
value={destinationName}
|
|
||||||
disabled={!!replyTo}
|
|
||||||
onChange={(e) => {
|
|
||||||
setDestinationName(e.target.value)
|
|
||||||
}}
|
|
||||||
placeholder="To (name) -public"
|
|
||||||
sx={{
|
|
||||||
width: '100%',
|
|
||||||
fontSize: '16px'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
id="standard-adornment-alias"
|
|
||||||
value={aliasValue}
|
|
||||||
disabled={!!alias}
|
|
||||||
onChange={(e) => {
|
|
||||||
setAliasValue(e.target.value)
|
|
||||||
}}
|
|
||||||
placeholder="Alias -optional"
|
|
||||||
sx={{
|
|
||||||
width: '100%',
|
|
||||||
fontSize: '16px'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Input
|
|
||||||
id="standard-adornment-name"
|
|
||||||
value={subject}
|
|
||||||
onChange={(e) => {
|
|
||||||
setSubject(e.target.value)
|
|
||||||
}}
|
|
||||||
placeholder="Subject"
|
|
||||||
sx={{
|
|
||||||
width: '100%',
|
|
||||||
fontSize: '16px'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Box
|
|
||||||
{...getRootProps()}
|
|
||||||
sx={{
|
|
||||||
border: '1px dashed gray',
|
|
||||||
padding: 2,
|
|
||||||
textAlign: 'center',
|
|
||||||
marginBottom: 2
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<input {...getInputProps()} />
|
|
||||||
<AttachFileIcon
|
|
||||||
sx={{
|
|
||||||
height: '20px',
|
|
||||||
width: 'auto',
|
|
||||||
cursor: 'pointer'
|
|
||||||
}}
|
|
||||||
></AttachFileIcon>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
{attachments.map((file, index) => {
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '15px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
fontSize: '16px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{file?.name}
|
|
||||||
</Typography>
|
|
||||||
<CloseIcon
|
|
||||||
onClick={() =>
|
|
||||||
setAttachments((prev) =>
|
|
||||||
prev.filter((item, itemIndex) => itemIndex !== index)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
sx={{
|
|
||||||
height: '16px',
|
|
||||||
width: 'auto',
|
|
||||||
cursor: 'pointer'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
<BlogEditor
|
|
||||||
mode="mail"
|
|
||||||
value={value}
|
|
||||||
setValue={setValue}
|
|
||||||
editorKey={1}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<BuilderButton onClick={sendMail}>
|
|
||||||
{replyTo ? 'Send reply mail' : 'Send mail'}
|
|
||||||
</BuilderButton>
|
|
||||||
<BuilderButton onClick={closeModal}>Close</BuilderButton>
|
|
||||||
</ReusableModal>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,256 +0,0 @@
|
|||||||
import React, { useState } from 'react'
|
|
||||||
import { ReusableModal } from '../../components/modals/ReusableModal'
|
|
||||||
import { Box, Button, Input, Typography } from '@mui/material'
|
|
||||||
import { BuilderButton } from '../CreatePost/CreatePost-styles'
|
|
||||||
import BlogEditor from '../../components/editor/BlogEditor'
|
|
||||||
import EmailIcon from '@mui/icons-material/Email'
|
|
||||||
import { Descendant } from 'slate'
|
|
||||||
import ShortUniqueId from 'short-unique-id'
|
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
|
||||||
import { RootState } from '../../state/store'
|
|
||||||
import AttachFileIcon from '@mui/icons-material/AttachFile'
|
|
||||||
|
|
||||||
import { setNotification } from '../../state/features/notificationsSlice'
|
|
||||||
import {
|
|
||||||
objectToBase64,
|
|
||||||
objectToUint8Array,
|
|
||||||
objectToUint8ArrayFromResponse,
|
|
||||||
uint8ArrayToBase64
|
|
||||||
} from '../../utils/toBase64'
|
|
||||||
import ReadOnlySlate from '../../components/editor/ReadOnlySlate'
|
|
||||||
import MailThread from './MailThread'
|
|
||||||
import { AvatarWrapper } from './MailTable'
|
|
||||||
import { formatTimestamp } from '../../utils/time'
|
|
||||||
import FileElement from '../../components/FileElement'
|
|
||||||
const initialValue: Descendant[] = [
|
|
||||||
{
|
|
||||||
type: 'paragraph',
|
|
||||||
children: [{ text: '' }]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
const uid = new ShortUniqueId()
|
|
||||||
|
|
||||||
export const ShowMessage = ({
|
|
||||||
isOpen,
|
|
||||||
setIsOpen,
|
|
||||||
message,
|
|
||||||
setReplyTo,
|
|
||||||
alias
|
|
||||||
}: any) => {
|
|
||||||
const [value, setValue] = useState(initialValue)
|
|
||||||
const [title, setTitle] = useState<string>('')
|
|
||||||
const [attachments, setAttachments] = useState<any[]>([])
|
|
||||||
const [description, setDescription] = useState<string>('')
|
|
||||||
const [isOpenMailThread, setIsOpenMailThread] = useState<boolean>(false)
|
|
||||||
|
|
||||||
const [destinationName, setDestinationName] = useState('')
|
|
||||||
const user = useSelector((state: RootState) => state.auth?.user)
|
|
||||||
const dispatch = useDispatch()
|
|
||||||
const openModal = () => {
|
|
||||||
setIsOpen(true)
|
|
||||||
}
|
|
||||||
const closeModal = () => {
|
|
||||||
setIsOpen(false)
|
|
||||||
setIsOpenMailThread(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleReply = () => {
|
|
||||||
setReplyTo(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'flex-end',
|
|
||||||
width: '100%'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ReusableModal
|
|
||||||
open={isOpen}
|
|
||||||
customStyles={{
|
|
||||||
width: '96%',
|
|
||||||
maxWidth: 1500,
|
|
||||||
height: '96%'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'flex-end',
|
|
||||||
width: '100%',
|
|
||||||
alignItems: 'center'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{isOpenMailThread &&
|
|
||||||
!alias &&
|
|
||||||
message?.generalData?.thread &&
|
|
||||||
message?.user &&
|
|
||||||
user?.name && (
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
onClick={() => {
|
|
||||||
setIsOpenMailThread(false)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Hide message threads
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!isOpenMailThread &&
|
|
||||||
!alias &&
|
|
||||||
message?.generalData?.thread?.length > 0 &&
|
|
||||||
message?.user &&
|
|
||||||
user?.name && (
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
onClick={() => {
|
|
||||||
setIsOpenMailThread(true)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Show message threads
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
flexDirection: 'column',
|
|
||||||
gap: 1,
|
|
||||||
flexGrow: 1,
|
|
||||||
overflow: 'auto',
|
|
||||||
width: '100%'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{isOpenMailThread &&
|
|
||||||
!alias &&
|
|
||||||
message?.generalData?.thread?.length > 0 &&
|
|
||||||
message?.user &&
|
|
||||||
user?.name && (
|
|
||||||
<MailThread
|
|
||||||
thread={message?.generalData?.thread}
|
|
||||||
users={[message.user, user.name]}
|
|
||||||
otherUser={message?.user}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
gap: 1,
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
width: '100%'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '10px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<AvatarWrapper user={message?.user} />
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
fontSize: '16px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{message?.user}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '10px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
fontSize: '16px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{message?.subject}
|
|
||||||
</Typography>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
fontSize: '16px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{formatTimestamp(message?.createdAt)}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
{message?.attachments?.length > 0 && (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
width: '100%',
|
|
||||||
marginTop: '10px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{message?.attachments.map((file: any) => {
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'flex-start',
|
|
||||||
width: '100%'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '5px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
width: 'auto'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FileElement
|
|
||||||
fileInfo={file}
|
|
||||||
title={file?.filename}
|
|
||||||
mode="mail"
|
|
||||||
otherUser={message?.user}
|
|
||||||
>
|
|
||||||
<AttachFileIcon
|
|
||||||
sx={{
|
|
||||||
height: '16px',
|
|
||||||
width: 'auto'
|
|
||||||
}}
|
|
||||||
></AttachFileIcon>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
fontSize: '16px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{file?.originalFilename || file?.filename}
|
|
||||||
</Typography>
|
|
||||||
</FileElement>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{message?.textContent && (
|
|
||||||
<ReadOnlySlate content={message.textContent} mode="mail" />
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
gap: 1,
|
|
||||||
justifyContent: 'flex-end'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<BuilderButton onClick={handleReply}>Reply</BuilderButton>
|
|
||||||
<BuilderButton onClick={closeModal}>Close</BuilderButton>
|
|
||||||
</Box>
|
|
||||||
</ReusableModal>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
@ -178,12 +178,13 @@ export const blogSlice = createSlice({
|
|||||||
},
|
},
|
||||||
addToHashMap: (state, action) => {
|
addToHashMap: (state, action) => {
|
||||||
const post = action.payload
|
const post = action.payload
|
||||||
state.hashMapPosts[post.id] = post
|
const fullId =
|
||||||
|
state.hashMapPosts[post.id + "-" + post.user] = post
|
||||||
},
|
},
|
||||||
updateInHashMap: (state, action) => {
|
updateInHashMap: (state, action) => {
|
||||||
const { id } = action.payload
|
const { id, user } = action.payload
|
||||||
const post = action.payload
|
const post = action.payload
|
||||||
state.hashMapPosts[id] = { ...post }
|
state.hashMapPosts[id + '-' + user] = { ...post }
|
||||||
},
|
},
|
||||||
removeFromHashMap: (state, action) => {
|
removeFromHashMap: (state, action) => {
|
||||||
const idToDelete = action.payload
|
const idToDelete = action.payload
|
||||||
@ -192,7 +193,7 @@ export const blogSlice = createSlice({
|
|||||||
addArrayToHashMap: (state, action) => {
|
addArrayToHashMap: (state, action) => {
|
||||||
const posts = action.payload
|
const posts = action.payload
|
||||||
posts.forEach((post: BlogPost) => {
|
posts.forEach((post: BlogPost) => {
|
||||||
state.hashMapPosts[post.id] = post
|
state.hashMapPosts[post.id + "-" + post.user] = post
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
upsertPosts: (state, action) => {
|
upsertPosts: (state, action) => {
|
||||||
|
@ -7,7 +7,7 @@ export const checkAndUpdatePost = (post: BlogPost) => {
|
|||||||
const hashMapPosts = useSelector((state: RootState) => state.blog.hashMapPosts);
|
const hashMapPosts = useSelector((state: RootState) => state.blog.hashMapPosts);
|
||||||
|
|
||||||
// Check if the post exists in hashMapPosts
|
// Check if the post exists in hashMapPosts
|
||||||
const existingPost = hashMapPosts[post.id];
|
const existingPost = hashMapPosts[post.id + "-" + post.user];
|
||||||
|
|
||||||
if (!existingPost) {
|
if (!existingPost) {
|
||||||
// If the post doesn't exist, add it to hashMapPosts
|
// If the post doesn't exist, add it to hashMapPosts
|
||||||
|
Loading…
Reference in New Issue
Block a user