Identifiers.ts created, all identifiers used in app are located here.

Fixed warning involving messages in Thread.tsx not having a key prop.
This commit is contained in:
Qortal Dev 2024-08-23 15:33:33 -06:00
parent 8d54ec7d1c
commit 6ad071376b
9 changed files with 950 additions and 866 deletions

View File

@ -1,21 +1,22 @@
import React from 'react' import React from "react";
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from "react-redux";
import { setNotification } from '../../state/features/notificationsSlice' import { AUDIO_BASE } from "../../constants/Identifiers";
import { RootState } from '../../state/store' import { setNotification } from "../../state/features/notificationsSlice";
import ShortUniqueId from 'short-unique-id' import { RootState } from "../../state/store";
import ShortUniqueId from "short-unique-id";
const uid = new ShortUniqueId() const uid = new ShortUniqueId();
interface IPublishVideo { interface IPublishVideo {
title: string title: string;
description: string description: string;
base64: string base64: string;
category: string category: string;
} }
export const usePublishAudio = () => { export const usePublishAudio = () => {
const { user } = useSelector((state: RootState) => state.auth) const { user } = useSelector((state: RootState) => state.auth);
const dispatch = useDispatch() const dispatch = useDispatch();
const publishAudio = async ({ const publishAudio = async ({
title, title,
description, description,
@ -23,84 +24,83 @@ export const usePublishAudio = () => {
category, category,
...rest ...rest
}: IPublishVideo) => { }: IPublishVideo) => {
let address let address;
let name let name;
let errorMsg = '' let errorMsg = "";
address = user?.address address = user?.address;
name = user?.name || '' name = user?.name || "";
const missingFields = [] const missingFields = [];
if (!address) { if (!address) {
errorMsg = "Cannot post: your address isn't available" errorMsg = "Cannot post: your address isn't available";
} }
if (!name) { 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) { if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(', ') const missingFieldsString = missingFields.join(", ");
const errMsg = `Missing: ${missingFieldsString}` const errMsg = `Missing: ${missingFieldsString}`;
errorMsg = errMsg errorMsg = errMsg;
} }
if (errorMsg) { if (errorMsg) {
dispatch( dispatch(
setNotification({ setNotification({
msg: errorMsg, msg: errorMsg,
alertType: 'error' alertType: "error",
}) })
) );
throw new Error(errorMsg) throw new Error(errorMsg);
} }
try { try {
const id = uid() const id = uid();
const identifier = `qaudio_qblog_${id}` const identifier = AUDIO_BASE + id;
const resourceResponse = await qortalRequest({ const resourceResponse = await qortalRequest({
action: 'PUBLISH_QDN_RESOURCE', action: "PUBLISH_QDN_RESOURCE",
name: name, name: name,
service: 'AUDIO', service: "AUDIO",
data64: base64, data64: base64,
title: title, title: title,
description: description, description: description,
category: category, category: category,
...rest, ...rest,
identifier: identifier identifier: identifier,
}) });
dispatch( dispatch(
setNotification({ setNotification({
msg: 'Audio successfully published', msg: "Audio successfully published",
alertType: 'success' alertType: "success",
}) })
) );
return resourceResponse return resourceResponse;
} catch (error: any) { } catch (error: any) {
let notificationObj = null let notificationObj = null;
if (typeof error === 'string') { if (typeof error === "string") {
notificationObj = { notificationObj = {
msg: error || 'Failed to publish audio', msg: error || "Failed to publish audio",
alertType: 'error' alertType: "error",
} };
} else if (typeof error?.error === 'string') { } else if (typeof error?.error === "string") {
notificationObj = { notificationObj = {
msg: error?.error || 'Failed to publish audio', msg: error?.error || "Failed to publish audio",
alertType: 'error' alertType: "error",
} };
} else { } else {
notificationObj = { notificationObj = {
msg: error?.message || error?.message || 'Failed to publish audio', msg: error?.message || error?.message || "Failed to publish audio",
alertType: 'error' alertType: "error",
} };
} }
if (!notificationObj) return if (!notificationObj) return;
dispatch(setNotification(notificationObj)) dispatch(setNotification(notificationObj));
} }
} };
return { return {
publishAudio publishAudio,
} };
} };

View File

@ -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";

View File

