mirror of https://github.com/Qortal/q-blog
PhilReact
8 months ago
14 changed files with 24 additions and 2289 deletions
@ -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 |
||||
} |
||||
} |
@ -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> |
||||
) |
||||
} |
Loading…
Reference in new issue