import React, { useEffect, useRef, useState } from "react"; import { Box, Button, CircularProgress, Input, Typography } from "@mui/material"; import ShortUniqueId from "short-unique-id"; import CloseIcon from "@mui/icons-material/Close"; import ModalCloseSVG from "../../../assets/svgs/ModalClose.svg"; import ComposeIconSVG from "../../../assets/svgs/ComposeIcon.svg"; import { AttachmentContainer, CloseContainer, ComposeContainer, ComposeIcon, ComposeP, InstanceFooter, InstanceListContainer, InstanceListHeader, NewMessageAttachmentImg, NewMessageCloseImg, NewMessageHeaderP, NewMessageInputRow, NewMessageSendButton, NewMessageSendP, } from "./Mail-styles"; import { ReusableModal } from "./ReusableModal"; import { Spacer } from "../../../common/Spacer"; import { formatBytes } from "../../../utils/Size"; import { CreateThreadIcon } from "../../../assets/svgs/CreateThreadIcon"; import { SendNewMessage } from "../../../assets/svgs/SendNewMessage"; import { TextEditor } from "./TextEditor"; import { MyContext, isMobile, pauseAllQueues, resumeAllQueues } from "../../../App"; import { getFee } from "../../../background"; import TipTap from "../../Chat/TipTap"; import { MessageDisplay } from "../../Chat/MessageDisplay"; import { CustomizedSnackbars } from "../../Snackbar/Snackbar"; import { saveTempPublish } from "../../Chat/GroupAnnouncements"; const uid = new ShortUniqueId({ length: 8 }); export const toBase64 = (file: File): Promise => new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = () => resolve(reader.result); reader.onerror = (error) => { reject(error); }; }); export function objectToBase64(obj: any) { // Step 1: Convert the object to a JSON string const jsonString = JSON.stringify(obj); // Step 2: Create a Blob from the JSON string const blob = new Blob([jsonString], { type: "application/json" }); // Step 3: Create a FileReader to read the Blob as a base64-encoded string return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onloadend = () => { if (typeof reader.result === "string") { // Remove 'data:application/json;base64,' prefix const base64 = reader.result.replace( "data:application/json;base64,", "" ); resolve(base64); } else { reject(new Error("Failed to read the Blob as a base64-encoded string")); } }; reader.onerror = () => { reject(reader.error); }; reader.readAsDataURL(blob); }); } interface NewMessageProps { hideButton?: boolean; groupInfo: any; currentThread?: any; isMessage?: boolean; messageCallback?: (val: any) => void; publishCallback?: () => void; refreshLatestThreads?: () => void; members: any; } export const publishGroupEncryptedResource = async ({ encryptedData, identifier, }) => { return new Promise((res, rej) => { chrome?.runtime?.sendMessage( { action: "publishGroupEncryptedResource", payload: { encryptedData, identifier, }, }, (response) => { if (!response?.error) { res(response); return } rej(response.error); } ); }); }; export const encryptSingleFunc = async (data: string, secretKeyObject: any) => { try { return new Promise((res, rej) => { chrome?.runtime?.sendMessage( { action: "encryptSingle", payload: { data, secretKeyObject, }, }, (response) => { if (!response?.error) { res(response); return; } rej(response.error); } ); }); } catch (error) {} }; export const NewThread = ({ groupInfo, members, currentThread, isMessage = false, publishCallback, userInfo, getSecretKey, closeCallback, postReply, myName }: NewMessageProps) => { const { show } = React.useContext(MyContext); const [isOpen, setIsOpen] = useState(false); const [value, setValue] = useState(""); const [isSending, setIsSending] = useState(false); const [threadTitle, setThreadTitle] = useState(""); const [openSnack, setOpenSnack] = React.useState(false); const [infoSnack, setInfoSnack] = React.useState(null); const editorRef = useRef(null); const setEditorRef = (editorInstance) => { editorRef.current = editorInstance; }; useEffect(() => { if (postReply) { setIsOpen(true); } }, [postReply]); const closeModal = () => { setIsOpen(false); setValue(""); }; async function publishQDNResource() { try { pauseAllQueues() if(isSending) return setIsSending(true) let name: string = ""; let errorMsg = ""; name = userInfo?.name || ""; const missingFields: string[] = []; if (!isMessage && !threadTitle) { errorMsg = "Please provide a thread title"; } if (!name) { errorMsg = "Cannot send a message without a access to your name"; } if (!groupInfo) { errorMsg = "Cannot access group information"; } // 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 htmlContent = editorRef.current.getHTML(); if (!htmlContent?.trim() || htmlContent?.trim() === "

") throw new Error("Please provide a first message to the thread"); const fee = await getFee("ARBITRARY"); let feeToShow = fee.fee; if (!isMessage) { feeToShow = +feeToShow * 2; } await show({ message: "Would you like to perform a ARBITRARY transaction?", publishFee: feeToShow + " QORT", }); let reply = null; if (postReply) { reply = { ...postReply }; if (reply.reply) { delete reply.reply; } } const mailObject: any = { createdAt: Date.now(), version: 1, textContentV2: htmlContent, name, threadOwner: currentThread?.threadData?.name || name, reply, }; const secretKey = await getSecretKey(false, true); if (!secretKey) { throw new Error("Cannot get group secret key"); } if (!isMessage) { const idThread = uid.rnd(); const idMsg = uid.rnd(); const messageToBase64 = await objectToBase64(mailObject); const encryptSingleFirstPost = await encryptSingleFunc( messageToBase64, secretKey ); const threadObject = { title: threadTitle, groupId: groupInfo.id, createdAt: Date.now(), name, }; const threadToBase64 = await objectToBase64(threadObject); const encryptSingleThread = await encryptSingleFunc( threadToBase64, secretKey ); let identifierThread = `grp-${groupInfo.groupId}-thread-${idThread}`; await publishGroupEncryptedResource({ identifier: identifierThread, encryptedData: encryptSingleThread, }); let identifierPost = `thmsg-${identifierThread}-${idMsg}`; await publishGroupEncryptedResource({ identifier: identifierPost, encryptedData: encryptSingleFirstPost, }); const dataToSaveToStorage = { name: myName, identifier: identifierThread, service: 'DOCUMENT', tempData: threadObject, created: Date.now(), } const dataToSaveToStoragePost = { name: myName, identifier: identifierPost, service: 'DOCUMENT', tempData: mailObject, created: Date.now(), threadId: identifierThread } await saveTempPublish({data: dataToSaveToStorage, key: 'thread'}) await saveTempPublish({data: dataToSaveToStoragePost, key: 'thread-post'}) setInfoSnack({ type: "success", message: "Successfully created thread. It may take some time for the publish to propagate", }); setOpenSnack(true) // dispatch( // setNotification({ // msg: "Message sent", // alertType: "success", // }) // ); if (publishCallback) { publishCallback() // threadCallback({ // threadData: threadObject, // threadOwner: name, // name, // threadId: identifierThread, // created: Date.now(), // service: 'MAIL_PRIVATE', // identifier: identifier // }) } closeModal(); } else { if (!currentThread) throw new Error("unable to locate thread Id"); const idThread = currentThread.threadId; const messageToBase64 = await objectToBase64(mailObject); const encryptSinglePost = await encryptSingleFunc( messageToBase64, secretKey ); const idMsg = uid.rnd(); let identifier = `thmsg-${idThread}-${idMsg}`; const res = await publishGroupEncryptedResource({ identifier: identifier, encryptedData: encryptSinglePost, }); const dataToSaveToStoragePost = { threadId: idThread, name: myName, identifier: identifier, service: 'DOCUMENT', tempData: mailObject, created: Date.now() } await saveTempPublish({data: dataToSaveToStoragePost, key: 'thread-post'}) // await qortalRequest(multiplePublishMsg); // dispatch( // setNotification({ // msg: "Message sent", // alertType: "success", // }) // ); setInfoSnack({ type: "success", message: "Successfully created post. It may take some time for the publish to propagate", }); setOpenSnack(true) if(publishCallback){ publishCallback() } // messageCallback({ // identifier, // id: identifier, // name, // service: MAIL_SERVICE_TYPE, // created: Date.now(), // ...mailObject, // }); } closeModal(); } catch (error: any) { if(error?.message){ setInfoSnack({ type: "error", message: error?.message, }); setOpenSnack(true) } } finally { setIsSending(false); resumeAllQueues() } } const sendMail = () => { publishQDNResource(); }; return ( setIsOpen(true)} > {currentThread ? "New Post" : "New Thread"} {isMessage ? "Post Message" : "New Thread"} {!isMessage && ( <> { setThreadTitle(e.target.value); }} placeholder="Thread Title" disableUnderline autoComplete="off" autoCorrect="off" sx={{ width: "100%", color: "white", "& .MuiInput-input::placeholder": { color: "rgba(255,255,255, 0.70) !important", fontSize: isMobile ? '14px' : "20px", fontStyle: "normal", fontWeight: 400, lineHeight: "120%", // 24px letterSpacing: "0.15px", opacity: 1, }, "&:focus": { outline: "none", }, // Add any additional styles for the input here }} /> )} {postReply && postReply.textContentV2 && ( )} {!isMobile && ( )} {/* { setValue(val); }} /> */} {isSending && ( )} {isMessage ? "Post" : "Create Thread"} {isMessage ? ( ) : ( )} ); };