@ -8,6 +8,7 @@ import React, {
} from "react"; } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { THREAD_BASE, THREAD_MESSAGE } from "../../constants/Identifiers";
import { RootState } from "../../state/store"; import { RootState } from "../../state/store";
import EditIcon from "@mui/icons-material/Edit"; import EditIcon from "@mui/icons-material/Edit";
import { import {
@ -128,7 +129,7 @@ export const GroupMail = ({
if (isInitial) { if (isInitial) {
dispatch(setIsLoadingCustom("Loading threads")); 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 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, { const response = await fetch(url, {
method: "GET", method: "GET",
@ -213,7 +214,7 @@ export const GroupMail = ({
.join(""); .join("");
dispatch(setIsLoadingCustom("Loading recent threads")); 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 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, { const response = await fetch(url, {
method: "GET", method: "GET",
@ -237,7 +238,7 @@ export const GroupMail = ({
.map(key => { .map(key => {
return { return {
...messagesForThread[key], ...messagesForThread[key],
threadId: `qortal_qmail_thread_group${groupId}_${key}`, threadId: `${THREAD_BASE}${groupId}_${key}`,
}; };
}) })
.sort((a, b) => b.created - a.created) .sort((a, b) => b.created - a.created)

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,8 @@
import React, { Dispatch, useCallback, useEffect, useState } from "react"; import React, { Dispatch, useCallback, useEffect, useState } from "react";
import { ReusableModal } from "../../components/modals/ReusableModal"; import { ReusableModal } from "../../components/modals/ReusableModal";
import { Box, Button, Input, Typography, useTheme } from "@mui/material"; 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 { BuilderButton } from "../CreatePost/CreatePost-styles";
import BlogEditor from "../../components/editor/BlogEditor"; import BlogEditor from "../../components/editor/BlogEditor";
import EmailIcon from "@mui/icons-material/Email"; 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 AttachmentSVG from "../../assets/svgs/NewMessageAttachment.svg";
import CreateThreadSVG from "../../assets/svgs/CreateThread.svg"; import CreateThreadSVG from "../../assets/svgs/CreateThread.svg";
import { import {
objectToBase64, objectToBase64,
objectToUint8Array, objectToUint8Array,
@ -70,7 +71,7 @@ interface NewMessageProps {
currentThread?: any; currentThread?: any;
isMessage?: boolean; isMessage?: boolean;
messageCallback?: (val: any) => void; messageCallback?: (val: any) => void;
threadCallback?: (val: any)=> void; threadCallback?: (val: any) => void;
refreshLatestThreads?: () => void; refreshLatestThreads?: () => void;
members: any; members: any;
} }
@ -83,7 +84,7 @@ export const NewThread = ({
isMessage = false, isMessage = false,
messageCallback, messageCallback,
refreshLatestThreads, refreshLatestThreads,
threadCallback threadCallback,
}: NewMessageProps) => { }: NewMessageProps) => {
const [isOpen, setIsOpen] = useState<boolean>(false); const [isOpen, setIsOpen] = useState<boolean>(false);
const [value, setValue] = useState(""); const [value, setValue] = useState("");
@ -98,7 +99,6 @@ export const NewThread = ({
const [publishes, setPublishes] = useState<any>(null); const [publishes, setPublishes] = useState<any>(null);
const [callbackContent, setCallbackContent] = useState<any>(null); const [callbackContent, setCallbackContent] = useState<any>(null);
const theme = useTheme(); const theme = useTheme();
const navigate = useNavigate(); const navigate = useNavigate();
@ -115,7 +115,7 @@ export const NewThread = ({
files.push({ files.push({
file: item, file: item,
mimetype: null, mimetype: null,
extension: null, extension: getFileExtension(item.name),
}); });
} else { } else {
const extension = mime.getExtension(type); const extension = mime.getExtension(type);
@ -123,7 +123,7 @@ export const NewThread = ({
files.push({ files.push({
file: item, file: item,
mimetype: type, mimetype: type,
extension: null, extension: getFileExtension(item.name),
}); });
} else { } else {
files.push({ files.push({
@ -169,7 +169,6 @@ export const NewThread = ({
setIsOpen(false); setIsOpen(false);
}; };
useEffect(() => { useEffect(() => {
subscribeToEvent("openNewThreadModal", openModalFromEvent); subscribeToEvent("openNewThreadModal", openModalFromEvent);
@ -264,7 +263,7 @@ export const NewThread = ({
const id = uid(); const id = uid();
const id2 = uid(); const id2 = uid();
const identifier = `attachments_qmail_${id}_${id2}`; const identifier = `${ATTATCHMENT_BASE}${id}_${id2}`;
let fileExtension = attachment?.name?.split(".")?.pop(); let fileExtension = attachment?.name?.split(".")?.pop();
if (!fileExtension) { if (!fileExtension) {
fileExtension = singleAttachment.extension; fileExtension = singleAttachment.extension;
@ -314,7 +313,7 @@ export const NewThread = ({
name, name,
}; };
const threadToBase64 = await objectToBase64(threadObject); const threadToBase64 = await objectToBase64(threadObject);
let identifierThread = `qortal_qmail_thread_group${groupInfo.id}_${idThread}`; let identifierThread = `${THREAD_BASE}${groupInfo.id}_${idThread}`;
let requestBodyThread: any = { let requestBodyThread: any = {
name: name, name: name,
service: THREAD_SERVICE_TYPE, service: THREAD_SERVICE_TYPE,
@ -366,10 +365,10 @@ export const NewThread = ({
name, name,
threadId: identifierThread, threadId: identifierThread,
created: Date.now(), created: Date.now(),
service: 'MAIL_PRIVATE', service: "MAIL_PRIVATE",
identifier: identifier identifier: identifier,
} },
}) });
} }
closeModal(); closeModal();
} else { } else {
@ -410,8 +409,8 @@ export const NewThread = ({
service: MAIL_SERVICE_TYPE, service: MAIL_SERVICE_TYPE,
created: Date.now(), created: Date.now(),
...mailObject, ...mailObject,
} },
}) });
// messageCallback({ // messageCallback({
// identifier, // identifier,
// id: identifier, // id: identifier,
@ -484,7 +483,7 @@ export const NewThread = ({
{isMessage ? "Post Message" : "New Thread"} {isMessage ? "Post Message" : "New Thread"}
</NewMessageHeaderP> </NewMessageHeaderP>
<CloseContainer onClick={closeModal}> <CloseContainer onClick={closeModal}>
<NewMessageCloseImg src={ModalCloseSVG} /> <NewMessageCloseImg src={ModalCloseSVG} />
</CloseContainer> </CloseContainer>
</InstanceListHeader> </InstanceListHeader>
<InstanceListContainer <InstanceListContainer
@ -497,46 +496,46 @@ export const NewThread = ({
> >
{!isMessage && ( {!isMessage && (
<> <>
<Spacer height="10px" /> <Spacer height="10px" />
<NewMessageInputRow> <NewMessageInputRow>
<Input <Input
id="standard-adornment-name" id="standard-adornment-name"
value={threadTitle} value={threadTitle}
onChange={(e) => { onChange={e => {
setThreadTitle(e.target.value) setThreadTitle(e.target.value);
}} }}
placeholder="Thread Title" placeholder="Thread Title"
disableUnderline disableUnderline
autoComplete='off' autoComplete="off"
autoCorrect='off' autoCorrect="off"
sx={{ sx={{
width: '100%', width: "100%",
color: 'var(--new-message-text)', color: "var(--new-message-text)",
'& .MuiInput-input::placeholder': { "& .MuiInput-input::placeholder": {
color: 'rgba(84, 84, 84, 0.70) !important', color: "rgba(84, 84, 84, 0.70) !important",
fontSize: '20px', fontSize: "20px",
fontStyle: 'normal', fontStyle: "normal",
fontWeight: 400, fontWeight: 400,
lineHeight: '120%', // 24px lineHeight: "120%", // 24px
letterSpacing: '0.15px', letterSpacing: "0.15px",
opacity: 1 opacity: 1,
}, },
'&:focus': { "&:focus": {
outline: 'none', outline: "none",
}, },
// Add any additional styles for the input here // Add any additional styles for the input here
}} }}
/> />
</NewMessageInputRow> </NewMessageInputRow>
</> </>
)} )}
<Spacer height="10px" /> <Spacer height="10px" />
<NewMessageInputRow sx={{ <NewMessageInputRow
gap: '10px' sx={{
}}> gap: "10px",
}}
>
<AttachmentContainer <AttachmentContainer
{...getRootProps()} {...getRootProps()}
sx={{ sx={{
@ -562,6 +561,7 @@ export const NewThread = ({
alignItems: "center", alignItems: "center",
gap: "15px", gap: "15px",
}} }}
key={file.name + index}
> >
<Typography <Typography
sx={{ sx={{
@ -628,55 +628,57 @@ export const NewThread = ({
{isMessage ? "Post" : "Create Thread"} {isMessage ? "Post" : "Create Thread"}
</NewMessageSendP> </NewMessageSendP>
{isMessage ? ( {isMessage ? (
<SendNewMessage <SendNewMessage
color="red" color="red"
opacity={1} opacity={1}
height="25px" height="25px"
width="25px" width="25px"
/> />
) : ( ) : (
<CreateThreadIcon color="red" <CreateThreadIcon
opacity={1} height="25px" width="25px" /> color="red"
opacity={1}
height="25px"
width="25px"
/>
)} )}
</NewMessageSendButton> </NewMessageSendButton>
</InstanceFooter> </InstanceFooter>
</ReusableModal> </ReusableModal>
{isOpenMultiplePublish && ( {isOpenMultiplePublish && (
<MultiplePublish <MultiplePublish
isOpen={isOpenMultiplePublish} isOpen={isOpenMultiplePublish}
onError={(messageNotification)=> { onError={messageNotification => {
setIsOpenMultiplePublish(false); setIsOpenMultiplePublish(false);
setPublishes(null) setPublishes(null);
setCallbackContent(null) setCallbackContent(null);
if(messageNotification){ if (messageNotification) {
dispatch( dispatch(
setNotification({ setNotification({
msg: messageNotification, msg: messageNotification,
alertType: 'error' alertType: "error",
}) })
) );
} }
}} }}
onSubmit={() => { onSubmit={() => {
dispatch( dispatch(
setNotification({ setNotification({
msg: 'Posted', msg: "Posted",
alertType: 'success' alertType: "success",
}) })
) );
if(messageCallback && callbackContent?.message){ if (messageCallback && callbackContent?.message) {
messageCallback(callbackContent.message) messageCallback(callbackContent.message);
} }
if(threadCallback && callbackContent?.thread){ if (threadCallback && callbackContent?.thread) {
threadCallback(callbackContent.thread) threadCallback(callbackContent.thread);
} }
setCallbackContent(null) setCallbackContent(null);
setIsOpenMultiplePublish(false); setIsOpenMultiplePublish(false);
setPublishes(null) setPublishes(null);
closeModal() closeModal();
}} }}
publishes={publishes} publishes={publishes}
/> />

View File

@ -4,112 +4,125 @@ import React, {
useEffect, useEffect,
useMemo, useMemo,
useRef, useRef,
useState useState,
} from 'react' } from "react";
import { useNavigate } from 'react-router-dom' import { useNavigate } from "react-router-dom";
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from "react-redux";
import { RootState } from '../../state/store' import { QMAIL_BASE } from "../../constants/Identifiers";
import EditIcon from '@mui/icons-material/Edit' import { RootState } from "../../state/store";
import { Box, Button, CircularProgress, Input, Typography, useTheme } from '@mui/material' import EditIcon from "@mui/icons-material/Edit";
import { useFetchPosts } from '../../hooks/useFetchPosts' import {
import LazyLoad from '../../components/common/LazyLoad' Box,
import { removePrefix } from '../../utils/blogIdformats' Button,
import { NewMessage } from './NewMessage' CircularProgress,
import Tabs from '@mui/material/Tabs' Input,
import Tab from '@mui/material/Tab' Typography,
import { useFetchMail } from '../../hooks/useFetchMail' useTheme,
import { ShowMessage } from './ShowMessage' } from "@mui/material";
import { addToHashMapMail } from '../../state/features/mailSlice' 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 { import {
setIsLoadingGlobal, setIsLoadingGlobal,
setUserAvatarHash setUserAvatarHash,
} from '../../state/features/globalSlice' } from "../../state/features/globalSlice";
import SimpleTable from './MailTable' import SimpleTable from "./MailTable";
import { MAIL_SERVICE_TYPE } from '../../constants/mail' import { MAIL_SERVICE_TYPE } from "../../constants/mail";
import { BlogPost } from '../../state/features/blogSlice' import { BlogPost } from "../../state/features/blogSlice";
import { setNotification } from '../../state/features/notificationsSlice' import { setNotification } from "../../state/features/notificationsSlice";
import { useModal } from '../../components/common/useModal' import { useModal } from "../../components/common/useModal";
import { OpenMail } from './OpenMail' import { OpenMail } from "./OpenMail";
import { MessagesContainer } from './Mail-styles' import { MessagesContainer } from "./Mail-styles";
import { MailMessageRow } from './MailMessageRow' import { MailMessageRow } from "./MailMessageRow";
interface SentMailProps { interface SentMailProps {
onOpen: (user: string, identifier: string, content: any, to?:string)=> Promise<void> onOpen: (
user: string,
identifier: string,
content: any,
to?: string
) => Promise<void>;
} }
export const SentMail = ({onOpen}: SentMailProps) => { export const SentMail = ({ onOpen }: SentMailProps) => {
const {isShow, onCancel, onOk, show} = useModal() const { isShow, onCancel, onOk, show } = useModal();
const theme = useTheme() const theme = useTheme();
const { user } = useSelector((state: RootState) => state.auth) const { user } = useSelector((state: RootState) => state.auth);
const [isOpen, setIsOpen] = useState<boolean>(false) const [isOpen, setIsOpen] = useState<boolean>(false);
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(false);
const [message, setMessage] = useState<any>(null) const [message, setMessage] = useState<any>(null);
const [replyTo, setReplyTo] = useState<any>(null) const [replyTo, setReplyTo] = useState<any>(null);
const [valueTab, setValueTab] = React.useState(0) const [valueTab, setValueTab] = React.useState(0);
const [aliasValue, setAliasValue] = useState('') const [aliasValue, setAliasValue] = useState("");
const [alias, setAlias] = useState<string[]>([]) const [alias, setAlias] = useState<string[]>([]);
const [mailInfo, setMailInfo] = useState<any>(null) const [mailInfo, setMailInfo] = useState<any>(null);
const hashMapPosts = useSelector( const hashMapPosts = useSelector(
(state: RootState) => state.blog.hashMapPosts (state: RootState) => state.blog.hashMapPosts
) );
const [mailMessages, setMailMessages] = useState<any[]>([]) const [mailMessages, setMailMessages] = useState<any[]>([]);
const hashMapMailMessages = useSelector( const hashMapMailMessages = useSelector(
(state: RootState) => state.mail.hashMapMailMessages (state: RootState) => state.mail.hashMapMailMessages
) );
const fullMailMessages = useMemo(() => { const fullMailMessages = useMemo(() => {
return mailMessages.map((msg) => { return mailMessages.map(msg => {
let message = msg let message = msg;
const existingMessage = hashMapMailMessages[msg.id] const existingMessage = hashMapMailMessages[msg.id];
if (existingMessage) { if (existingMessage) {
message = existingMessage message = existingMessage;
} }
return message return message;
}) });
}, [mailMessages, hashMapMailMessages, user]) }, [mailMessages, hashMapMailMessages, user]);
const dispatch = useDispatch() const dispatch = useDispatch();
const navigate = useNavigate() const navigate = useNavigate();
const getAvatar = async (user: string) => { const getAvatar = async (user: string) => {
try { try {
let url = await qortalRequest({ let url = await qortalRequest({
action: 'GET_QDN_RESOURCE_URL', action: "GET_QDN_RESOURCE_URL",
name: user, name: user,
service: 'THUMBNAIL', service: "THUMBNAIL",
identifier: 'qortal_avatar' identifier: "qortal_avatar",
}) });
dispatch( dispatch(
setUserAvatarHash({ setUserAvatarHash({
name: user, name: user,
url url,
}) })
) );
} catch (error) {} } catch (error) {}
} };
const checkNewMessages = React.useCallback( const checkNewMessages = React.useCallback(
async (recipientName: string, recipientAddress: string) => { async (recipientName: string, recipientAddress: string) => {
try { try {
if (!user?.name) return 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()
const latestPost = mailMessages[0] 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`;
if (!latestPost) return 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( const findPost = responseData?.findIndex(
(item: any) => item?.identifier === latestPost?.id (item: any) => item?.identifier === latestPost?.id
) );
if (findPost === -1) { if (findPost === -1) {
return return;
} }
const newArray = responseData.slice(0, findPost) const newArray = responseData.slice(0, findPost);
const structureData = newArray.map((post: any): BlogPost => { const structureData = newArray.map((post: any): BlogPost => {
return { return {
title: post?.metadata?.title, title: post?.metadata?.title,
@ -120,50 +133,50 @@ export const SentMail = ({onOpen}: SentMailProps) => {
createdAt: post?.created, createdAt: post?.created,
updated: post?.updated, updated: post?.updated,
user: post.name, user: post.name,
id: post.identifier id: post.identifier,
} };
}) });
setMailMessages((prev) => { setMailMessages(prev => {
const updatedMessages = [...prev] const updatedMessages = [...prev];
structureData.forEach((newMessage: any) => { structureData.forEach((newMessage: any) => {
const existingIndex = updatedMessages.findIndex( const existingIndex = updatedMessages.findIndex(
(prevMessage) => prevMessage.id === newMessage.id prevMessage => prevMessage.id === newMessage.id
) );
if (existingIndex !== -1) { if (existingIndex !== -1) {
// Replace existing message // Replace existing message
updatedMessages[existingIndex] = newMessage updatedMessages[existingIndex] = newMessage;
} else { } else {
// Add new message // Add new message
updatedMessages.unshift(newMessage) updatedMessages.unshift(newMessage);
} }
}) });
return updatedMessages return updatedMessages;
}) });
return return;
} catch (error) {} } catch (error) {}
}, },
[mailMessages] [mailMessages]
) );
const getMailMessages = React.useCallback( const getMailMessages = React.useCallback(
async (recipientName: string, recipientAddress: string) => { async (recipientName: string, recipientAddress: string) => {
try { try {
if (!user?.name) return if (!user?.name) return;
const offset = mailMessages.length const offset = mailMessages.length;
// dispatch(setIsLoadingGlobal(true)) // 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, { const response = await fetch(url, {
method: 'GET', method: "GET",
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
} },
}) });
const responseData = await response.json() const responseData = await response.json();
const structureData = responseData.map((post: any): BlogPost => { const structureData = responseData.map((post: any): BlogPost => {
return { return {
title: post?.metadata?.title, title: post?.metadata?.title,
@ -174,32 +187,32 @@ export const SentMail = ({onOpen}: SentMailProps) => {
createdAt: post?.created, createdAt: post?.created,
updated: post?.updated, updated: post?.updated,
user: post.name, user: post.name,
id: post.identifier id: post.identifier,
} };
}) });
setMailMessages((prev) => { setMailMessages(prev => {
const updatedMessages = [...prev] const updatedMessages = [...prev];
structureData.forEach((newMessage: any) => { structureData.forEach((newMessage: any) => {
const existingIndex = updatedMessages.findIndex( const existingIndex = updatedMessages.findIndex(
(prevMessage) => prevMessage.id === newMessage.id prevMessage => prevMessage.id === newMessage.id
) );
if (existingIndex !== -1) { if (existingIndex !== -1) {
// Replace existing message // Replace existing message
updatedMessages[existingIndex] = newMessage updatedMessages[existingIndex] = newMessage;
} else { } else {
// Add new message // Add new message
updatedMessages.push(newMessage) updatedMessages.push(newMessage);
} }
}) });
return updatedMessages return updatedMessages;
}) });
for (const content of structureData) { for (const content of structureData) {
if (content.user && content.id) { if (content.user && content.id) {
getAvatar(content.user) getAvatar(content.user);
} }
} }
} catch (error) { } catch (error) {
@ -208,19 +221,22 @@ export const SentMail = ({onOpen}: SentMailProps) => {
} }
}, },
[mailMessages, hashMapMailMessages, user] [mailMessages, hashMapMailMessages, user]
) );
const getMessages = React.useCallback(async (isOnMount?: boolean) => { const getMessages = React.useCallback(
if (!user?.name || !user?.address) return; async (isOnMount?: boolean) => {
try { if (!user?.name || !user?.address) return;
if (isOnMount) { try {
setIsLoading(true); if (isOnMount) {
setIsLoading(true);
}
await getMailMessages(user.name, user.address);
} catch (error) {
} finally {
setIsLoading(false);
} }
await getMailMessages(user.name, user.address); },
} catch (error) { [getMailMessages, user]
} finally { );
setIsLoading(false);
}
}, [getMailMessages, user])
const firstMount = useRef(false); const firstMount = useRef(false);
useEffect(() => { useEffect(() => {
@ -230,30 +246,27 @@ export const SentMail = ({onOpen}: SentMailProps) => {
} }
}, [user]); }, [user]);
const interval = useRef<any>(null) const interval = useRef<any>(null);
const checkNewMessagesFunc = useCallback(() => { const checkNewMessagesFunc = useCallback(() => {
if (!user?.name || !user?.address) return if (!user?.name || !user?.address) return;
let isCalling = false let isCalling = false;
interval.current = setInterval(async () => { interval.current = setInterval(async () => {
if (isCalling || !user?.name || !user?.address) return if (isCalling || !user?.name || !user?.address) return;
isCalling = true isCalling = true;
const res = await checkNewMessages(user?.name, user.address) const res = await checkNewMessages(user?.name, user.address);
isCalling = false isCalling = false;
}, 30000) }, 30000);
}, [checkNewMessages, user]) }, [checkNewMessages, user]);
useEffect(() => { useEffect(() => {
checkNewMessagesFunc() checkNewMessagesFunc();
return () => { return () => {
if (interval?.current) { if (interval?.current) {
clearInterval(interval.current) clearInterval(interval.current);
} }
} };
}, [checkNewMessagesFunc]) }, [checkNewMessagesFunc]);
const openMessage = async ( const openMessage = async (
user: string, user: string,
@ -262,16 +275,15 @@ export const SentMail = ({onOpen}: SentMailProps) => {
to?: string to?: string
) => { ) => {
try { try {
onOpen(user, messageIdentifier, {}, to) onOpen(user, messageIdentifier, {}, to);
} finally { } finally {
} }
} };
return ( return (
<> <>
{mailInfo && isShow && ( {mailInfo && isShow && (
<OpenMail open={isShow} handleClose={onOk} fileInfo={mailInfo}/> <OpenMail open={isShow} handleClose={onOk} fileInfo={mailInfo} />
)} )}
{/* <NewMessage replyTo={replyTo} setReplyTo={setReplyTo} hideButton /> */} {/* <NewMessage replyTo={replyTo} setReplyTo={setReplyTo} hideButton /> */}
<ShowMessage <ShowMessage
@ -280,32 +292,34 @@ export const SentMail = ({onOpen}: SentMailProps) => {
message={message} message={message}
setReplyTo={setReplyTo} setReplyTo={setReplyTo}
/> />
<MessagesContainer> <MessagesContainer>
{fullMailMessages.map(item => { {fullMailMessages.map(item => {
return ( return (
<MailMessageRow <MailMessageRow
messageData={item} messageData={item}
openMessage={openMessage} openMessage={openMessage}
isFromSent isFromSent
/> />
); );
})} })}
<LazyLoad onLoadMore={getMessages}></LazyLoad> <LazyLoad onLoadMore={getMessages}></LazyLoad>
{isLoading && ( {isLoading && (
<Box sx={{ <Box
display: 'flex', sx={{
width: '100%', display: "flex",
justifyContent: 'center' width: "100%",
}}> justifyContent: "center",
<CircularProgress /> }}
</Box> >
)} <CircularProgress />
</MessagesContainer> </Box>
)}
</MessagesContainer>
{/* <SimpleTable {/* <SimpleTable
openMessage={openMessage} openMessage={openMessage}
data={fullMailMessages} data={fullMailMessages}
></SimpleTable> ></SimpleTable>
<LazyLoad onLoadMore={getMessages}></LazyLoad> */} <LazyLoad onLoadMore={getMessages}></LazyLoad> */}
</> </>
) );
} };

View File

@ -63,7 +63,7 @@ export const ShowMessage = ({ message }: any) => {
height: "auto", height: "auto",
alignItems: "flex-start", alignItems: "flex-start",
cursor: "default", cursor: "default",
borderRadius: '35px 4px 4px 4px' borderRadius: "35px 4px 4px 4px",
}} }}
> >
<Box <Box
@ -71,7 +71,7 @@ export const ShowMessage = ({ message }: any) => {
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
alignItems: "flex-start", alignItems: "flex-start",
width: '100%' width: "100%",
}} }}
> >
<Box <Box
@ -79,7 +79,6 @@ export const ShowMessage = ({ message }: any) => {
display: "flex", display: "flex",
alignItems: "flex-start", alignItems: "flex-start",
gap: "10px", gap: "10px",
}} }}
> >
<AvatarWrapper <AvatarWrapper
@ -95,96 +94,99 @@ export const ShowMessage = ({ message }: any) => {
</ThreadInfoColumnTime> </ThreadInfoColumnTime>
</ThreadInfoColumn> </ThreadInfoColumn>
<div <div
style={{ style={{
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
justifyContent: "center", justifyContent: "center",
}}
>
{message?.attachments?.length > 0 && (
<Box
sx={{
width: "100%",
marginTop: "10px",
}} }}
> >
{message?.attachments {message?.attachments?.length > 0 && (
.map((file: any, index: number) => { <Box
const isFirst = index === 0 sx={{
return ( width: "100%",
<Box marginTop: "10px",
sx={{ }}
display: expandAttachments ? "flex" : !expandAttachments && isFirst ? 'flex' : 'none', >
alignItems: "center", {message?.attachments.map((file: any, index: number) => {
justifyContent: "flex-start", const isFirst = index === 0;
width: "100%", return (
}}
>
<Box <Box
sx={{ sx={{
display: "flex", display: expandAttachments
? "flex"
: !expandAttachments && isFirst
? "flex"
: "none",
alignItems: "center", alignItems: "center",
gap: "5px", justifyContent: "flex-start",
cursor: "pointer", width: "100%",
width: "auto",
}} }}
key={file.name + index}
> >
<FileElement <Box
fileInfo={{ ...file, mimeTypeSaved: file?.type }} sx={{
title={file?.filename} display: "flex",
mode="mail" alignItems: "center",
otherUser={message?.user} gap: "5px",
cursor: "pointer",
width: "auto",
}}
> >
<MailAttachmentImg src={AttachmentMailSVG} /> <FileElement
fileInfo={{ ...file, mimeTypeSaved: file?.type }}
title={file?.filename}
mode="mail"
otherUser={message?.user}
>
<MailAttachmentImg src={AttachmentMailSVG} />
<Typography <Typography
sx={{
fontSize: "16px",
transition: '0.2s all',
"&:hover": {
color: 'rgba(255, 255, 255, 0.90)',
textDecoration: 'underline'
}
}}
>
{file?.originalFilename || file?.filename}
</Typography>
</FileElement>
{message?.attachments?.length > 1 && isFirst && (
<Box
sx={{
display: "flex",
alignItems: "center",
gap: "5px",
}}
onClick={() => {
setExpandAttachments(prev => !prev);
}}
>
<MoreImg
sx={{ sx={{
marginLeft: "5px", fontSize: "16px",
transform: expandAttachments transition: "0.2s all",
? "rotate(180deg)" "&:hover": {
: "unset", color: "rgba(255, 255, 255, 0.90)",
textDecoration: "underline",
},
}} }}
src={MoreSVG} >
/> {file?.originalFilename || file?.filename}
<MoreP> </Typography>
{expandAttachments ? 'hide' : `(${message?.attachments?.length - 1} more)`} </FileElement>
{message?.attachments?.length > 1 && isFirst && (
</MoreP> <Box
</Box> sx={{
)} display: "flex",
alignItems: "center",
gap: "5px",
}}
onClick={() => {
setExpandAttachments(prev => !prev);
}}
>
<MoreImg
sx={{
marginLeft: "5px",
transform: expandAttachments
? "rotate(180deg)"
: "unset",
}}
src={MoreSVG}
/>
<MoreP>
{expandAttachments
? "hide"
: `(${message?.attachments?.length - 1} more)`}
</MoreP>
</Box>
)}
</Box>
</Box> </Box>
</Box> );
); })}
}) </Box>
} )}
</Box> </div>
)}
</div>
</Box> </Box>
<Spacer height="20px" /> <Spacer height="20px" />
@ -198,9 +200,6 @@ export const ShowMessage = ({ message }: any) => {
<div dangerouslySetInnerHTML={{ __html: cleanHTML }} /> <div dangerouslySetInnerHTML={{ __html: cleanHTML }} />
)} )}
</Box> </Box>
</SingleTheadMessageParent> </SingleTheadMessageParent>
); );
}; };

View File

@ -8,6 +8,7 @@ import React, {
} from "react"; } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { THREAD_MESSAGE } from "../../constants/Identifiers";
import { RootState } from "../../state/store"; import { RootState } from "../../state/store";
import { import {
@ -98,7 +99,7 @@ export const Thread = ({
let result = parts[0]; let result = parts[0];
const threadId = result; const threadId = result;
const offset = messages.length; 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 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, { const response = await fetch(url, {
method: "GET", method: "GET",
@ -194,7 +195,7 @@ export const Thread = ({
let parts = str.split("_").reverse(); let parts = str.split("_").reverse();
let result = parts[0]; let result = parts[0];
const threadId = result; 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 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, { const response = await fetch(url, {
method: "GET", method: "GET",
@ -328,8 +329,7 @@ export const Thread = ({
} }
return ( return (
<SingleThreadParent> <SingleThreadParent key={message?.identifier}>
key={message?.identifier}
<Skeleton <Skeleton
variant="rectangular" variant="rectangular"
style={{ style={{

View File

@ -1,12 +1,10 @@
import moment from "moment"; export const delay = (time: number) =>
new Promise((_, reject) =>
export const delay = (time: number) => new Promise((_, reject) => setTimeout(() => reject(new Error("Request timed out")), time)
setTimeout(() => reject(new Error('Request timed out')), time) );
);
// const originalHtml = `<p>---------- Forwarded message ---------</p><p>From: Alex</p><p>Date: Mon, Jun 9 2014 9:32 PM</p><p>Subject: Batteries </p><p>To: Jessica</p><p><br></p><p><br></p>`; // const originalHtml = `<p>---------- Forwarded message ---------</p><p>From: Alex</p><p>Date: Mon, Jun 9 2014 9:32 PM</p><p>Subject: Batteries </p><p>To: Jessica</p><p><br></p><p><br></p>`;
// export function updateMessageDetails(newFrom: string, newDateMillis: number, newTo: string) { // export function updateMessageDetails(newFrom: string, newDateMillis: number, newTo: string) {
// let htmlString = originalHtml // let htmlString = originalHtml
// // Use Moment.js to format the date from milliseconds // // Use Moment.js to format the date from milliseconds
@ -22,13 +20,27 @@ export const delay = (time: number) => new Promise((_, reject) =>
const originalHtml = `<p>---------- Forwarded message ---------</p><p>From: Alex</p><p>Subject: Batteries </p><p>To: Jessica</p><p><br></p><p><br></p>`; const originalHtml = `<p>---------- Forwarded message ---------</p><p>From: Alex</p><p>Subject: Batteries </p><p>To: Jessica</p><p><br></p><p><br></p>`;
export function updateMessageDetails(
newFrom: string,
newSubject: string,
newTo: string
) {
let htmlString = originalHtml;
export function updateMessageDetails(newFrom: string, newSubject: string, newTo: string) { htmlString = htmlString.replace(
let htmlString = originalHtml /<p>From:.*?<\/p>/,
`<p>From: ${newFrom}</p>`
);
htmlString = htmlString.replace(
/<p>Subject:.*?<\/p>/,
`<p>Subject: ${newSubject}</p>`
);
htmlString = htmlString.replace(/<p>To:.*?<\/p>/, `<p>To: ${newTo}</p>`);
htmlString = htmlString.replace(/<p>From:.*?<\/p>/, `<p>From: ${newFrom}</p>`); return htmlString;
htmlString = htmlString.replace(/<p>Subject:.*?<\/p>/, `<p>Subject: ${newSubject}</p>`); }
htmlString = htmlString.replace(/<p>To:.*?<\/p>/, `<p>To: ${newTo}</p>`);
return htmlString; export const getFileExtension = (fileName: string) => {
} if (!fileName.includes(".")) return null;
return fileName.split(".").at(-1);
};