diff --git a/src/components/common/PublishAudio.tsx b/src/components/common/PublishAudio.tsx index 23367fd..6f30d01 100644 --- a/src/components/common/PublishAudio.tsx +++ b/src/components/common/PublishAudio.tsx @@ -1,21 +1,22 @@ -import React from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { setNotification } from '../../state/features/notificationsSlice' -import { RootState } from '../../state/store' -import ShortUniqueId from 'short-unique-id' +import React from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { AUDIO_BASE } from "../../constants/Identifiers"; +import { setNotification } from "../../state/features/notificationsSlice"; +import { RootState } from "../../state/store"; +import ShortUniqueId from "short-unique-id"; -const uid = new ShortUniqueId() +const uid = new ShortUniqueId(); interface IPublishVideo { - title: string - description: string - base64: string - category: string + title: string; + description: string; + base64: string; + category: string; } export const usePublishAudio = () => { - const { user } = useSelector((state: RootState) => state.auth) - const dispatch = useDispatch() + const { user } = useSelector((state: RootState) => state.auth); + const dispatch = useDispatch(); const publishAudio = async ({ title, description, @@ -23,84 +24,83 @@ export const usePublishAudio = () => { category, ...rest }: IPublishVideo) => { - let address - let name - let errorMsg = '' + let address; + let name; + let errorMsg = ""; - address = user?.address - name = user?.name || '' + address = user?.address; + name = user?.name || ""; - const missingFields = [] + const missingFields = []; if (!address) { - errorMsg = "Cannot post: your address isn't available" + errorMsg = "Cannot post: your address isn't available"; } if (!name) { - errorMsg = 'Cannot post without a name' + errorMsg = "Cannot post without a name"; } - if (!title) missingFields.push('title') + if (!title) missingFields.push("title"); if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', ') - const errMsg = `Missing: ${missingFieldsString}` - errorMsg = errMsg + const missingFieldsString = missingFields.join(", "); + const errMsg = `Missing: ${missingFieldsString}`; + errorMsg = errMsg; } if (errorMsg) { dispatch( setNotification({ msg: errorMsg, - alertType: 'error' + alertType: "error", }) - ) - throw new Error(errorMsg) + ); + throw new Error(errorMsg); } try { - const id = uid() + const id = uid(); - const identifier = `qaudio_qblog_${id}` + const identifier = AUDIO_BASE + id; const resourceResponse = await qortalRequest({ - action: 'PUBLISH_QDN_RESOURCE', + action: "PUBLISH_QDN_RESOURCE", name: name, - service: 'AUDIO', + service: "AUDIO", data64: base64, title: title, description: description, category: category, ...rest, - identifier: identifier - }) + identifier: identifier, + }); dispatch( setNotification({ - msg: 'Audio successfully published', - alertType: 'success' + msg: "Audio successfully published", + alertType: "success", }) - ) - return resourceResponse + ); + return resourceResponse; } catch (error: any) { - let notificationObj = null - if (typeof error === 'string') { + let notificationObj = null; + if (typeof error === "string") { notificationObj = { - msg: error || 'Failed to publish audio', - alertType: 'error' - } - } else if (typeof error?.error === 'string') { + msg: error || "Failed to publish audio", + alertType: "error", + }; + } else if (typeof error?.error === "string") { notificationObj = { - msg: error?.error || 'Failed to publish audio', - alertType: 'error' - } + msg: error?.error || "Failed to publish audio", + alertType: "error", + }; } else { notificationObj = { - msg: error?.message || error?.message || 'Failed to publish audio', - alertType: 'error' - } + msg: error?.message || error?.message || "Failed to publish audio", + alertType: "error", + }; } - if (!notificationObj) return - dispatch(setNotification(notificationObj)) - + if (!notificationObj) return; + dispatch(setNotification(notificationObj)); } - } + }; return { - publishAudio - } -} + publishAudio, + }; +}; diff --git a/src/constants/Identifiers.ts b/src/constants/Identifiers.ts new file mode 100644 index 0000000..3237e08 --- /dev/null +++ b/src/constants/Identifiers.ts @@ -0,0 +1,6 @@ +export const AUDIO_BASE = "qaudio_qblog_"; +export const THREAD_BASE = "qortal_qmail_thread_group"; +export const ATTATCHMENT_BASE = "attachments_qmail_"; +export const QMAIL_BASE = "_mail_qortal_qmail_"; + +export const THREAD_MESSAGE = "qortal_qmail_thmsg_group"; diff --git a/src/pages/Mail/GroupMail.tsx b/src/pages/Mail/GroupMail.tsx index 4e1e58d..2ee959d 100644 --- a/src/pages/Mail/GroupMail.tsx +++ b/src/pages/Mail/GroupMail.tsx @@ -8,6 +8,7 @@ import React, { } from "react"; import { useNavigate } from "react-router-dom"; import { useDispatch, useSelector } from "react-redux"; +import { THREAD_BASE, THREAD_MESSAGE } from "../../constants/Identifiers"; import { RootState } from "../../state/store"; import EditIcon from "@mui/icons-material/Edit"; import { @@ -128,7 +129,7 @@ export const GroupMail = ({ if (isInitial) { dispatch(setIsLoadingCustom("Loading threads")); } - const query = `qortal_qmail_thread_group${groupId}`; + const query = `${THREAD_BASE}${groupId}`; const url = `/arbitrary/resources/search?mode=ALL&service=${THREAD_SERVICE_TYPE}&query=${query}&limit=${20}&includemetadata=true&offset=${offset}&reverse=${isReverse}&excludeblocked=true`; const response = await fetch(url, { method: "GET", @@ -213,7 +214,7 @@ export const GroupMail = ({ .join(""); dispatch(setIsLoadingCustom("Loading recent threads")); - const query = `qortal_qmail_thmsg_group${groupId}`; + const query = `${THREAD_MESSAGE}${groupId}`; const url = `/arbitrary/resources/search?mode=ALL&service=${MAIL_SERVICE_TYPE}&query=${query}&limit=100&includemetadata=false&offset=${0}&reverse=true&excludeblocked=true${queryString}`; const response = await fetch(url, { method: "GET", @@ -237,7 +238,7 @@ export const GroupMail = ({ .map(key => { return { ...messagesForThread[key], - threadId: `qortal_qmail_thread_group${groupId}_${key}`, + threadId: `${THREAD_BASE}${groupId}_${key}`, }; }) .sort((a, b) => b.created - a.created) diff --git a/src/pages/Mail/NewMessage.tsx b/src/pages/Mail/NewMessage.tsx index e0b1036..c702845 100644 --- a/src/pages/Mail/NewMessage.tsx +++ b/src/pages/Mail/NewMessage.tsx @@ -1,64 +1,86 @@ -import React, { Dispatch, useEffect, useState } from 'react' -import { ReusableModal } from '../../components/modals/ReusableModal' -import { Box, Button, Input, Typography, useTheme } 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 CreateIcon from '@mui/icons-material/Create' -import { setNotification } from '../../state/features/notificationsSlice' -import { useNavigate, useLocation, useParams } from 'react-router-dom' -import mime from 'mime'; +import React, { Dispatch, useEffect, useState } from "react"; +import { ReusableModal } from "../../components/modals/ReusableModal"; +import { Box, Button, Input, Typography, useTheme } from "@mui/material"; +import { ATTATCHMENT_BASE, QMAIL_BASE } from "../../constants/Identifiers"; +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 CreateIcon from "@mui/icons-material/Create"; +import { setNotification } from "../../state/features/notificationsSlice"; +import { useNavigate, useLocation, useParams } from "react-router-dom"; +import mime from "mime"; import { objectToBase64, objectToUint8Array, objectToUint8ArrayFromResponse, processFileInChunks, toBase64, - uint8ArrayToBase64 -} from '../../utils/toBase64' + uint8ArrayToBase64, +} from "../../utils/toBase64"; import { MAIL_ATTACHMENT_SERVICE_TYPE, - MAIL_SERVICE_TYPE -} from '../../constants/mail' -import ConfirmationModal from '../../components/common/ConfirmationModal' -import useConfirmationModal from '../../hooks/useConfirmModal' -import { MultiplePublish } from '../../components/common/MultiplePublish/MultiplePublish' -import { ChipInputComponent, NameChip } from '../../components/common/ChipInputComponent/ChipInputComponent' -import { TextEditor } from '../../components/common/TextEditor/TextEditor' -import { AliasLabelP, AttachmentContainer, CloseContainer, ComposeContainer, ComposeIcon, ComposeP, InstanceFooter, InstanceListContainer, InstanceListHeader, NewMessageAliasContainer, NewMessageAttachmentImg, NewMessageCloseImg, NewMessageHeaderP, NewMessageInputLabelP, NewMessageInputRow, NewMessageSendButton, NewMessageSendP } from './Mail-styles' -import ComposeIconSVG from "../../assets/svgs/ComposeIcon.svg" -import ModalCloseSVG from "../../assets/svgs/ModalClose.svg" -import AttachmentSVG from "../../assets/svgs/NewMessageAttachment.svg" -import { Spacer } from '../../components/common/Spacer' -import { SendNewMessage } from '../../assets/svgs/SendNewMessage' -import { AttachmentTwoTone } from '@mui/icons-material' -import { formatBytes } from '../../utils/displaySize' + MAIL_SERVICE_TYPE, +} from "../../constants/mail"; +import ConfirmationModal from "../../components/common/ConfirmationModal"; +import useConfirmationModal from "../../hooks/useConfirmModal"; +import { MultiplePublish } from "../../components/common/MultiplePublish/MultiplePublish"; +import { + ChipInputComponent, + NameChip, +} from "../../components/common/ChipInputComponent/ChipInputComponent"; +import { TextEditor } from "../../components/common/TextEditor/TextEditor"; +import { + AliasLabelP, + AttachmentContainer, + CloseContainer, + ComposeContainer, + ComposeIcon, + ComposeP, + InstanceFooter, + InstanceListContainer, + InstanceListHeader, + NewMessageAliasContainer, + NewMessageAttachmentImg, + NewMessageCloseImg, + NewMessageHeaderP, + NewMessageInputLabelP, + NewMessageInputRow, + NewMessageSendButton, + NewMessageSendP, +} from "./Mail-styles"; +import ComposeIconSVG from "../../assets/svgs/ComposeIcon.svg"; +import ModalCloseSVG from "../../assets/svgs/ModalClose.svg"; +import AttachmentSVG from "../../assets/svgs/NewMessageAttachment.svg"; +import { Spacer } from "../../components/common/Spacer"; +import { SendNewMessage } from "../../assets/svgs/SendNewMessage"; +import { AttachmentTwoTone } from "@mui/icons-material"; +import { formatBytes } from "../../utils/displaySize"; const initialValue: Descendant[] = [ { - type: 'paragraph', - children: [{ text: '' }] - } -] -const uid = new ShortUniqueId() + type: "paragraph", + children: [{ text: "" }], + }, +]; +const uid = new ShortUniqueId(); interface NewMessageProps { - replyTo?: any - setReplyTo: React.Dispatch - alias?: string - hideButton?: boolean - isFromTo?: boolean - setForwardInfo: React.Dispatch - forwardInfo: any + replyTo?: any; + setReplyTo: React.Dispatch; + alias?: string; + hideButton?: boolean; + isFromTo?: boolean; + setForwardInfo: React.Dispatch; + forwardInfo: any; } -const maxSize = 40 * 1024 * 1024 // 40 MB in bytes +const maxSize = 40 * 1024 * 1024; // 40 MB in bytes export const NewMessage = ({ setReplyTo, replyTo, @@ -66,185 +88,184 @@ export const NewMessage = ({ hideButton, isFromTo, setForwardInfo, - forwardInfo + forwardInfo, }: NewMessageProps) => { - const { name } = useParams() + const { name } = useParams(); const [publishes, setPublishes] = useState(null); const [isOpenMultiplePublish, setIsOpenMultiplePublish] = useState(false); - const [isFromToName, setIsFromToName] = useState(null) - const [isOpen, setIsOpen] = useState(false) - const [value, setValue] = useState("") - const [title, setTitle] = useState('') - const [attachments, setAttachments] = useState([]) - const [description, setDescription] = useState('') - const [subject, setSubject] = useState('') - const [destinationName, setDestinationName] = useState('') - const [aliasValue, setAliasValue] = useState('') - const { user } = useSelector((state: RootState) => state.auth) - const [isModalOpen, setIsModalOpen] = useState(false) - const [showAlias, setShowAlias] = useState(false) - const [showBCC, setShowBCC] = useState(false) - const [bccNames, setBccNames] = useState([]) - const theme = useTheme() + const [isFromToName, setIsFromToName] = useState(null); + const [isOpen, setIsOpen] = useState(false); + const [value, setValue] = useState(""); + const [title, setTitle] = useState(""); + const [attachments, setAttachments] = useState([]); + const [description, setDescription] = useState(""); + const [subject, setSubject] = useState(""); + const [destinationName, setDestinationName] = useState(""); + const [aliasValue, setAliasValue] = useState(""); + const { user } = useSelector((state: RootState) => state.auth); + const [isModalOpen, setIsModalOpen] = useState(false); + const [showAlias, setShowAlias] = useState(false); + const [showBCC, setShowBCC] = useState(false); + const [bccNames, setBccNames] = useState([]); + const theme = useTheme(); const { Modal, showModal } = useConfirmationModal({ - title: 'Important', + title: "Important", message: - 'To keep yourself anonymous remember to not use the same alias as the person you are messaging' - }) - const navigate = useNavigate() - const dispatch = useDispatch() - const location = useLocation() + "To keep yourself anonymous remember to not use the same alias as the person you are messaging", + }); + const navigate = useNavigate(); + const dispatch = useDispatch(); + const location = useLocation(); const { getRootProps, getInputProps } = useDropzone({ maxSize, - onDrop: async (acceptedFiles) => { - - let files: any[] = [] + onDrop: async acceptedFiles => { + let files: any[] = []; try { - acceptedFiles.forEach((item)=> { - const type = item?.type - if(!type){ + acceptedFiles.forEach(item => { + const type = item?.type; + if (!type) { files.push({ file: item, mimetype: null, - extension: null - }) + extension: null, + }); } else { - const extension = mime.getExtension(type); - if(!extension){ + const extension = mime.getExtension(type); + if (!extension) { files.push({ file: item, mimetype: type, - extension: null - }) + extension: null, + }); } else { files.push({ file: item, mimetype: type, - extension: extension - }) + extension: extension, + }); } - } - }) + }); } catch (error) { dispatch( setNotification({ - msg: 'One of your files is corrupted', - alertType: 'error' + msg: "One of your files is corrupted", + alertType: "error", }) - ) + ); } - setAttachments((prev) => [...prev, ...files]) + setAttachments(prev => [...prev, ...files]); }, - onDropRejected: (rejectedFiles) => { + onDropRejected: rejectedFiles => { dispatch( setNotification({ - msg: 'One of your files is over the 40mb limit', - alertType: 'error' + msg: "One of your files is over the 40mb limit", + alertType: "error", }) - ) - } - }) + ); + }, + }); const openModal = () => { - setIsOpen(true) + setIsOpen(true); - setReplyTo(null) - setForwardInfo(null) - } + setReplyTo(null); + setForwardInfo(null); + }; const closeModal = () => { - setAttachments([]) - setSubject('') - setDestinationName('') - setBccNames([]) - setShowAlias(false) - setShowBCC(false) - setValue("") - setReplyTo(null) - setForwardInfo(null) - setIsOpen(false) - setAliasValue('') - } + setAttachments([]); + setSubject(""); + setDestinationName(""); + setBccNames([]); + setShowAlias(false); + setShowBCC(false); + setValue(""); + setReplyTo(null); + setForwardInfo(null); + setIsOpen(false); + setAliasValue(""); + }; useEffect(() => { if (isFromTo && name) { - setIsFromToName(name) + setIsFromToName(name); } - }, [isFromTo, name]) + }, [isFromTo, name]); useEffect(() => { - if (!isFromToName) return - setDestinationName(isFromToName) - setIsOpen(true) - setIsFromToName(null) - }, [isFromToName]) + if (!isFromToName) return; + setDestinationName(isFromToName); + setIsOpen(true); + setIsFromToName(null); + }, [isFromToName]); useEffect(() => { if (replyTo) { - setIsOpen(true) - setDestinationName(replyTo?.user || '') - if(replyTo?.subject){ - setSubject(replyTo.subject) + setIsOpen(true); + setDestinationName(replyTo?.user || ""); + if (replyTo?.subject) { + setSubject(replyTo.subject); } } - }, [replyTo]) + }, [replyTo]); useEffect(() => { if (forwardInfo) { - setIsOpen(true) - setValue(forwardInfo) + setIsOpen(true); + setValue(forwardInfo); } - }, [forwardInfo]) - + }, [forwardInfo]); + async function publishQDNResource() { - let address: string = '' - let name: string = '' - let errorMsg = '' + let address: string = ""; + let name: string = ""; + let errorMsg = ""; - address = user?.address || '' - name = user?.name || '' + address = user?.address || ""; + name = user?.name || ""; - const missingFields: string[] = [] + const missingFields: string[] = []; if (!address) { - errorMsg = "Cannot send: your address isn't available" + errorMsg = "Cannot send: your address isn't available"; } if (!name) { - errorMsg = 'Cannot send a message without access to your name' + errorMsg = "Cannot send a message without access to your name"; } if (!destinationName) { - errorMsg = 'Cannot send a message without recipient name' + errorMsg = "Cannot send a message without recipient name"; } // if (!description) missingFields.push('subject') if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', ') - const errMsg = `Missing: ${missingFieldsString}` - errorMsg = errMsg + const missingFieldsString = missingFields.join(", "); + const errMsg = `Missing: ${missingFieldsString}`; + errorMsg = errMsg; } if (alias && !aliasValue) { - errorMsg = 'An alias is required when inside an alias tab' + errorMsg = "An alias is required when inside an alias tab"; } if (alias && alias === aliasValue) { - errorMsg = "The recipient's alias cannot be the same as yours" + errorMsg = "The recipient's alias cannot be the same as yours"; } - const noExtension = attachments.filter(item=> !item.extension) - if(noExtension.length > 0){ - errorMsg = "One of your attachments does not have an extension (example: .png, .pdf, ect...)" + const noExtension = attachments.filter(item => !item.extension); + if (noExtension.length > 0) { + errorMsg = + "One of your attachments does not have an extension (example: .png, .pdf, ect...)"; } - + if (errorMsg) { dispatch( setNotification({ msg: errorMsg, - alertType: 'error' + alertType: "error", }) - ) - throw new Error(errorMsg) + ); + throw new Error(errorMsg); } - + if (aliasValue && !alias) { - const userConfirmed = await showModal() - if (userConfirmed === false) return + const userConfirmed = await showModal(); + if (userConfirmed === false) return; } const mailObject: any = { subject, @@ -254,80 +275,89 @@ export const NewMessage = ({ textContentV2: value, generalData: { thread: [], - threadV2: [] + threadV2: [], }, - recipient: destinationName - } + recipient: destinationName, + }; if (replyTo?.id) { const previousTread = Array.isArray(replyTo?.generalData?.threadV2) ? replyTo?.generalData?.threadV2 - : [] + : []; mailObject.generalData.threadV2 = [ ...previousTread, { reference: { identifier: replyTo.id, - name: replyTo.user, - service: MAIL_SERVICE_TYPE + name: replyTo.user, + service: MAIL_SERVICE_TYPE, }, - data: replyTo - } - ] + data: replyTo, + }, + ]; } - - 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 + 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 - const bccPublicKeys = bccNames.map((item)=> item.publicKey) + action: "GET_ACCOUNT_DATA", + address: recipientAddress, + }); + if (!resAddress?.publicKey) return; + const recipientPublicKey = resAddress.publicKey; + const bccPublicKeys = bccNames.map(item => item.publicKey); // START OF ATTACHMENT LOGIC - const attachmentArray = [] + const attachmentArray: { + name: string; + service: "ATTACHMENT_PRIVATE"; + filename: string; + originalFilename: any; + identifier: string; + data64: string; + type: any; + size: any; + }[] = []; for (const singleAttachment of attachments) { - const attachment = singleAttachment.file - 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 attachment = singleAttachment.file; + 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}` - let fileExtension = attachment?.name?.split('.')?.pop() + const id = uid(); + const id2 = uid(); + const identifier = `${ATTATCHMENT_BASE}${id}_${id2}`; + let fileExtension = attachment?.name?.split(".")?.pop(); if (!fileExtension) { - fileExtension = singleAttachment.extension + fileExtension = singleAttachment.extension; } const obj = { name: name, service: MAIL_ATTACHMENT_SERVICE_TYPE, filename: `${id}.${fileExtension}`, - originalFilename: attachment?.name || '', + originalFilename: attachment?.name || "", identifier, data64: base64String, type: attachment?.type, - size: attachment?.size - } + size: attachment?.size, + }; - attachmentArray.push(obj) + attachmentArray.push(obj); } - const listOfPublishes = [...attachmentArray] + const listOfPublishes = [...attachmentArray]; if (attachmentArray?.length > 0) { - mailObject.attachments = attachmentArray.map((item) => { + mailObject.attachments = attachmentArray.map(item => { return { identifier: item.identifier, name, @@ -335,371 +365,391 @@ export const NewMessage = ({ filename: item.filename, originalFilename: item.originalFilename, type: item?.type, - size: item?.size - } - }) - - + size: item?.size, + }; + }); } //END OF ATTACHMENT LOGIC - - - const blogPostToBase64 = await objectToBase64(mailObject) - let identifier = `_mail_qortal_qmail_${recipientName.slice( + const blogPostToBase64 = await objectToBase64(mailObject); + let identifier = `${QMAIL_BASE}${recipientName.slice( 0, 20 - )}_${recipientAddress.slice(-6)}_mail_${id}` + )}_${recipientAddress.slice(-6)}_mail_${id}`; if (aliasValue) { - identifier = `_mail_qortal_qmail_${aliasValue}_mail_${id}` + identifier = `${QMAIL_BASE}${aliasValue}_mail_${id}`; } let requestBody: any = { - action: 'PUBLISH_QDN_RESOURCE', + action: "PUBLISH_QDN_RESOURCE", name: name, service: MAIL_SERVICE_TYPE, data64: blogPostToBase64, - identifier - } + identifier, + }; - const mails = [requestBody] + const mails = [requestBody]; if (!aliasValue) { + for (const element of bccNames) { + const copyMailObject = structuredClone(mailObject); + copyMailObject.recipient = element.name; + const mailPostToBase64 = await objectToBase64(copyMailObject); + let identifierMail = `${QMAIL_BASE}${element.name.slice( + 0, + 20 + )}_${element.address.slice(-6)}_mail_${id}`; - for (const element of bccNames) { - const copyMailObject = structuredClone(mailObject) - copyMailObject.recipient = element.name - const mailPostToBase64 = await objectToBase64(copyMailObject) - let identifierMail = `_mail_qortal_qmail_${element.name.slice( - 0, - 20 - )}_${element.address.slice(-6)}_mail_${id}` - - let requestBodyMail: any = { - action: 'PUBLISH_QDN_RESOURCE', - name: name, - service: MAIL_SERVICE_TYPE, - data64: mailPostToBase64, - identifier: identifierMail + let requestBodyMail: any = { + action: "PUBLISH_QDN_RESOURCE", + name: name, + service: MAIL_SERVICE_TYPE, + data64: mailPostToBase64, + identifier: identifierMail, + }; + mails.push(requestBodyMail); } - mails.push(requestBodyMail) } - - } // await qortalRequest(requestBody) const multiplePublish = { - action: 'PUBLISH_MULTIPLE_QDN_RESOURCES', + action: "PUBLISH_MULTIPLE_QDN_RESOURCES", resources: [...listOfPublishes, ...mails], encrypt: true, - publicKeys: [recipientPublicKey, ...bccPublicKeys] + publicKeys: [recipientPublicKey, ...bccPublicKeys], }; setPublishes(multiplePublish); setIsOpenMultiplePublish(true); - } catch (error: any) { setIsOpenMultiplePublish(false); - setPublishes(null) - let notificationObj = null - if (typeof error === 'string') { + setPublishes(null); + let notificationObj = null; + if (typeof error === "string") { notificationObj = { - msg: error || 'Failed to send message', - alertType: 'error' - } - } else if (typeof error?.error === 'string') { + msg: error || "Failed to send message", + alertType: "error", + }; + } else if (typeof error?.error === "string") { notificationObj = { - msg: error?.error || 'Failed to send message', - alertType: 'error' - } + msg: error?.error || "Failed to send message", + alertType: "error", + }; } else { notificationObj = { - msg: error?.message || 'Failed to send message', - alertType: 'error' - } + msg: error?.message || "Failed to send message", + alertType: "error", + }; } - if (!notificationObj) return - dispatch(setNotification(notificationObj)) + if (!notificationObj) return; + dispatch(setNotification(notificationObj)); - throw new Error('Failed to send message') + throw new Error("Failed to send message"); } } const sendMail = () => { - publishQDNResource() - } + publishQDNResource(); + }; return ( {!hideButton && ( - - - Compose - + + + Compose + )} - + New Message - + - - - - To: - { - setDestinationName(e.target.value) - }} - disableUnderline - autoComplete='off' - autoCorrect='off' - sx={{ - width: '100%', - color: 'var(--new-message-text)', - '& .MuiInput-input::placeholder': { - color: 'rgba(84, 84, 84, 0.70) !important', - fontSize: '20px', - fontStyle: 'normal', - fontWeight: 400, - lineHeight: '120%', // 24px - letterSpacing: '0.15px', - opacity: 1 - }, - '&:focus': { - outline: 'none', - }, - // Add any additional styles for the input here - }} - /> - - - setShowAlias(true)}>Add Alias - {!replyTo && ( - setShowBCC(true)}>Bcc - )} - - - - - + + + + To: + + { + setDestinationName(e.target.value); + }} + disableUnderline + autoComplete="off" + autoCorrect="off" + sx={{ + width: "100%", + color: "var(--new-message-text)", + "& .MuiInput-input::placeholder": { + color: "rgba(84, 84, 84, 0.70) !important", + fontSize: "20px", + fontStyle: "normal", + fontWeight: 400, + lineHeight: "120%", // 24px + letterSpacing: "0.15px", + opacity: 1, + }, + "&:focus": { + outline: "none", + }, + // Add any additional styles for the input here + }} + /> + + + setShowAlias(true)}> + Add Alias + + {!replyTo && ( + setShowBCC(true)}>Bcc + )} + + + + + { - setSubject(e.target.value) + onChange={e => { + setSubject(e.target.value); }} placeholder="Subject" disableUnderline - autoComplete='off' - autoCorrect='off' + autoComplete="off" + autoCorrect="off" sx={{ - width: '100%', - color: 'var(--new-message-text)', - '& .MuiInput-input::placeholder': { - color: 'rgba(84, 84, 84, 0.70) !important', - fontSize: '20px', - fontStyle: 'normal', + width: "100%", + color: "var(--new-message-text)", + "& .MuiInput-input::placeholder": { + color: "rgba(84, 84, 84, 0.70) !important", + fontSize: "20px", + fontStyle: "normal", fontWeight: 400, - lineHeight: '120%', // 24px - letterSpacing: '0.15px', - opacity: 1 + lineHeight: "120%", // 24px + letterSpacing: "0.15px", + opacity: 1, }, - '&:focus': { - outline: 'none', + "&:focus": { + outline: "none", }, // Add any additional styles for the input here }} /> - - + - {(alias || showAlias) && ( - - + {(alias || showAlias) && ( + + Alias: { - setAliasValue(e.target.value) - }} - disableUnderline - autoComplete='off' - autoCorrect='off' - sx={{ - width: '100%', - color: 'var(--new-message-text)', - '& .MuiInput-input::placeholder': { - color: 'rgba(84, 84, 84, 0.70) !important', - fontSize: '20px', - fontStyle: 'normal', - fontWeight: 400, - lineHeight: '120%', // 24px - letterSpacing: '0.15px', - opacity: 1 - }, - '&:focus': { - outline: 'none', - }, - // Add any additional styles for the input here - }} - /> - - - )} + id="standard-adornment-name" + value={aliasValue} + onChange={e => { + setAliasValue(e.target.value); + }} + disableUnderline + autoComplete="off" + autoCorrect="off" + sx={{ + width: "100%", + color: "var(--new-message-text)", + "& .MuiInput-input::placeholder": { + color: "rgba(84, 84, 84, 0.70) !important", + fontSize: "20px", + fontStyle: "normal", + fontWeight: 400, + lineHeight: "120%", // 24px + letterSpacing: "0.15px", + opacity: 1, + }, + "&:focus": { + outline: "none", + }, + // Add any additional styles for the input here + }} + /> + + + )} -{showBCC && ( - - + {showBCC && ( + + Bcc: - - - )} - - - - - - - - {attachments.map(({file, extension}, index) => { - return ( - + + )} + + + + + + + + {attachments.map(({ file, extension }, index) => { + return ( + + + {file?.name} ({formatBytes(file?.size || 0)}) + + + setAttachments(prev => + prev.filter((item, itemIndex) => itemIndex !== index) + ) + } + sx={{ + height: "16px", + width: "auto", + cursor: "pointer", + color: "rgba(84, 84, 84, 1)", + }} + /> + {!extension && ( + - - {file?.name} ({formatBytes(file?.size || 0)}) - - - setAttachments((prev) => - prev.filter((item, itemIndex) => itemIndex !== index) - ) - } - sx={{ - height: '16px', - width: 'auto', - cursor: 'pointer', - color: 'rgba(84, 84, 84, 1)' - }} - /> - {!extension && ( - - This file has no extension - - )} - - - ) - })} - - - { - setValue(val) - }} /> - + This file has no extension + + )} + + ); + })} + + + { + setValue(val); + }} + /> + - - - {replyTo ? 'Reply' : 'Send Message'} - - + + + + {replyTo ? "Reply" : "Send Message"} + + + - {isOpenMultiplePublish && ( { + onError={messageNotification => { setIsOpenMultiplePublish(false); - setPublishes(null) - if(messageNotification){ + setPublishes(null); + if (messageNotification) { dispatch( setNotification({ msg: messageNotification, - alertType: 'error' + alertType: "error", }) - ) + ); } }} onSubmit={() => { dispatch( setNotification({ - msg: 'Message sent', - alertType: 'success' + msg: "Message sent", + alertType: "success", }) - ) + ); setIsOpenMultiplePublish(false); - setPublishes(null) - closeModal() + setPublishes(null); + closeModal(); }} publishes={publishes} /> )} - ) -} + ); +}; diff --git a/src/pages/Mail/NewThread.tsx b/src/pages/Mail/NewThread.tsx index c937bd5..0f84c8b 100644 --- a/src/pages/Mail/NewThread.tsx +++ b/src/pages/Mail/NewThread.tsx @@ -1,6 +1,8 @@ import React, { Dispatch, useCallback, useEffect, useState } from "react"; import { ReusableModal } from "../../components/modals/ReusableModal"; import { Box, Button, Input, Typography, useTheme } from "@mui/material"; +import { ATTATCHMENT_BASE, THREAD_BASE } from "../../constants/Identifiers"; +import { getFileExtension } from "../../utils/helpers"; import { BuilderButton } from "../CreatePost/CreatePost-styles"; import BlogEditor from "../../components/editor/BlogEditor"; import EmailIcon from "@mui/icons-material/Email"; @@ -19,7 +21,6 @@ import ModalCloseSVG from "../../assets/svgs/ModalClose.svg"; import AttachmentSVG from "../../assets/svgs/NewMessageAttachment.svg"; import CreateThreadSVG from "../../assets/svgs/CreateThread.svg"; - import { objectToBase64, objectToUint8Array, @@ -70,7 +71,7 @@ interface NewMessageProps { currentThread?: any; isMessage?: boolean; messageCallback?: (val: any) => void; - threadCallback?: (val: any)=> void; + threadCallback?: (val: any) => void; refreshLatestThreads?: () => void; members: any; } @@ -83,7 +84,7 @@ export const NewThread = ({ isMessage = false, messageCallback, refreshLatestThreads, - threadCallback + threadCallback, }: NewMessageProps) => { const [isOpen, setIsOpen] = useState(false); const [value, setValue] = useState(""); @@ -98,7 +99,6 @@ export const NewThread = ({ const [publishes, setPublishes] = useState(null); const [callbackContent, setCallbackContent] = useState(null); - const theme = useTheme(); const navigate = useNavigate(); @@ -115,7 +115,7 @@ export const NewThread = ({ files.push({ file: item, mimetype: null, - extension: null, + extension: getFileExtension(item.name), }); } else { const extension = mime.getExtension(type); @@ -123,7 +123,7 @@ export const NewThread = ({ files.push({ file: item, mimetype: type, - extension: null, + extension: getFileExtension(item.name), }); } else { files.push({ @@ -169,7 +169,6 @@ export const NewThread = ({ setIsOpen(false); }; - useEffect(() => { subscribeToEvent("openNewThreadModal", openModalFromEvent); @@ -264,7 +263,7 @@ export const NewThread = ({ const id = uid(); const id2 = uid(); - const identifier = `attachments_qmail_${id}_${id2}`; + const identifier = `${ATTATCHMENT_BASE}${id}_${id2}`; let fileExtension = attachment?.name?.split(".")?.pop(); if (!fileExtension) { fileExtension = singleAttachment.extension; @@ -314,7 +313,7 @@ export const NewThread = ({ name, }; const threadToBase64 = await objectToBase64(threadObject); - let identifierThread = `qortal_qmail_thread_group${groupInfo.id}_${idThread}`; + let identifierThread = `${THREAD_BASE}${groupInfo.id}_${idThread}`; let requestBodyThread: any = { name: name, service: THREAD_SERVICE_TYPE, @@ -366,10 +365,10 @@ export const NewThread = ({ name, threadId: identifierThread, created: Date.now(), - service: 'MAIL_PRIVATE', - identifier: identifier - } - }) + service: "MAIL_PRIVATE", + identifier: identifier, + }, + }); } closeModal(); } else { @@ -410,8 +409,8 @@ export const NewThread = ({ service: MAIL_SERVICE_TYPE, created: Date.now(), ...mailObject, - } - }) + }, + }); // messageCallback({ // identifier, // id: identifier, @@ -484,7 +483,7 @@ export const NewThread = ({ {isMessage ? "Post Message" : "New Thread"} - + {!isMessage && ( <> - - - { - setThreadTitle(e.target.value) - }} - placeholder="Thread Title" - disableUnderline - autoComplete='off' - autoCorrect='off' - sx={{ - width: '100%', - color: 'var(--new-message-text)', - '& .MuiInput-input::placeholder': { - color: 'rgba(84, 84, 84, 0.70) !important', - fontSize: '20px', - fontStyle: 'normal', - fontWeight: 400, - lineHeight: '120%', // 24px - letterSpacing: '0.15px', - opacity: 1 - }, - '&:focus': { - outline: 'none', - }, - // Add any additional styles for the input here - }} - /> - + + + { + setThreadTitle(e.target.value); + }} + placeholder="Thread Title" + disableUnderline + autoComplete="off" + autoCorrect="off" + sx={{ + width: "100%", + color: "var(--new-message-text)", + "& .MuiInput-input::placeholder": { + color: "rgba(84, 84, 84, 0.70) !important", + fontSize: "20px", + fontStyle: "normal", + fontWeight: 400, + lineHeight: "120%", // 24px + letterSpacing: "0.15px", + opacity: 1, + }, + "&:focus": { + outline: "none", + }, + // Add any additional styles for the input here + }} + /> + )} - - - - - + + + {isMessage ? ( - + ) : ( - + )} - - {isOpenMultiplePublish && ( { + onError={messageNotification => { setIsOpenMultiplePublish(false); - setPublishes(null) - setCallbackContent(null) - if(messageNotification){ + setPublishes(null); + setCallbackContent(null); + if (messageNotification) { dispatch( setNotification({ msg: messageNotification, - alertType: 'error' + alertType: "error", }) - ) + ); } }} onSubmit={() => { dispatch( setNotification({ - msg: 'Posted', - alertType: 'success' + msg: "Posted", + alertType: "success", }) - ) - if(messageCallback && callbackContent?.message){ - messageCallback(callbackContent.message) + ); + if (messageCallback && callbackContent?.message) { + messageCallback(callbackContent.message); } - if(threadCallback && callbackContent?.thread){ - threadCallback(callbackContent.thread) + if (threadCallback && callbackContent?.thread) { + threadCallback(callbackContent.thread); } - setCallbackContent(null) + setCallbackContent(null); setIsOpenMultiplePublish(false); - setPublishes(null) + setPublishes(null); - closeModal() + closeModal(); }} publishes={publishes} /> diff --git a/src/pages/Mail/SentMail.tsx b/src/pages/Mail/SentMail.tsx index 37159d0..ca078fc 100644 --- a/src/pages/Mail/SentMail.tsx +++ b/src/pages/Mail/SentMail.tsx @@ -4,112 +4,125 @@ import React, { 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, CircularProgress, 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 { addToHashMapMail } from '../../state/features/mailSlice' + useState, +} from "react"; +import { useNavigate } from "react-router-dom"; +import { useDispatch, useSelector } from "react-redux"; +import { QMAIL_BASE } from "../../constants/Identifiers"; +import { RootState } from "../../state/store"; +import EditIcon from "@mui/icons-material/Edit"; +import { + Box, + Button, + CircularProgress, + 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 { 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' -import { setNotification } from '../../state/features/notificationsSlice' -import { useModal } from '../../components/common/useModal' -import { OpenMail } from './OpenMail' -import { MessagesContainer } from './Mail-styles' -import { MailMessageRow } from './MailMessageRow' + setUserAvatarHash, +} from "../../state/features/globalSlice"; +import SimpleTable from "./MailTable"; +import { MAIL_SERVICE_TYPE } from "../../constants/mail"; +import { BlogPost } from "../../state/features/blogSlice"; +import { setNotification } from "../../state/features/notificationsSlice"; +import { useModal } from "../../components/common/useModal"; +import { OpenMail } from "./OpenMail"; +import { MessagesContainer } from "./Mail-styles"; +import { MailMessageRow } from "./MailMessageRow"; interface SentMailProps { - onOpen: (user: string, identifier: string, content: any, to?:string)=> Promise + onOpen: ( + user: string, + identifier: string, + content: any, + to?: string + ) => Promise; } -export const SentMail = ({onOpen}: SentMailProps) => { - const {isShow, onCancel, onOk, show} = useModal() +export const SentMail = ({ onOpen }: SentMailProps) => { + const { isShow, onCancel, onOk, show } = useModal(); - const theme = useTheme() - const { user } = useSelector((state: RootState) => state.auth) - const [isOpen, setIsOpen] = useState(false) + const theme = useTheme(); + const { user } = useSelector((state: RootState) => state.auth); + const [isOpen, setIsOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); - const [message, setMessage] = useState(null) - const [replyTo, setReplyTo] = useState(null) - const [valueTab, setValueTab] = React.useState(0) - const [aliasValue, setAliasValue] = useState('') - const [alias, setAlias] = useState([]) - const [mailInfo, setMailInfo] = useState(null) + const [message, setMessage] = useState(null); + const [replyTo, setReplyTo] = useState(null); + const [valueTab, setValueTab] = React.useState(0); + const [aliasValue, setAliasValue] = useState(""); + const [alias, setAlias] = useState([]); + const [mailInfo, setMailInfo] = useState(null); const hashMapPosts = useSelector( (state: RootState) => state.blog.hashMapPosts - ) - const [mailMessages, setMailMessages] = useState([]) + ); + const [mailMessages, setMailMessages] = useState([]); const hashMapMailMessages = useSelector( (state: RootState) => state.mail.hashMapMailMessages - ) + ); const fullMailMessages = useMemo(() => { - return mailMessages.map((msg) => { - let message = msg - const existingMessage = hashMapMailMessages[msg.id] + return mailMessages.map(msg => { + let message = msg; + const existingMessage = hashMapMailMessages[msg.id]; if (existingMessage) { - message = existingMessage + message = existingMessage; } - return message - }) - }, [mailMessages, hashMapMailMessages, user]) - const dispatch = useDispatch() - const navigate = useNavigate() + return message; + }); + }, [mailMessages, hashMapMailMessages, user]); + const dispatch = useDispatch(); + const navigate = useNavigate(); const getAvatar = async (user: string) => { try { let url = await qortalRequest({ - action: 'GET_QDN_RESOURCE_URL', + action: "GET_QDN_RESOURCE_URL", name: user, - service: 'THUMBNAIL', - identifier: 'qortal_avatar' - }) + service: "THUMBNAIL", + identifier: "qortal_avatar", + }); dispatch( setUserAvatarHash({ name: user, - url + url, }) - ) + ); } catch (error) {} - } + }; const checkNewMessages = React.useCallback( async (recipientName: string, recipientAddress: string) => { try { - if (!user?.name) return - const query = `_mail_qortal_qmail_` - const url = `/arbitrary/resources/search?mode=ALL&service=${MAIL_SERVICE_TYPE}&identifier=_mail_&query=${query}&name=${user?.name}&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() + if (!user?.name) return; - const latestPost = mailMessages[0] - if (!latestPost) return + const url = `/arbitrary/resources/search?mode=ALL&service=${MAIL_SERVICE_TYPE}&identifier=_mail_&query=${QMAIL_BASE}&name=${user?.name}&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 + return; } - const newArray = responseData.slice(0, findPost) + const newArray = responseData.slice(0, findPost); const structureData = newArray.map((post: any): BlogPost => { return { title: post?.metadata?.title, @@ -120,50 +133,50 @@ export const SentMail = ({onOpen}: SentMailProps) => { createdAt: post?.created, updated: post?.updated, user: post.name, - id: post.identifier - } - }) - setMailMessages((prev) => { - const updatedMessages = [...prev] + id: post.identifier, + }; + }); + setMailMessages(prev => { + const updatedMessages = [...prev]; structureData.forEach((newMessage: any) => { const existingIndex = updatedMessages.findIndex( - (prevMessage) => prevMessage.id === newMessage.id - ) + prevMessage => prevMessage.id === newMessage.id + ); if (existingIndex !== -1) { // Replace existing message - updatedMessages[existingIndex] = newMessage + updatedMessages[existingIndex] = newMessage; } else { // Add new message - updatedMessages.unshift(newMessage) + updatedMessages.unshift(newMessage); } - }) + }); - return updatedMessages - }) - return + return updatedMessages; + }); + return; } catch (error) {} }, [mailMessages] - ) + ); const getMailMessages = React.useCallback( async (recipientName: string, recipientAddress: string) => { try { - if (!user?.name) return - const offset = mailMessages.length + if (!user?.name) return; + const offset = mailMessages.length; // dispatch(setIsLoadingGlobal(true)) - const query = `_mail_qortal_qmail_` - const url = `/arbitrary/resources/search?mode=ALL&service=${MAIL_SERVICE_TYPE}&identifier=_mail_&query=${query}&name=${user.name}&limit=20&includemetadata=true&offset=${offset}&reverse=true&excludeblocked=true` + + const url = `/arbitrary/resources/search?mode=ALL&service=${MAIL_SERVICE_TYPE}&identifier=_mail_&query=${QMAIL_BASE}&name=${user.name}&limit=20&includemetadata=true&offset=${offset}&reverse=true&excludeblocked=true`; const response = await fetch(url, { - method: 'GET', + method: "GET", headers: { - 'Content-Type': 'application/json' - } - }) - const responseData = await response.json() + "Content-Type": "application/json", + }, + }); + const responseData = await response.json(); const structureData = responseData.map((post: any): BlogPost => { return { title: post?.metadata?.title, @@ -174,32 +187,32 @@ export const SentMail = ({onOpen}: SentMailProps) => { createdAt: post?.created, updated: post?.updated, user: post.name, - id: post.identifier - } - }) - setMailMessages((prev) => { - const updatedMessages = [...prev] + id: post.identifier, + }; + }); + setMailMessages(prev => { + const updatedMessages = [...prev]; structureData.forEach((newMessage: any) => { const existingIndex = updatedMessages.findIndex( - (prevMessage) => prevMessage.id === newMessage.id - ) + prevMessage => prevMessage.id === newMessage.id + ); if (existingIndex !== -1) { // Replace existing message - updatedMessages[existingIndex] = newMessage + updatedMessages[existingIndex] = newMessage; } else { // Add new message - updatedMessages.push(newMessage) + updatedMessages.push(newMessage); } - }) + }); - return updatedMessages - }) + return updatedMessages; + }); for (const content of structureData) { if (content.user && content.id) { - getAvatar(content.user) + getAvatar(content.user); } } } catch (error) { @@ -208,19 +221,22 @@ export const SentMail = ({onOpen}: SentMailProps) => { } }, [mailMessages, hashMapMailMessages, user] - ) - const getMessages = React.useCallback(async (isOnMount?: boolean) => { - if (!user?.name || !user?.address) return; - try { - if (isOnMount) { - setIsLoading(true); + ); + const getMessages = React.useCallback( + async (isOnMount?: boolean) => { + if (!user?.name || !user?.address) return; + try { + if (isOnMount) { + setIsLoading(true); + } + await getMailMessages(user.name, user.address); + } catch (error) { + } finally { + setIsLoading(false); } - await getMailMessages(user.name, user.address); - } catch (error) { - } finally { - setIsLoading(false); - } - }, [getMailMessages, user]) + }, + [getMailMessages, user] + ); const firstMount = useRef(false); useEffect(() => { @@ -230,30 +246,27 @@ export const SentMail = ({onOpen}: SentMailProps) => { } }, [user]); - const interval = useRef(null) + const interval = useRef(null); const checkNewMessagesFunc = useCallback(() => { - if (!user?.name || !user?.address) return - let isCalling = false + 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]) + if (isCalling || !user?.name || !user?.address) return; + isCalling = true; + const res = await checkNewMessages(user?.name, user.address); + isCalling = false; + }, 30000); + }, [checkNewMessages, user]); useEffect(() => { - checkNewMessagesFunc() + checkNewMessagesFunc(); return () => { if (interval?.current) { - clearInterval(interval.current) + clearInterval(interval.current); } - } - }, [checkNewMessagesFunc]) - - - + }; + }, [checkNewMessagesFunc]); const openMessage = async ( user: string, @@ -262,16 +275,15 @@ export const SentMail = ({onOpen}: SentMailProps) => { to?: string ) => { try { - onOpen(user, messageIdentifier, {}, to) - + onOpen(user, messageIdentifier, {}, to); } finally { } - } + }; return ( <> - {mailInfo && isShow && ( - + {mailInfo && isShow && ( + )} {/* */} { message={message} setReplyTo={setReplyTo} /> - - {fullMailMessages.map(item => { - return ( - - ); - })} - - {isLoading && ( - - - - )} - + + {fullMailMessages.map(item => { + return ( + + ); + })} + + {isLoading && ( + + + + )} + {/* */} - ) -} + ); +}; diff --git a/src/pages/Mail/ShowMessageWithoutModal.tsx b/src/pages/Mail/ShowMessageWithoutModal.tsx index 3f137d2..46cf669 100644 --- a/src/pages/Mail/ShowMessageWithoutModal.tsx +++ b/src/pages/Mail/ShowMessageWithoutModal.tsx @@ -63,7 +63,7 @@ export const ShowMessage = ({ message }: any) => { height: "auto", alignItems: "flex-start", cursor: "default", - borderRadius: '35px 4px 4px 4px' + borderRadius: "35px 4px 4px 4px", }} > { display: "flex", flexDirection: "column", alignItems: "flex-start", - width: '100%' + width: "100%", }} > { display: "flex", alignItems: "flex-start", gap: "10px", - }} > {
- {message?.attachments?.length > 0 && ( - - {message?.attachments - .map((file: any, index: number) => { - const isFirst = index === 0 - return ( - + {message?.attachments?.length > 0 && ( + + {message?.attachments.map((file: any, index: number) => { + const isFirst = index === 0; + return ( - - + + - - {file?.originalFilename || file?.filename} - - - {message?.attachments?.length > 1 && isFirst && ( - { - setExpandAttachments(prev => !prev); - }} - > - - - {expandAttachments ? 'hide' : `(${message?.attachments?.length - 1} more)`} - - - - )} + > + {file?.originalFilename || file?.filename} + + + {message?.attachments?.length > 1 && isFirst && ( + { + setExpandAttachments(prev => !prev); + }} + > + + + {expandAttachments + ? "hide" + : `(${message?.attachments?.length - 1} more)`} + + + )} + - - ); - }) - } - - )} - -
+ ); + })} +
+ )} +
@@ -198,9 +200,6 @@ export const ShowMessage = ({ message }: any) => {
)} - - - ); }; diff --git a/src/pages/Mail/Thread.tsx b/src/pages/Mail/Thread.tsx index 3a60cfc..af788c2 100644 --- a/src/pages/Mail/Thread.tsx +++ b/src/pages/Mail/Thread.tsx @@ -8,6 +8,7 @@ import React, { } from "react"; import { useNavigate } from "react-router-dom"; import { useDispatch, useSelector } from "react-redux"; +import { THREAD_MESSAGE } from "../../constants/Identifiers"; import { RootState } from "../../state/store"; import { @@ -98,7 +99,7 @@ export const Thread = ({ let result = parts[0]; const threadId = result; const offset = messages.length; - const query = `qortal_qmail_thmsg_group${groupInfo?.threadData?.groupId}_${threadId}`; + const query = `${THREAD_MESSAGE}${groupInfo?.threadData?.groupId}_${threadId}`; const url = `/arbitrary/resources/search?mode=ALL&service=${MAIL_SERVICE_TYPE}&query=${query}&limit=20&includemetadata=false&offset=${offset}&reverse=true&excludeblocked=true`; const response = await fetch(url, { method: "GET", @@ -194,7 +195,7 @@ export const Thread = ({ let parts = str.split("_").reverse(); let result = parts[0]; const threadId = result; - const query = `qortal_qmail_thmsg_group${groupInfo?.threadData?.groupId}_${threadId}`; + const query = `${THREAD_MESSAGE}${groupInfo?.threadData?.groupId}_${threadId}`; const url = `/arbitrary/resources/search?mode=ALL&service=${MAIL_SERVICE_TYPE}&query=${query}&limit=20&includemetadata=false&offset=${0}&reverse=true&excludeblocked=true`; const response = await fetch(url, { method: "GET", @@ -328,8 +329,7 @@ export const Thread = ({ } return ( - - key={message?.identifier} + new Promise((_, reject) => - setTimeout(() => reject(new Error('Request timed out')), time) -); +export const delay = (time: number) => + new Promise((_, reject) => + setTimeout(() => reject(new Error("Request timed out")), time) + ); // const originalHtml = `

---------- Forwarded message ---------

From: Alex

Date: Mon, Jun 9 2014 9:32 PM

Subject: Batteries

To: Jessica



`; - // export function updateMessageDetails(newFrom: string, newDateMillis: number, newTo: string) { // let htmlString = originalHtml // // Use Moment.js to format the date from milliseconds @@ -22,13 +20,27 @@ export const delay = (time: number) => new Promise((_, reject) => const originalHtml = `

---------- Forwarded message ---------

From: Alex

Subject: Batteries

To: Jessica



`; +export function updateMessageDetails( + newFrom: string, + newSubject: string, + newTo: string +) { + let htmlString = originalHtml; -export function updateMessageDetails(newFrom: string, newSubject: string, newTo: string) { - let htmlString = originalHtml + htmlString = htmlString.replace( + /

From:.*?<\/p>/, + `

From: ${newFrom}

` + ); + htmlString = htmlString.replace( + /

Subject:.*?<\/p>/, + `

Subject: ${newSubject}

` + ); + htmlString = htmlString.replace(/

To:.*?<\/p>/, `

To: ${newTo}

`); - htmlString = htmlString.replace(/

From:.*?<\/p>/, `

From: ${newFrom}

`); - htmlString = htmlString.replace(/

Subject:.*?<\/p>/, `

Subject: ${newSubject}

`); - htmlString = htmlString.replace(/

To:.*?<\/p>/, `

To: ${newTo}

`); + return htmlString; +} - return htmlString; -} \ No newline at end of file +export const getFileExtension = (fileName: string) => { + if (!fileName.includes(".")) return null; + return fileName.split(".").at(-1); +};