Removed most code from Mail.tsx.
Mail.tsx renamed to Home.tsx and moved to its own directory. It shows a "New Forum" button if the user owns the app or Test Identifiers are being used.
This commit is contained in:
parent
aaf9923103
commit
c1d812a0e6
29
src/App.tsx
29
src/App.tsx
@ -1,19 +1,19 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import { Routes, Route } from 'react-router-dom'
|
||||
import { Routes, Route } from "react-router-dom";
|
||||
|
||||
import { ThemeProvider } from '@mui/material/styles'
|
||||
import { CssBaseline } from '@mui/material'
|
||||
import { lightTheme, darkTheme } from './styles/theme'
|
||||
import { store } from './state/store'
|
||||
import { Provider } from 'react-redux'
|
||||
import GlobalWrapper from './wrappers/GlobalWrapper'
|
||||
import DownloadWrapper from './wrappers/DownloadWrapper'
|
||||
import Notification from './components/common/Notification/Notification'
|
||||
import { Mail } from './pages/Mail/Mail'
|
||||
import { ThemeProvider } from "@mui/material/styles";
|
||||
import { CssBaseline } from "@mui/material";
|
||||
import { Home } from "./pages/Home/Home";
|
||||
import { lightTheme, darkTheme } from "./styles/theme";
|
||||
import { store } from "./state/store";
|
||||
import { Provider } from "react-redux";
|
||||
import GlobalWrapper from "./wrappers/GlobalWrapper";
|
||||
import DownloadWrapper from "./wrappers/DownloadWrapper";
|
||||
import Notification from "./components/common/Notification/Notification";
|
||||
|
||||
function App() {
|
||||
const themeColor = window._qdnTheme
|
||||
const themeColor = window._qdnTheme;
|
||||
|
||||
return (
|
||||
<Provider store={store}>
|
||||
@ -24,14 +24,13 @@ function App() {
|
||||
<CssBaseline />
|
||||
|
||||
<Routes>
|
||||
<Route path="/" element={<Mail />} />
|
||||
<Route path="/to/:name" element={<Mail isFromTo />} />
|
||||
<Route path="/" element={<Home />} />
|
||||
</Routes>
|
||||
</GlobalWrapper>
|
||||
</DownloadWrapper>
|
||||
</ThemeProvider>
|
||||
</Provider>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default App
|
||||
export default App;
|
||||
|
@ -1,8 +1,11 @@
|
||||
const useTestIdentifiers = false;
|
||||
export const useTestIdentifiers = true;
|
||||
|
||||
const appName = useTestIdentifiers ? "mintTEST" : "mintership";
|
||||
export const AUDIO_BASE = `${appName}_qaudio`;
|
||||
export const THREAD_BASE = `${appName}_forum_thread`;
|
||||
export const ATTATCHMENT_BASE = `${appName}_attachments`;
|
||||
|
||||
export const QMAIL_BASE = `${appName}_mail`;
|
||||
export const ATTATCHMENT_BASE = `${appName}_attachments`;
|
||||
export const AUDIO_BASE = `${appName}_qaudio`;
|
||||
|
||||
export const FORUM_BASE = `${appName}_forum`;
|
||||
export const THREAD_BASE = `${appName}_thread`;
|
||||
export const THREAD_MESSAGE = `${appName}_thread_message`;
|
||||
|
1
src/constants/Misc.ts
Normal file
1
src/constants/Misc.ts
Normal file
@ -0,0 +1 @@
|
||||
export const appOwner = "Q-Mintership";
|
47
src/pages/Forum/Forum.tsx
Normal file
47
src/pages/Forum/Forum.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
import { Box } from "@mui/material";
|
||||
import React, { useState } from "react";
|
||||
import ComposeIconSVG from "../../assets/svgs/ComposeIcon.svg";
|
||||
import { executeEvent } from "../../utils/events";
|
||||
import {
|
||||
ComposeContainer,
|
||||
ComposeIcon,
|
||||
ComposeP,
|
||||
InstanceContainer,
|
||||
} from "../Home/Home-styles";
|
||||
import { GroupMail } from "../Mail/GroupMail";
|
||||
|
||||
export type EncryptionType = "None" | "Group" | "GroupAdmin";
|
||||
interface ForumProps {
|
||||
title: string;
|
||||
description: string;
|
||||
encryption?: EncryptionType;
|
||||
}
|
||||
|
||||
export const Forum = ({ encryption = "None" }: ForumProps) => {
|
||||
const [currentThread, setCurrentThread] = useState<any>(null);
|
||||
|
||||
const openNewThread = () => {
|
||||
if (currentThread) {
|
||||
executeEvent("openNewThreadMessageModal", {});
|
||||
return;
|
||||
}
|
||||
executeEvent("openNewThreadModal", {});
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<InstanceContainer>
|
||||
<ComposeContainer onClick={openNewThread}>
|
||||
<ComposeIcon src={ComposeIconSVG} />
|
||||
<ComposeP>{currentThread ? "New Post" : "New Thread"}</ComposeP>
|
||||
</ComposeContainer>
|
||||
</InstanceContainer>
|
||||
|
||||
{/*<GroupMail*/}
|
||||
{/* groupInfo={selectedGroup}*/}
|
||||
{/* currentThread={currentThread}*/}
|
||||
{/* setCurrentThread={setCurrentThread}*/}
|
||||
{/*/>*/}
|
||||
</Box>
|
||||
);
|
||||
};
|
@ -1,7 +1,107 @@
|
||||
import React from 'react'
|
||||
import { Box } from "@mui/material";
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import ComposeIconSVG from "../../assets/svgs/ComposeIcon.svg";
|
||||
import { useTestIdentifiers } from "../../constants/Identifiers";
|
||||
import { appOwner } from "../../constants/Misc";
|
||||
import { useFetchMail } from "../../hooks/useFetchMail";
|
||||
import { RootState } from "../../state/store";
|
||||
import { executeEvent } from "../../utils/events";
|
||||
|
||||
import {
|
||||
ComposeContainer,
|
||||
ComposeIcon,
|
||||
ComposeP,
|
||||
InstanceContainer,
|
||||
MailContainer,
|
||||
} from "./Home-styles";
|
||||
|
||||
export const Home = () => {
|
||||
const { user } = useSelector((state: RootState) => state.auth);
|
||||
|
||||
const privateGroups = useSelector(
|
||||
(state: RootState) => state.global.privateGroups
|
||||
);
|
||||
|
||||
const options = useMemo(() => {
|
||||
return Object.keys(privateGroups).map(key => {
|
||||
return {
|
||||
...privateGroups[key],
|
||||
name: privateGroups[key].groupName,
|
||||
id: key,
|
||||
};
|
||||
});
|
||||
}, [privateGroups]);
|
||||
const hashMapMailMessages = useSelector(
|
||||
(state: RootState) => state.mail.hashMapMailMessages
|
||||
);
|
||||
|
||||
const userName = useMemo(() => {
|
||||
if (!user?.name) return "";
|
||||
return user.name;
|
||||
}, [user]);
|
||||
|
||||
const { getMailMessages, checkNewMessages } = useFetchMail();
|
||||
const getMessages = React.useCallback(
|
||||
async (isOnMount?: boolean) => {
|
||||
if (!user?.name || !user?.address) return;
|
||||
try {
|
||||
await getMailMessages(user.name, user.address);
|
||||
} catch (error) {}
|
||||
},
|
||||
[getMailMessages, user]
|
||||
);
|
||||
|
||||
const interval = useRef<any>(null);
|
||||
|
||||
const checkNewMessagesFunc = useCallback(() => {
|
||||
if (!user?.name || !user?.address) return;
|
||||
let isCalling = false;
|
||||
interval.current = setInterval(async () => {
|
||||
if (isCalling || !user?.name || !user?.address) return;
|
||||
isCalling = true;
|
||||
const res = await checkNewMessages(user?.name, user.address);
|
||||
isCalling = false;
|
||||
}, 30000);
|
||||
}, [checkNewMessages, user]);
|
||||
|
||||
useEffect(() => {
|
||||
checkNewMessagesFunc();
|
||||
return () => {
|
||||
if (interval?.current) {
|
||||
clearInterval(interval.current);
|
||||
}
|
||||
};
|
||||
}, [checkNewMessagesFunc]);
|
||||
|
||||
const firstMount = useRef(false);
|
||||
useEffect(() => {
|
||||
if (user?.name && !firstMount.current) {
|
||||
getMessages(true);
|
||||
firstMount.current = true;
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
const publishNewForum = () => {
|
||||
executeEvent("openNewThreadModal", {});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>Home</div>
|
||||
)
|
||||
}
|
||||
<Box>
|
||||
<InstanceContainer>
|
||||
{(user?.name === appOwner || useTestIdentifiers) && (
|
||||
<ComposeContainer onClick={publishNewForum}>
|
||||
<ComposeIcon src={ComposeIconSVG} />
|
||||
<ComposeP>{"New Forum"}</ComposeP>
|
||||
</ComposeContainer>
|
||||
)}
|
||||
</InstanceContainer>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@ -4,112 +4,116 @@ 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, 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 { RootState } from "../../state/store";
|
||||
import EditIcon from "@mui/icons-material/Edit";
|
||||
import { Box, Button, Input, Typography, useTheme } from "@mui/material";
|
||||
import { useFetchPosts } from "../../hooks/useFetchPosts";
|
||||
import LazyLoad from "../../components/common/LazyLoad";
|
||||
import { removePrefix } from "../../utils/blogIdformats";
|
||||
import { NewMessage } from "./NewMessage";
|
||||
import Tabs from "@mui/material/Tabs";
|
||||
import Tab from "@mui/material/Tab";
|
||||
import { useFetchMail } from "../../hooks/useFetchMail";
|
||||
import { ShowMessage } from "./ShowMessage";
|
||||
import { 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 { 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 { useModal } from "../../components/common/useModal";
|
||||
import { OpenMail } from "./OpenMail";
|
||||
import { MessagesContainer } from "../Home/Home-styles";
|
||||
import { MailMessageRow } from "./MailMessageRow";
|
||||
|
||||
interface AliasMailProps {
|
||||
value: string;
|
||||
onOpen: (user: string, identifier: string, content: any)=> Promise<void>
|
||||
messageOpenedId: number
|
||||
onOpen: (user: string, identifier: string, content: any) => Promise<void>;
|
||||
messageOpenedId: number;
|
||||
}
|
||||
export const AliasMail = ({ value, onOpen, messageOpenedId}: AliasMailProps) => {
|
||||
const {isShow, onCancel, onOk, show} = useModal()
|
||||
export const AliasMail = ({
|
||||
value,
|
||||
onOpen,
|
||||
messageOpenedId,
|
||||
}: AliasMailProps) => {
|
||||
const { isShow, onCancel, onOk, show } = useModal();
|
||||
|
||||
const theme = useTheme()
|
||||
const { user } = useSelector((state: RootState) => state.auth)
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false)
|
||||
const [message, setMessage] = useState<any>(null)
|
||||
const [replyTo, setReplyTo] = useState<any>(null)
|
||||
const theme = useTheme();
|
||||
const { user } = useSelector((state: RootState) => state.auth);
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
const [message, setMessage] = useState<any>(null);
|
||||
const [replyTo, setReplyTo] = useState<any>(null);
|
||||
const [forwardInfo, setForwardInfo] = useState<any>(null);
|
||||
const [valueTab, setValueTab] = React.useState(0)
|
||||
const [aliasValue, setAliasValue] = useState('')
|
||||
const [alias, setAlias] = useState<string[]>([])
|
||||
const [mailInfo, setMailInfo] = useState<any>(null)
|
||||
const [valueTab, setValueTab] = React.useState(0);
|
||||
const [aliasValue, setAliasValue] = useState("");
|
||||
const [alias, setAlias] = useState<string[]>([]);
|
||||
const [mailInfo, setMailInfo] = useState<any>(null);
|
||||
const hashMapPosts = useSelector(
|
||||
(state: RootState) => state.blog.hashMapPosts
|
||||
)
|
||||
const [mailMessages, setMailMessages] = useState<any[]>([])
|
||||
);
|
||||
const [mailMessages, setMailMessages] = useState<any[]>([]);
|
||||
const hashMapMailMessages = useSelector(
|
||||
(state: RootState) => state.mail.hashMapMailMessages
|
||||
)
|
||||
);
|
||||
|
||||
const fullMailMessages = useMemo(() => {
|
||||
return mailMessages.map((msg) => {
|
||||
let message = msg
|
||||
const existingMessage = hashMapMailMessages[msg.id]
|
||||
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 {
|
||||
const query = `qortal_qmail_${value}_mail`
|
||||
const url = `/arbitrary/resources/search?mode=ALL&service=${MAIL_SERVICE_TYPE}&query=${query}&limit=50&includemetadata=true&reverse=true&excludeblocked=true`
|
||||
const query = `qortal_qmail_${value}_mail`;
|
||||
const url = `/arbitrary/resources/search?mode=ALL&service=${MAIL_SERVICE_TYPE}&query=${query}&limit=50&includemetadata=true&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 latestPost = mailMessages[0]
|
||||
if (!latestPost) return
|
||||
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,49 +124,49 @@ export const AliasMail = ({ value, onOpen, messageOpenedId}: AliasMailProps) =>
|
||||
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, value]
|
||||
)
|
||||
);
|
||||
|
||||
const getMailMessages = React.useCallback(
|
||||
async (recipientName: string, recipientAddress: string) => {
|
||||
try {
|
||||
const offset = mailMessages.length
|
||||
const offset = mailMessages.length;
|
||||
|
||||
// dispatch(setIsLoadingGlobal(true))
|
||||
const query = `qortal_qmail_${value}_mail`
|
||||
const url = `/arbitrary/resources/search?mode=ALL&service=${MAIL_SERVICE_TYPE}&query=${query}&limit=50&includemetadata=true&offset=${offset}&reverse=true&excludeblocked=true`
|
||||
const query = `qortal_qmail_${value}_mail`;
|
||||
const url = `/arbitrary/resources/search?mode=ALL&service=${MAIL_SERVICE_TYPE}&query=${query}&limit=50&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,
|
||||
@ -173,32 +177,32 @@ export const AliasMail = ({ value, onOpen, messageOpenedId}: AliasMailProps) =>
|
||||
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) {
|
||||
@ -207,33 +211,33 @@ export const AliasMail = ({ value, onOpen, messageOpenedId}: AliasMailProps) =>
|
||||
}
|
||||
},
|
||||
[mailMessages, hashMapMailMessages, value]
|
||||
)
|
||||
);
|
||||
const getMessages = React.useCallback(async () => {
|
||||
if (!user?.name || !user?.address) return
|
||||
await getMailMessages(user.name, user.address)
|
||||
}, [getMailMessages, user])
|
||||
if (!user?.name || !user?.address) return;
|
||||
await getMailMessages(user.name, user.address);
|
||||
}, [getMailMessages, user]);
|
||||
|
||||
const interval = useRef<any>(null)
|
||||
const interval = useRef<any>(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,
|
||||
@ -241,7 +245,7 @@ export const AliasMail = ({ value, onOpen, messageOpenedId}: AliasMailProps) =>
|
||||
content: any
|
||||
) => {
|
||||
try {
|
||||
onOpen(user, messageIdentifier, {})
|
||||
onOpen(user, messageIdentifier, {});
|
||||
// const existingMessage: any = hashMapMailMessages[messageIdentifier]
|
||||
// if (existingMessage && existingMessage.isValid && !existingMessage.unableToDecrypt) {
|
||||
// setMessage(existingMessage)
|
||||
@ -264,18 +268,18 @@ export const AliasMail = ({ value, onOpen, messageOpenedId}: AliasMailProps) =>
|
||||
} catch (error) {
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const firstMount = useRef<null | string>(null)
|
||||
const firstMount = useRef<null | string>(null);
|
||||
useEffect(() => {
|
||||
if (user?.name && (!firstMount.current || firstMount.current !== value)) {
|
||||
setMailMessages([])
|
||||
setMailMessages([]);
|
||||
setTimeout(() => {
|
||||
getMessages()
|
||||
getMessages();
|
||||
}, 100);
|
||||
firstMount.current = value
|
||||
firstMount.current = value;
|
||||
}
|
||||
}, [user, value])
|
||||
}, [user, value]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -294,26 +298,26 @@ export const AliasMail = ({ value, onOpen, messageOpenedId}: AliasMailProps) =>
|
||||
setReplyTo={setReplyTo}
|
||||
alias={value}
|
||||
/>
|
||||
<MessagesContainer>
|
||||
{fullMailMessages.map(item => {
|
||||
return (
|
||||
<MailMessageRow
|
||||
messageData={item}
|
||||
openMessage={openMessage}
|
||||
isOpen={messageOpenedId === item?.id}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<LazyLoad onLoadMore={getMessages}></LazyLoad>
|
||||
</MessagesContainer>
|
||||
<MessagesContainer>
|
||||
{fullMailMessages.map(item => {
|
||||
return (
|
||||
<MailMessageRow
|
||||
messageData={item}
|
||||
openMessage={openMessage}
|
||||
isOpen={messageOpenedId === item?.id}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<LazyLoad onLoadMore={getMessages}></LazyLoad>
|
||||
</MessagesContainer>
|
||||
{/* <SimpleTable
|
||||
openMessage={openMessage}
|
||||
data={fullMailMessages}
|
||||
></SimpleTable> */}
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
justifyContent: 'center'
|
||||
width: "100%",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{mailMessages.length > 20 && (
|
||||
@ -321,9 +325,9 @@ export const AliasMail = ({ value, onOpen, messageOpenedId}: AliasMailProps) =>
|
||||
)}
|
||||
</Box>
|
||||
{mailInfo && isShow && (
|
||||
<OpenMail open={isShow} handleClose={onOk} fileInfo={mailInfo}/>
|
||||
<OpenMail open={isShow} handleClose={onOk} fileInfo={mailInfo} />
|
||||
)}
|
||||
{/* <LazyLoad onLoadMore={getMessages}></LazyLoad> */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -64,7 +64,7 @@ import {
|
||||
ThreadSingleLastMessageP,
|
||||
ThreadSingleLastMessageSpanP,
|
||||
ThreadSingleTitle,
|
||||
} from "./Mail-styles";
|
||||
} from "../Home/Home-styles";
|
||||
import { Spacer } from "../../components/common/Spacer";
|
||||
interface AliasMailProps {
|
||||
groupInfo: any;
|
||||
|
@ -1,524 +0,0 @@
|
||||
import { Box, Popover } from "@mui/material";
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { Step } from "react-joyride";
|
||||
import { useSelector } from "react-redux";
|
||||
import AddAliasSVG from "../../assets/svgs/AddAlias.svg";
|
||||
import ArrowDownSVG from "../../assets/svgs/ArrowDown.svg";
|
||||
|
||||
import CheckSVG from "../../assets/svgs/Check.svg";
|
||||
import { CloseSVG } from "../../assets/svgs/CloseSVG";
|
||||
import ComposeIconSVG from "../../assets/svgs/ComposeIcon.svg";
|
||||
import GroupSVG from "../../assets/svgs/Group.svg";
|
||||
import SortSVG from "../../assets/svgs/Sort.svg";
|
||||
import { useFetchMail } from "../../hooks/useFetchMail";
|
||||
import { RootState } from "../../state/store";
|
||||
import { executeEvent } from "../../utils/events";
|
||||
import { GroupMail } from "./GroupMail";
|
||||
import {
|
||||
ArrowDownIcon,
|
||||
CloseParent,
|
||||
ComposeContainer,
|
||||
ComposeContainerBlank,
|
||||
ComposeIcon,
|
||||
ComposeP,
|
||||
InstanceContainer,
|
||||
InstanceFooter,
|
||||
InstanceLabel,
|
||||
InstanceListContainer,
|
||||
InstanceListContainerRow,
|
||||
InstanceListContainerRowCheck,
|
||||
InstanceListContainerRowCheckIcon,
|
||||
InstanceListContainerRowGroupIcon,
|
||||
InstanceListContainerRowLabelContainer,
|
||||
InstanceListContainerRowMain,
|
||||
InstanceListContainerRowMainP,
|
||||
InstanceListHeader,
|
||||
InstanceListParent,
|
||||
InstanceP,
|
||||
MailContainer,
|
||||
SelectInstanceContainer,
|
||||
SelectInstanceContainerFilterInner,
|
||||
SelectInstanceContainerInner,
|
||||
TypeInAliasTextfield,
|
||||
} from "./Mail-styles";
|
||||
|
||||
const filterOptions = ["Recently active", "Newest", "Oldest"];
|
||||
|
||||
const steps: Step[] = [
|
||||
{
|
||||
content: (
|
||||
<div>
|
||||
<h2>Welcome To Q-Mail</h2>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "18px",
|
||||
}}
|
||||
>
|
||||
Let's take a tour
|
||||
</p>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
}}
|
||||
>
|
||||
The Qortal community, along with its development team and the creators
|
||||
of this application, cannot be held accountable for any content
|
||||
published or displayed. Furthermore, they bear no responsibility for
|
||||
any data loss that may occur as a result of using this application.
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
placement: "center",
|
||||
target: ".step-1",
|
||||
},
|
||||
{
|
||||
target: ".step-3",
|
||||
content: (
|
||||
<div>
|
||||
<h2>Changing instances</h2>
|
||||
|
||||
<p
|
||||
style={{
|
||||
fontSize: "18px",
|
||||
fontFamily: "Arial",
|
||||
}}
|
||||
>
|
||||
Toggle between your main inbox, alias' and private groups.
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
placement: "bottom",
|
||||
},
|
||||
{
|
||||
target: ".step-2",
|
||||
content: (
|
||||
<div>
|
||||
<h2>Composing a mail message</h2>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "18px",
|
||||
fontWeight: "bold",
|
||||
fontFamily: "Arial",
|
||||
}}
|
||||
>
|
||||
Compose a secure message featuring encrypted attachments (up to 40MB
|
||||
per attachment).
|
||||
</p>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "18px",
|
||||
fontFamily: "Arial",
|
||||
}}
|
||||
>
|
||||
To protect the identity of the recipient, assign them an alias for
|
||||
added anonymity.
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
placement: "bottom",
|
||||
},
|
||||
|
||||
{
|
||||
target: ".step-3",
|
||||
content: (
|
||||
<div>
|
||||
<h2>What is an alias?</h2>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "18px",
|
||||
fontWeight: "bold",
|
||||
fontFamily: "Arial",
|
||||
}}
|
||||
>
|
||||
To conceal the identity of the message recipient, utilize the alias
|
||||
option when sending.
|
||||
</p>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "14px",
|
||||
fontFamily: "Arial",
|
||||
}}
|
||||
>
|
||||
For instance, instruct your friend to address the message to you using
|
||||
the alias 'FrederickGreat'.
|
||||
</p>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "14px",
|
||||
fontFamily: "Arial",
|
||||
}}
|
||||
>
|
||||
To access messages sent to that alias, simply add the alias as an
|
||||
instance.
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
placement: "bottom",
|
||||
},
|
||||
];
|
||||
|
||||
interface MailProps {
|
||||
isFromTo: boolean;
|
||||
}
|
||||
|
||||
export const Mail = ({ isFromTo }: MailProps) => {
|
||||
const { user } = useSelector((state: RootState) => state.auth);
|
||||
const [isOpenInstanceList, setIsOpenInstanceList] = useState<boolean>(false);
|
||||
const [isOpenFilterList, setIsOpenFilterList] = useState<boolean>(false);
|
||||
|
||||
const anchorElInstance = useRef<any>(null);
|
||||
const anchorElInstanceFilter = useRef<any>(null);
|
||||
|
||||
const [currentThread, setCurrentThread] = useState<any>(null);
|
||||
|
||||
const [aliasValue, setAliasValue] = useState("");
|
||||
const [alias, setAlias] = useState<string[]>([]);
|
||||
|
||||
const [filterMode, setFilterMode] = useState<string>("Recently active");
|
||||
const [selectedAlias, setSelectedAlias] = useState<string | null>(null);
|
||||
const [selectedGroup, setSelectedGroup] = useState<any>(null);
|
||||
const privateGroups = useSelector(
|
||||
(state: RootState) => state.global.privateGroups
|
||||
);
|
||||
|
||||
const options = useMemo(() => {
|
||||
return Object.keys(privateGroups).map(key => {
|
||||
return {
|
||||
...privateGroups[key],
|
||||
name: privateGroups[key].groupName,
|
||||
id: key,
|
||||
};
|
||||
});
|
||||
}, [privateGroups]);
|
||||
const hashMapMailMessages = useSelector(
|
||||
(state: RootState) => state.mail.hashMapMailMessages
|
||||
);
|
||||
|
||||
const userName = useMemo(() => {
|
||||
if (!user?.name) return "";
|
||||
return user.name;
|
||||
}, [user]);
|
||||
|
||||
const { getMailMessages, checkNewMessages } = useFetchMail();
|
||||
const getMessages = React.useCallback(
|
||||
async (isOnMount?: boolean) => {
|
||||
if (!user?.name || !user?.address) return;
|
||||
try {
|
||||
await getMailMessages(user.name, user.address);
|
||||
} catch (error) {}
|
||||
},
|
||||
[getMailMessages, user]
|
||||
);
|
||||
|
||||
const interval = useRef<any>(null);
|
||||
|
||||
const checkNewMessagesFunc = useCallback(() => {
|
||||
if (!user?.name || !user?.address) return;
|
||||
let isCalling = false;
|
||||
interval.current = setInterval(async () => {
|
||||
if (isCalling || !user?.name || !user?.address) return;
|
||||
isCalling = true;
|
||||
const res = await checkNewMessages(user?.name, user.address);
|
||||
isCalling = false;
|
||||
}, 30000);
|
||||
}, [checkNewMessages, user]);
|
||||
|
||||
useEffect(() => {
|
||||
checkNewMessagesFunc();
|
||||
return () => {
|
||||
if (interval?.current) {
|
||||
clearInterval(interval.current);
|
||||
}
|
||||
};
|
||||
}, [checkNewMessagesFunc]);
|
||||
|
||||
const firstMount = useRef(false);
|
||||
useEffect(() => {
|
||||
if (user?.name && !firstMount.current) {
|
||||
getMessages(true);
|
||||
firstMount.current = true;
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!userName) return;
|
||||
const savedAlias = localStorage.getItem(`alias-qmail-${userName}`);
|
||||
if (savedAlias) {
|
||||
try {
|
||||
setAlias(JSON.parse(savedAlias));
|
||||
} catch (error) {
|
||||
console.error("Error parsing JSON from localStorage:", error);
|
||||
}
|
||||
}
|
||||
}, [userName]);
|
||||
|
||||
const handleCloseInstanceList = () => {
|
||||
setIsOpenInstanceList(false);
|
||||
};
|
||||
const handleCloseThreadFilterList = () => {
|
||||
setIsOpenFilterList(false);
|
||||
};
|
||||
|
||||
const addAlias = () => {
|
||||
if (!aliasValue) return;
|
||||
const newList = [...alias, aliasValue];
|
||||
if (userName) {
|
||||
try {
|
||||
localStorage.setItem(
|
||||
`alias-qmail-${userName}`,
|
||||
JSON.stringify(newList)
|
||||
);
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
setAlias(prev => [...prev, aliasValue]);
|
||||
setAliasValue("");
|
||||
};
|
||||
|
||||
const handleInputKeyDown = (event: any) => {
|
||||
if (event.key === "Enter") {
|
||||
addAlias();
|
||||
}
|
||||
};
|
||||
|
||||
const openNewThread = () => {
|
||||
if (currentThread) {
|
||||
executeEvent("openNewThreadMessageModal", {});
|
||||
return;
|
||||
}
|
||||
executeEvent("openNewThreadModal", {});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (options[0] && !selectedGroup) setSelectedGroup(options[0]);
|
||||
}, [options]);
|
||||
return (
|
||||
<MailContainer>
|
||||
<InstanceContainer>
|
||||
<ComposeContainer onClick={openNewThread}>
|
||||
<ComposeIcon src={ComposeIconSVG} />
|
||||
<ComposeP>{currentThread ? "New Post" : "New Thread"}</ComposeP>
|
||||
</ComposeContainer>
|
||||
<SelectInstanceContainer className="step-3">
|
||||
<InstanceLabel>Current Instance:</InstanceLabel>
|
||||
<SelectInstanceContainerInner
|
||||
onClick={() => {
|
||||
setIsOpenInstanceList(true);
|
||||
}}
|
||||
ref={anchorElInstance}
|
||||
>
|
||||
<InstanceP>
|
||||
{selectedGroup ? selectedGroup?.name : user?.name}
|
||||
</InstanceP>
|
||||
<ArrowDownIcon src={ArrowDownSVG} />
|
||||
</SelectInstanceContainerInner>
|
||||
</SelectInstanceContainer>
|
||||
<Popover
|
||||
open={isOpenInstanceList}
|
||||
anchorEl={anchorElInstance.current}
|
||||
onClose={handleCloseInstanceList}
|
||||
anchorOrigin={{
|
||||
vertical: "bottom",
|
||||
horizontal: "center",
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "center",
|
||||
}}
|
||||
style={{ marginTop: "8px" }} // Adjust this value as needed
|
||||
>
|
||||
<InstanceListParent>
|
||||
<InstanceListHeader>
|
||||
<InstanceListContainerRowLabelContainer>
|
||||
<InstanceListContainerRowCheck></InstanceListContainerRowCheck>
|
||||
<InstanceListContainerRowMain>
|
||||
<InstanceListContainerRowMainP>
|
||||
Instances:
|
||||
</InstanceListContainerRowMainP>
|
||||
</InstanceListContainerRowMain>
|
||||
</InstanceListContainerRowLabelContainer>
|
||||
</InstanceListHeader>
|
||||
<InstanceListContainer>
|
||||
{options?.map(group => {
|
||||
return (
|
||||
<InstanceListContainerRow
|
||||
onClick={() => {
|
||||
setSelectedAlias(null);
|
||||
setSelectedGroup(group);
|
||||
handleCloseInstanceList();
|
||||
}}
|
||||
sx={{
|
||||
backgroundColor:
|
||||
selectedGroup?.id === group?.id
|
||||
? "rgba(74, 158, 244, 1)"
|
||||
: "unset",
|
||||
}}
|
||||
key={group?.id}
|
||||
>
|
||||
<InstanceListContainerRowCheck>
|
||||
{group?.id === selectedGroup?.id && (
|
||||
<InstanceListContainerRowCheckIcon src={CheckSVG} />
|
||||
)}
|
||||
</InstanceListContainerRowCheck>
|
||||
<InstanceListContainerRowMain>
|
||||
<InstanceListContainerRowMainP>
|
||||
{group?.name}
|
||||
</InstanceListContainerRowMainP>
|
||||
<CloseParent>
|
||||
<InstanceListContainerRowGroupIcon src={GroupSVG} />
|
||||
<Box
|
||||
sx={{
|
||||
visibility: "hidden",
|
||||
}}
|
||||
>
|
||||
<CloseSVG
|
||||
height=""
|
||||
width=""
|
||||
color="white"
|
||||
opacity={0.2}
|
||||
/>
|
||||
</Box>
|
||||
</CloseParent>
|
||||
</InstanceListContainerRowMain>
|
||||
</InstanceListContainerRow>
|
||||
);
|
||||
})}
|
||||
</InstanceListContainer>
|
||||
<InstanceFooter>
|
||||
<InstanceListContainerRowLabelContainer>
|
||||
<InstanceListContainerRowCheck>
|
||||
<InstanceListContainerRowCheckIcon
|
||||
onClick={addAlias}
|
||||
src={AddAliasSVG}
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
}}
|
||||
/>
|
||||
</InstanceListContainerRowCheck>
|
||||
<TypeInAliasTextfield
|
||||
onKeyDown={handleInputKeyDown}
|
||||
value={aliasValue}
|
||||
placeholder="Type in Alias"
|
||||
onChange={e => {
|
||||
setAliasValue(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</InstanceListContainerRowLabelContainer>
|
||||
</InstanceFooter>
|
||||
</InstanceListParent>
|
||||
{/* <InstanceList /> */}
|
||||
</Popover>
|
||||
<Popover
|
||||
open={isOpenFilterList}
|
||||
anchorEl={anchorElInstanceFilter.current}
|
||||
onClose={handleCloseThreadFilterList}
|
||||
anchorOrigin={{
|
||||
vertical: "bottom",
|
||||
horizontal: "right",
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "right",
|
||||
}}
|
||||
>
|
||||
<InstanceListParent
|
||||
sx={{
|
||||
minHeight: "unset",
|
||||
width: "auto",
|
||||
padding: "0px",
|
||||
}}
|
||||
>
|
||||
<InstanceListHeader></InstanceListHeader>
|
||||
<InstanceListContainer>
|
||||
{filterOptions?.map(filter => {
|
||||
return (
|
||||
<InstanceListContainerRow
|
||||
onClick={() => {
|
||||
setFilterMode(filter);
|
||||
handleCloseThreadFilterList();
|
||||
}}
|
||||
sx={{
|
||||
backgroundColor:
|
||||
filterMode === filter
|
||||
? "rgba(74, 158, 244, 1)"
|
||||
: "unset",
|
||||
}}
|
||||
key={filter}
|
||||
>
|
||||
<InstanceListContainerRowCheck>
|
||||
{filter === filterMode && (
|
||||
<InstanceListContainerRowCheckIcon src={CheckSVG} />
|
||||
)}
|
||||
</InstanceListContainerRowCheck>
|
||||
<InstanceListContainerRowMain>
|
||||
<InstanceListContainerRowMainP>
|
||||
{filter}
|
||||
</InstanceListContainerRowMainP>
|
||||
</InstanceListContainerRowMain>
|
||||
</InstanceListContainerRow>
|
||||
);
|
||||
})}
|
||||
</InstanceListContainer>
|
||||
<InstanceFooter></InstanceFooter>
|
||||
</InstanceListParent>
|
||||
</Popover>
|
||||
<ComposeContainerBlank>
|
||||
{selectedGroup && !currentThread && (
|
||||
<ComposeContainer
|
||||
onClick={() => {
|
||||
setIsOpenFilterList(true);
|
||||
}}
|
||||
ref={anchorElInstanceFilter}
|
||||
>
|
||||
<ComposeIcon src={SortSVG} />
|
||||
|
||||
<SelectInstanceContainerFilterInner>
|
||||
<ComposeP>Sort by</ComposeP>
|
||||
<ArrowDownIcon src={ArrowDownSVG} />
|
||||
</SelectInstanceContainerFilterInner>
|
||||
</ComposeContainer>
|
||||
)}
|
||||
</ComposeContainerBlank>
|
||||
</InstanceContainer>
|
||||
|
||||
<GroupMail
|
||||
groupInfo={selectedGroup}
|
||||
currentThread={currentThread}
|
||||
setCurrentThread={setCurrentThread}
|
||||
filterMode={filterMode}
|
||||
setFilterMode={setFilterMode}
|
||||
/>
|
||||
</MailContainer>
|
||||
);
|
||||
};
|
||||
|
||||
interface TabPanelProps {
|
||||
children?: React.ReactNode;
|
||||
index: number;
|
||||
value: number | null;
|
||||
}
|
||||
|
||||
export function TabPanel(props: TabPanelProps) {
|
||||
const { children, value, index, ...other } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
role="tabpanel"
|
||||
hidden={value !== index}
|
||||
id={`mail-tabs-${index}`}
|
||||
aria-labelledby={`mail-tabs-${index}`}
|
||||
{...other}
|
||||
style={{
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
{value === index && children}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -9,11 +9,11 @@ import {
|
||||
MessageExtraDate,
|
||||
MessageExtraInfo,
|
||||
MessageExtraName,
|
||||
} from "./Mail-styles";
|
||||
} from "../Home/Home-styles";
|
||||
import { AvatarWrapper } from "./MailTable";
|
||||
import { formatTimestamp } from "../../utils/time";
|
||||
import LockSVG from '../../assets/svgs/Lock.svg'
|
||||
import AttachmentSVG from '../../assets/svgs/Attachment.svg'
|
||||
import LockSVG from "../../assets/svgs/Lock.svg";
|
||||
import AttachmentSVG from "../../assets/svgs/Attachment.svg";
|
||||
import { useSelector } from "react-redux";
|
||||
import { RootState } from "../../state/store";
|
||||
import { base64ToUint8Array, uint8ArrayToObject } from "../../utils/toBase64";
|
||||
@ -24,9 +24,9 @@ function parseQuery(query: string) {
|
||||
const match = query.match(regex);
|
||||
|
||||
if (match) {
|
||||
const recipientName = match[1];
|
||||
const recipientAddress = match[2] || null; // Will be null if the address part is not present
|
||||
return { recipientName, recipientAddress };
|
||||
const recipientName = match[1];
|
||||
const recipientAddress = match[2] || null; // Will be null if the address part is not present
|
||||
return { recipientName, recipientAddress };
|
||||
}
|
||||
|
||||
return { recipientName: null, recipientAddress: null };
|
||||
@ -35,141 +35,171 @@ interface MailMessageRowProp {
|
||||
messageData: any;
|
||||
openMessage: (user: string, id: string, content: any, alias?: string) => void;
|
||||
isOpen?: boolean;
|
||||
isFromSent?: boolean
|
||||
isFromSent?: boolean;
|
||||
}
|
||||
export const MailMessageRow = ({
|
||||
messageData,
|
||||
openMessage,
|
||||
isOpen,
|
||||
isFromSent
|
||||
isFromSent,
|
||||
}: MailMessageRowProp) => {
|
||||
const username = useSelector((state: RootState) => state.auth?.user?.name)
|
||||
const [subjectInHashDecrypted, setSubjectInHashDecrypted] = useState<null | string>(null)
|
||||
const [hasAttachment, setHasAttachment] = useState<null | boolean>(null)
|
||||
const username = useSelector((state: RootState) => state.auth?.user?.name);
|
||||
const [subjectInHashDecrypted, setSubjectInHashDecrypted] = useState<
|
||||
null | string
|
||||
>(null);
|
||||
const [hasAttachment, setHasAttachment] = useState<null | boolean>(null);
|
||||
const [sentToNameInfo, setSentToNameInfo] = useState({
|
||||
name: ""
|
||||
})
|
||||
const [alias, setAlias] = useState<null | string>(null)
|
||||
name: "",
|
||||
});
|
||||
const [alias, setAlias] = useState<null | string>(null);
|
||||
|
||||
const identifier = useMemo(()=> {
|
||||
return messageData?.id
|
||||
}, [messageData])
|
||||
const identifier = useMemo(() => {
|
||||
return messageData?.id;
|
||||
}, [messageData]);
|
||||
|
||||
const getSentToName = useCallback(async (id: string)=> {
|
||||
const getSentToName = useCallback(async (id: string) => {
|
||||
try {
|
||||
const { recipientName, recipientAddress } = parseQuery(id);
|
||||
if(!recipientAddress && recipientName){
|
||||
setAlias(recipientName)
|
||||
return
|
||||
if (!recipientAddress && recipientName) {
|
||||
setAlias(recipientName);
|
||||
return;
|
||||
}
|
||||
if(!recipientName || !recipientAddress) return
|
||||
if (!recipientName || !recipientAddress) return;
|
||||
const response = await qortalRequest({
|
||||
action: "SEARCH_NAMES",
|
||||
query: recipientName,
|
||||
prefix: true,
|
||||
prefix: true,
|
||||
limit: 10,
|
||||
reverse: false
|
||||
})
|
||||
const findName = response?.find((item: any)=> item?.owner?.includes(recipientAddress))
|
||||
if(findName){
|
||||
reverse: false,
|
||||
});
|
||||
const findName = response?.find((item: any) =>
|
||||
item?.owner?.includes(recipientAddress)
|
||||
);
|
||||
if (findName) {
|
||||
setSentToNameInfo({
|
||||
name: findName.name
|
||||
})
|
||||
name: findName.name,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
} catch (error) {}
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (isFromSent && identifier) {
|
||||
getSentToName(identifier);
|
||||
}
|
||||
}, [])
|
||||
useEffect(()=> {
|
||||
if(isFromSent && identifier){
|
||||
getSentToName(identifier)
|
||||
}, [isFromSent, identifier]);
|
||||
let isEncrypted = true;
|
||||
let hasAttachments = null;
|
||||
let subject = "";
|
||||
const hashMapMailMessages = useSelector(
|
||||
(state: RootState) => state.mail.hashMapMailMessages
|
||||
);
|
||||
const hashMapSavedSubjects = useSelector(
|
||||
(state: RootState) => state.mail.hashMapSavedSubjects
|
||||
);
|
||||
const subjectInHash = hashMapSavedSubjects[messageData?.id];
|
||||
const data = hashMapMailMessages[messageData?.id];
|
||||
|
||||
if (subjectInHashDecrypted !== null) {
|
||||
subject = subjectInHashDecrypted || "- no subject";
|
||||
hasAttachments = hasAttachment || false;
|
||||
}
|
||||
if (data && data?.isValid && !data?.unableToDecrypt) {
|
||||
isEncrypted = false;
|
||||
subject = data?.subject || "- no subject";
|
||||
hasAttachments = (data?.attachments || [])?.length > 0;
|
||||
}
|
||||
|
||||
const getSubjectFromHash = async (
|
||||
subject: string,
|
||||
hasAttachmentParam: boolean
|
||||
) => {
|
||||
if (subject === undefined || subject === null)
|
||||
throw new Error("no subject");
|
||||
if (subject === "") {
|
||||
setSubjectInHashDecrypted("");
|
||||
setHasAttachment(hasAttachmentParam);
|
||||
return;
|
||||
}
|
||||
}, [isFromSent, identifier])
|
||||
let isEncrypted = true;
|
||||
let hasAttachments = null
|
||||
let subject = ""
|
||||
const hashMapMailMessages = useSelector(
|
||||
(state: RootState) => state.mail.hashMapMailMessages
|
||||
);
|
||||
const hashMapSavedSubjects = useSelector(
|
||||
(state: RootState) => state.mail.hashMapSavedSubjects
|
||||
);
|
||||
const subjectInHash = hashMapSavedSubjects[messageData?.id]
|
||||
const data = hashMapMailMessages[messageData?.id]
|
||||
|
||||
if(subjectInHashDecrypted !== null){
|
||||
subject = subjectInHashDecrypted || "- no subject"
|
||||
hasAttachments = hasAttachment || false
|
||||
}
|
||||
if(data && data?.isValid && !data?.unableToDecrypt){
|
||||
isEncrypted = false
|
||||
subject = data?.subject || "- no subject"
|
||||
hasAttachments = (data?.attachments || [])?.length > 0
|
||||
}
|
||||
|
||||
|
||||
const getSubjectFromHash = async (subject: string, hasAttachmentParam: boolean) => {
|
||||
if(subject === undefined || subject === null) throw new Error('no subject')
|
||||
if(subject === ""){
|
||||
setSubjectInHashDecrypted("")
|
||||
setHasAttachment(hasAttachmentParam)
|
||||
return
|
||||
}
|
||||
let requestEncryptSubject: any = {
|
||||
action: 'DECRYPT_DATA',
|
||||
encryptedData: subject
|
||||
}
|
||||
const resDecryptSubject = await qortalRequest(requestEncryptSubject)
|
||||
const decryptToUnit8ArraySubject =
|
||||
base64ToUint8Array(resDecryptSubject)
|
||||
const responseDataSubject = uint8ArrayToObject(
|
||||
decryptToUnit8ArraySubject
|
||||
)
|
||||
if(responseDataSubject !== null || responseDataSubject !== undefined){
|
||||
setSubjectInHashDecrypted(responseDataSubject)
|
||||
setHasAttachment(hasAttachmentParam)
|
||||
let requestEncryptSubject: any = {
|
||||
action: "DECRYPT_DATA",
|
||||
encryptedData: subject,
|
||||
};
|
||||
const resDecryptSubject = await qortalRequest(requestEncryptSubject);
|
||||
const decryptToUnit8ArraySubject = base64ToUint8Array(resDecryptSubject);
|
||||
const responseDataSubject = uint8ArrayToObject(decryptToUnit8ArraySubject);
|
||||
if (responseDataSubject !== null || responseDataSubject !== undefined) {
|
||||
setSubjectInHashDecrypted(responseDataSubject);
|
||||
setHasAttachment(hasAttachmentParam);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEncrypted) return;
|
||||
|
||||
useEffect(()=> {
|
||||
if(!isEncrypted) return
|
||||
if (subjectInHashDecrypted !== null) return;
|
||||
if (
|
||||
!subjectInHash ||
|
||||
subjectInHash?.subject === undefined ||
|
||||
subjectInHash?.subject === null
|
||||
)
|
||||
return;
|
||||
getSubjectFromHash(subjectInHash?.subject, subjectInHash?.attachments);
|
||||
}, [isEncrypted, subjectInHashDecrypted]);
|
||||
|
||||
if(subjectInHashDecrypted !== null) return
|
||||
if(!subjectInHash || subjectInHash?.subject === undefined || subjectInHash?.subject === null ) return
|
||||
getSubjectFromHash(subjectInHash?.subject, subjectInHash?.attachments)
|
||||
}, [isEncrypted, subjectInHashDecrypted])
|
||||
const name = useMemo(() => {
|
||||
if (isFromSent) {
|
||||
return sentToNameInfo?.name || "";
|
||||
}
|
||||
return messageData?.user;
|
||||
}, [sentToNameInfo, isFromSent, messageData]);
|
||||
|
||||
const name = useMemo(()=> {
|
||||
if(isFromSent){
|
||||
return sentToNameInfo?.name || ""
|
||||
}
|
||||
return messageData?.user
|
||||
}, [sentToNameInfo, isFromSent, messageData])
|
||||
|
||||
return (
|
||||
<MailMessageRowContainer sx={{
|
||||
background: isOpen ? '#434448' : 'unset'
|
||||
}} onClick={()=> {
|
||||
openMessage(messageData?.user, messageData?.id, messageData, isFromSent ? (alias || name) : username)
|
||||
}}>
|
||||
<MailMessageRowContainer
|
||||
sx={{
|
||||
background: isOpen ? "#434448" : "unset",
|
||||
}}
|
||||
onClick={() => {
|
||||
openMessage(
|
||||
messageData?.user,
|
||||
messageData?.id,
|
||||
messageData,
|
||||
isFromSent ? alias || name : username
|
||||
);
|
||||
}}
|
||||
>
|
||||
<MailMessageRowProfile>
|
||||
<AvatarWrapper isAlias={!!alias} height="50px" user={name} fallback={alias || name}></AvatarWrapper>
|
||||
<AvatarWrapper
|
||||
isAlias={!!alias}
|
||||
height="50px"
|
||||
user={name}
|
||||
fallback={alias || name}
|
||||
></AvatarWrapper>
|
||||
<MessageExtraInfo>
|
||||
<MessageExtraName sx={{
|
||||
fontWeight: isFromSent ? '300' : isEncrypted ? '900' : '300'
|
||||
}}>{isFromSent ? "To: " : ""} {alias || name}</MessageExtraName>
|
||||
<MessageExtraDate>{formatTimestamp(messageData?.createdAt)}</MessageExtraDate>
|
||||
<MessageExtraName
|
||||
sx={{
|
||||
fontWeight: isFromSent ? "300" : isEncrypted ? "900" : "300",
|
||||
}}
|
||||
>
|
||||
{isFromSent ? "To: " : ""} {alias || name}
|
||||
</MessageExtraName>
|
||||
<MessageExtraDate>
|
||||
{formatTimestamp(messageData?.createdAt)}
|
||||
</MessageExtraDate>
|
||||
</MessageExtraInfo>
|
||||
</MailMessageRowProfile>
|
||||
<MailMessageRowInfo>
|
||||
{hasAttachments ? <MailMessageRowInfoImg src={AttachmentSVG} /> : hasAttachments === false ? null : isEncrypted ? <MailMessageRowInfoImg src={LockSVG} /> : null}
|
||||
|
||||
{hasAttachments ? (
|
||||
<MailMessageRowInfoImg src={AttachmentSVG} />
|
||||
) : hasAttachments === false ? null : isEncrypted ? (
|
||||
<MailMessageRowInfoImg src={LockSVG} />
|
||||
) : null}
|
||||
|
||||
{subject ? (
|
||||
<MailMessageRowInfoStatusRead>{subject}</MailMessageRowInfoStatusRead>
|
||||
) : isEncrypted ? (
|
||||
<MailMessageRowInfoStatusNotDecrypted>ACCESS TO DECRYPT</MailMessageRowInfoStatusNotDecrypted>
|
||||
) : isEncrypted ? (
|
||||
<MailMessageRowInfoStatusNotDecrypted>
|
||||
ACCESS TO DECRYPT
|
||||
</MailMessageRowInfoStatusNotDecrypted>
|
||||
) : null}
|
||||
</MailMessageRowInfo>
|
||||
</MailMessageRowContainer>
|
||||
|
@ -1,145 +1,143 @@
|
||||
import * as React from 'react'
|
||||
import Table from '@mui/material/Table'
|
||||
import TableBody from '@mui/material/TableBody'
|
||||
import TableCell from '@mui/material/TableCell'
|
||||
import TableContainer from '@mui/material/TableContainer'
|
||||
import TableHead from '@mui/material/TableHead'
|
||||
import TableRow from '@mui/material/TableRow'
|
||||
import Paper from '@mui/material/Paper'
|
||||
import { Avatar, Box } from '@mui/material'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { RootState } from '../../state/store'
|
||||
import { formatTimestamp } from '../../utils/time'
|
||||
import AliasAvatar from '../../assets/svgs/AliasAvatar.svg'
|
||||
import { AliasAvatarImg } from './Mail-styles'
|
||||
const tableCellFontSize = '16px'
|
||||
import * as React from "react";
|
||||
import Table from "@mui/material/Table";
|
||||
import TableBody from "@mui/material/TableBody";
|
||||
import TableCell from "@mui/material/TableCell";
|
||||
import TableContainer from "@mui/material/TableContainer";
|
||||
import TableHead from "@mui/material/TableHead";
|
||||
import TableRow from "@mui/material/TableRow";
|
||||
import Paper from "@mui/material/Paper";
|
||||
import { Avatar, Box } from "@mui/material";
|
||||
import { useSelector } from "react-redux";
|
||||
import { RootState } from "../../state/store";
|
||||
import { formatTimestamp } from "../../utils/time";
|
||||
import AliasAvatar from "../../assets/svgs/AliasAvatar.svg";
|
||||
import { AliasAvatarImg } from "../Home/Home-styles";
|
||||
const tableCellFontSize = "16px";
|
||||
|
||||
interface Data {
|
||||
name: string
|
||||
description: string
|
||||
createdAt: number
|
||||
user: string
|
||||
id: string
|
||||
tags: string[]
|
||||
subject?: string
|
||||
name: string;
|
||||
description: string;
|
||||
createdAt: number;
|
||||
user: string;
|
||||
id: string;
|
||||
tags: string[];
|
||||
subject?: string;
|
||||
}
|
||||
|
||||
interface ColumnData {
|
||||
dataKey: keyof Data
|
||||
label: string
|
||||
numeric?: boolean
|
||||
width?: number
|
||||
dataKey: keyof Data;
|
||||
label: string;
|
||||
numeric?: boolean;
|
||||
width?: number;
|
||||
}
|
||||
|
||||
const columns: ColumnData[] = [
|
||||
{
|
||||
label: 'Sender',
|
||||
dataKey: 'user',
|
||||
width: 200
|
||||
label: "Sender",
|
||||
dataKey: "user",
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
label: 'Subject',
|
||||
dataKey: 'description'
|
||||
label: "Subject",
|
||||
dataKey: "description",
|
||||
},
|
||||
{
|
||||
label: 'Date',
|
||||
dataKey: 'createdAt',
|
||||
label: "Date",
|
||||
dataKey: "createdAt",
|
||||
numeric: true,
|
||||
width: 200
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
width: 200,
|
||||
},
|
||||
];
|
||||
|
||||
function fixedHeaderContent() {
|
||||
return (
|
||||
<TableRow>
|
||||
{columns.map((column) => {
|
||||
{columns.map(column => {
|
||||
return (
|
||||
<TableCell
|
||||
key={column.dataKey}
|
||||
variant="head"
|
||||
align={column.numeric || false ? 'right' : 'left'}
|
||||
align={column.numeric || false ? "right" : "left"}
|
||||
style={{ width: column.width }}
|
||||
sx={{
|
||||
backgroundColor: 'background.paper',
|
||||
backgroundColor: "background.paper",
|
||||
fontSize: tableCellFontSize,
|
||||
padding: '7px'
|
||||
padding: "7px",
|
||||
}}
|
||||
>
|
||||
{column.label}
|
||||
</TableCell>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</TableRow>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function rowContent(_index: number, row: Data, openMessage: any) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{columns.map((column) => {
|
||||
let subject = '-'
|
||||
if (column.dataKey === 'description' && row['subject']) {
|
||||
subject = row['subject']
|
||||
{columns.map(column => {
|
||||
let subject = "-";
|
||||
if (column.dataKey === "description" && row["subject"]) {
|
||||
subject = row["subject"];
|
||||
}
|
||||
return (
|
||||
<TableCell
|
||||
onClick={() => openMessage(row?.user, row?.id, row)}
|
||||
key={column.dataKey}
|
||||
align={column.numeric || false ? 'right' : 'left'}
|
||||
style={{ width: column.width, cursor: 'pointer' }}
|
||||
align={column.numeric || false ? "right" : "left"}
|
||||
style={{ width: column.width, cursor: "pointer" }}
|
||||
sx={{
|
||||
fontSize: tableCellFontSize,
|
||||
padding: '7px'
|
||||
padding: "7px",
|
||||
}}
|
||||
>
|
||||
{column.dataKey === 'user' && (
|
||||
{column.dataKey === "user" && (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
gap: '5px',
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
flexWrap: 'wrap',
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap'
|
||||
display: "flex",
|
||||
gap: "5px",
|
||||
width: "100%",
|
||||
alignItems: "center",
|
||||
flexWrap: "wrap",
|
||||
textOverflow: "ellipsis",
|
||||
overflow: "hidden",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
<AvatarWrapper user={row?.user}></AvatarWrapper>
|
||||
{row[column.dataKey]}
|
||||
</Box>
|
||||
)}
|
||||
{column.dataKey !== 'user' && (
|
||||
{column.dataKey !== "user" && (
|
||||
<>
|
||||
{column.dataKey === 'createdAt'
|
||||
{column.dataKey === "createdAt"
|
||||
? formatTimestamp(row[column.dataKey])
|
||||
: column.dataKey === 'description'
|
||||
: column.dataKey === "description"
|
||||
? subject
|
||||
: row[column.dataKey]}
|
||||
</>
|
||||
)}
|
||||
</TableCell>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
interface SimpleTableProps {
|
||||
openMessage: (user: string, messageIdentifier: string, content: any) => void
|
||||
data: Data[]
|
||||
children?: React.ReactNode
|
||||
openMessage: (user: string, messageIdentifier: string, content: any) => void;
|
||||
data: Data[];
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function SimpleTable({
|
||||
openMessage,
|
||||
data,
|
||||
children
|
||||
children,
|
||||
}: SimpleTableProps) {
|
||||
return (
|
||||
<Paper style={{ width: '100%' }}>
|
||||
<Paper style={{ width: "100%" }}>
|
||||
<TableContainer component={Paper}>
|
||||
<Table>
|
||||
<TableHead>{fixedHeaderContent()}</TableHead>
|
||||
@ -154,24 +152,45 @@ export default function SimpleTable({
|
||||
</TableContainer>
|
||||
{children}
|
||||
</Paper>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export const AvatarWrapper = ({ user, height , fallback, isAlias}: any) => {
|
||||
export const AvatarWrapper = ({ user, height, fallback, isAlias }: any) => {
|
||||
const userAvatarHash = useSelector(
|
||||
(state: RootState) => state.global.userAvatarHash
|
||||
)
|
||||
);
|
||||
const avatarLink = React.useMemo(() => {
|
||||
if (!user || !userAvatarHash) return ''
|
||||
const findUserAvatar = userAvatarHash[user]
|
||||
if (!findUserAvatar) return ''
|
||||
return findUserAvatar
|
||||
}, [userAvatarHash, user])
|
||||
if (!user || !userAvatarHash) return "";
|
||||
const findUserAvatar = userAvatarHash[user];
|
||||
if (!findUserAvatar) return "";
|
||||
return findUserAvatar;
|
||||
}, [userAvatarHash, user]);
|
||||
|
||||
if(isAlias) return <AliasAvatarImg sx={{
|
||||
width: height,
|
||||
height: height
|
||||
}} src={AliasAvatar}/>
|
||||
if(!fallback) return <Avatar sx={{ width: height, height: height }} src={avatarLink} alt={user} />
|
||||
return <Avatar sx={{ width: height, height: height }} src={avatarLink} alt={fallback} >{fallback?.charAt(0)}</Avatar>
|
||||
}
|
||||
if (isAlias)
|
||||
return (
|
||||
<AliasAvatarImg
|
||||
sx={{
|
||||
width: height,
|
||||
height: height,
|
||||
}}
|
||||
src={AliasAvatar}
|
||||
/>
|
||||
);
|
||||
if (!fallback)
|
||||
return (
|
||||
<Avatar
|
||||
sx={{ width: height, height: height }}
|
||||
src={avatarLink}
|
||||
alt={user}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<Avatar
|
||||
sx={{ width: height, height: height }}
|
||||
src={avatarLink}
|
||||
alt={fallback}
|
||||
>
|
||||
{fallback?.charAt(0)}
|
||||
</Avatar>
|
||||
);
|
||||
};
|
||||
|
@ -54,7 +54,7 @@ import {
|
||||
NewMessageInputRow,
|
||||
NewMessageSendButton,
|
||||
NewMessageSendP,
|
||||
} from "./Mail-styles";
|
||||
} from "../Home/Home-styles";
|
||||
import ComposeIconSVG from "../../assets/svgs/ComposeIcon.svg";
|
||||
import ModalCloseSVG from "../../assets/svgs/ModalClose.svg";
|
||||
import AttachmentSVG from "../../assets/svgs/NewMessageAttachment.svg";
|
||||
|
@ -50,7 +50,7 @@ import {
|
||||
NewMessageInputRow,
|
||||
NewMessageSendButton,
|
||||
NewMessageSendP,
|
||||
} from "./Mail-styles";
|
||||
} from "../Home/Home-styles";
|
||||
import { Spacer } from "../../components/common/Spacer";
|
||||
import { TextEditor } from "../../components/common/TextEditor/TextEditor";
|
||||
import { SendNewMessage } from "../../assets/svgs/SendNewMessage";
|
||||
|
@ -38,7 +38,7 @@ 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 { MessagesContainer } from "../Home/Home-styles";
|
||||
import { MailMessageRow } from "./MailMessageRow";
|
||||
|
||||
interface SentMailProps {
|
||||
|
@ -35,7 +35,7 @@ import {
|
||||
ShowMessageNameP,
|
||||
ShowMessageSubjectP,
|
||||
ShowMessageTimeP,
|
||||
} from "./Mail-styles";
|
||||
} from "../Home/Home-styles";
|
||||
import ReplySVG from "../../assets/svgs/Reply.svg";
|
||||
import ForwardSVG from "../../assets/svgs/Forward.svg";
|
||||
import AttachmentMailSVG from "../../assets/svgs/AttachmentMail.svg";
|
||||
@ -57,14 +57,14 @@ export const ShowMessageV2 = ({
|
||||
message,
|
||||
setReplyTo,
|
||||
alias,
|
||||
setForwardInfo
|
||||
setForwardInfo,
|
||||
}: any) => {
|
||||
const [value, setValue] = useState(initialValue);
|
||||
const [title, setTitle] = useState<string>("");
|
||||
const [attachments, setAttachments] = useState<any[]>([]);
|
||||
const [description, setDescription] = useState<string>("");
|
||||
const [expandAttachments, setExpandAttachments] = useState<boolean>(false);
|
||||
const [toUser, setToUser] = useState(null)
|
||||
const [toUser, setToUser] = useState(null);
|
||||
const [destinationName, setDestinationName] = useState("");
|
||||
const username = useSelector((state: RootState) => state.auth?.user?.name);
|
||||
const dispatch = useDispatch();
|
||||
@ -79,33 +79,33 @@ export const ShowMessageV2 = ({
|
||||
setReplyTo(message);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
const handleForwardedMessage = ()=> {
|
||||
if(!username) return
|
||||
let secondpart = ""
|
||||
if(message?.textContentV2){
|
||||
secondpart = message.textContentV2
|
||||
const handleForwardedMessage = () => {
|
||||
if (!username) return;
|
||||
let secondpart = "";
|
||||
if (message?.textContentV2) {
|
||||
secondpart = message.textContentV2;
|
||||
}
|
||||
if (message?.htmlContent) {
|
||||
secondpart = DOMPurify.sanitize(message.htmlContent);
|
||||
}
|
||||
let newTo = username
|
||||
if(alias){
|
||||
newTo = `${alias} (alias)`
|
||||
let newTo = username;
|
||||
if (alias) {
|
||||
newTo = `${alias} (alias)`;
|
||||
}
|
||||
const firstPart = updateMessageDetails(message?.user, message?.subject || "", newTo)
|
||||
const fullMessage = firstPart + secondpart
|
||||
setForwardInfo(fullMessage)
|
||||
}
|
||||
const firstPart = updateMessageDetails(
|
||||
message?.user,
|
||||
message?.subject || "",
|
||||
newTo
|
||||
);
|
||||
const fullMessage = firstPart + secondpart;
|
||||
setForwardInfo(fullMessage);
|
||||
};
|
||||
|
||||
let cleanHTML = "";
|
||||
if (message?.htmlContent) {
|
||||
cleanHTML = DOMPurify.sanitize(message.htmlContent);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
@ -154,9 +154,11 @@ export const ShowMessageV2 = ({
|
||||
}}
|
||||
>
|
||||
<ShowMessageNameP>{message?.user}</ShowMessageNameP>
|
||||
<ShowMessageTimeP sx={{
|
||||
marginTop: '2px'
|
||||
}}>
|
||||
<ShowMessageTimeP
|
||||
sx={{
|
||||
marginTop: "2px",
|
||||
}}
|
||||
>
|
||||
to: {message?.to}
|
||||
</ShowMessageTimeP>
|
||||
<ShowMessageTimeP>
|
||||
@ -181,78 +183,81 @@ export const ShowMessageV2 = ({
|
||||
marginTop: "10px",
|
||||
}}
|
||||
>
|
||||
{message?.attachments
|
||||
.map((file: any, index: number) => {
|
||||
const isFirst = index === 0
|
||||
return (
|
||||
{message?.attachments.map((file: any, index: number) => {
|
||||
const isFirst = index === 0;
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: expandAttachments
|
||||
? "flex"
|
||||
: !expandAttachments && isFirst
|
||||
? "flex"
|
||||
: "none",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-start",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: expandAttachments ? "flex" : !expandAttachments && isFirst ? 'flex' : 'none',
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-start",
|
||||
width: "100%",
|
||||
gap: "5px",
|
||||
cursor: "pointer",
|
||||
width: "auto",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "5px",
|
||||
cursor: "pointer",
|
||||
width: "auto",
|
||||
}}
|
||||
<FileElement
|
||||
fileInfo={{ ...file, mimeTypeSaved: file?.type }}
|
||||
title={file?.filename}
|
||||
mode="mail"
|
||||
otherUser={message?.user}
|
||||
>
|
||||
<FileElement
|
||||
fileInfo={{ ...file, mimeTypeSaved: file?.type }}
|
||||
title={file?.filename}
|
||||
mode="mail"
|
||||
otherUser={message?.user}
|
||||
>
|
||||
<MailAttachmentImg src={AttachmentMailSVG} />
|
||||
<MailAttachmentImg src={AttachmentMailSVG} />
|
||||
|
||||
<Typography
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
transition: "0.2s all",
|
||||
"&:hover": {
|
||||
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={{
|
||||
fontSize: "16px",
|
||||
transition: '0.2s all',
|
||||
"&:hover": {
|
||||
textDecoration: 'underline'
|
||||
}
|
||||
marginLeft: "5px",
|
||||
transform: expandAttachments
|
||||
? "rotate(180deg)"
|
||||
: "unset",
|
||||
}}
|
||||
>
|
||||
{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={{
|
||||
marginLeft: "5px",
|
||||
transform: expandAttachments
|
||||
? "rotate(180deg)"
|
||||
: "unset",
|
||||
}}
|
||||
src={MoreSVG}
|
||||
/>
|
||||
<MoreP>
|
||||
{expandAttachments ? 'hide' : `(${message?.attachments?.length - 1} more)`}
|
||||
|
||||
</MoreP>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
src={MoreSVG}
|
||||
/>
|
||||
<MoreP>
|
||||
{expandAttachments
|
||||
? "hide"
|
||||
: `(${message?.attachments?.length - 1} more)`}
|
||||
</MoreP>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
})
|
||||
}
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
)}
|
||||
<Spacer height="7px" />
|
||||
@ -268,7 +273,7 @@ export const ShowMessageV2 = ({
|
||||
display: "flex",
|
||||
gap: "25px",
|
||||
justifyContent: "center",
|
||||
marginTop: '10px'
|
||||
marginTop: "10px",
|
||||
}}
|
||||
>
|
||||
<ShowMessageButton onClick={handleReply}>
|
||||
@ -292,31 +297,33 @@ export const ShowMessageV2 = ({
|
||||
gap: "8px",
|
||||
}}
|
||||
>
|
||||
{message?.generalData?.threadV2 && [...(message?.generalData?.threadV2 || [])] // Create a copy of the array
|
||||
.sort((a, b) => {
|
||||
// Sort messages based on createdAt in descending order
|
||||
if (!a.data || !b.data) return 0;
|
||||
return a.data.createdAt - b.data.createdAt;
|
||||
})
|
||||
.map(msg => {
|
||||
// Render each message using a component
|
||||
if (!msg?.data) return null;
|
||||
return <ShowMessageV2Replies key={msg.data.id} message={msg.data} />;
|
||||
})
|
||||
}
|
||||
{message?.generalData?.threadV2 &&
|
||||
[...(message?.generalData?.threadV2 || [])] // Create a copy of the array
|
||||
.sort((a, b) => {
|
||||
// Sort messages based on createdAt in descending order
|
||||
if (!a.data || !b.data) return 0;
|
||||
return a.data.createdAt - b.data.createdAt;
|
||||
})
|
||||
.map(msg => {
|
||||
// Render each message using a component
|
||||
if (!msg?.data) return null;
|
||||
return (
|
||||
<ShowMessageV2Replies key={msg.data.id} message={msg.data} />
|
||||
);
|
||||
})}
|
||||
{message?.htmlContent && (
|
||||
<div dangerouslySetInnerHTML={{ __html: cleanHTML }} />
|
||||
)}
|
||||
<Box sx={{
|
||||
width: '100%',
|
||||
padding: '15px'
|
||||
}}>
|
||||
|
||||
|
||||
{message?.textContent && (
|
||||
<div dangerouslySetInnerHTML={{ __html: cleanHTML }} />
|
||||
)}
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
padding: "15px",
|
||||
}}
|
||||
>
|
||||
{message?.textContent && (
|
||||
<ReadOnlySlate content={message.textContent} mode="mail" />
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
@ -25,13 +25,22 @@ import FileElement from "../../components/FileElement";
|
||||
import MailThreadWithoutCalling from "./MailThreadWithoutCalling";
|
||||
import { DisplayHtml } from "../../components/common/TextEditor/DisplayHtml";
|
||||
import { Spacer } from "../../components/common/Spacer";
|
||||
import { MailAttachmentImg, MoreImg, MoreP, ShowMessageButton, ShowMessageButtonImg, ShowMessageButtonP, ShowMessageNameP, ShowMessageSubjectP, ShowMessageTimeP } from "./Mail-styles";
|
||||
import ReplySVG from '../../assets/svgs/Reply.svg'
|
||||
import ForwardSVG from '../../assets/svgs/Forward.svg'
|
||||
import {
|
||||
MailAttachmentImg,
|
||||
MoreImg,
|
||||
MoreP,
|
||||
ShowMessageButton,
|
||||
ShowMessageButtonImg,
|
||||
ShowMessageButtonP,
|
||||
ShowMessageNameP,
|
||||
ShowMessageSubjectP,
|
||||
ShowMessageTimeP,
|
||||
} from "../Home/Home-styles";
|
||||
import ReplySVG from "../../assets/svgs/Reply.svg";
|
||||
import ForwardSVG from "../../assets/svgs/Forward.svg";
|
||||
import MoreSVG from "../../assets/svgs/More.svg";
|
||||
import AttachmentMailSVG from "../../assets/svgs/AttachmentMail.svg";
|
||||
|
||||
|
||||
const initialValue: Descendant[] = [
|
||||
{
|
||||
type: "paragraph",
|
||||
@ -40,21 +49,15 @@ const initialValue: Descendant[] = [
|
||||
];
|
||||
const uid = new ShortUniqueId();
|
||||
|
||||
export const ShowMessageV2Replies = ({
|
||||
message,
|
||||
}: any) => {
|
||||
export const ShowMessageV2Replies = ({ message }: any) => {
|
||||
const username = useSelector((state: RootState) => state.auth?.user?.name);
|
||||
const [expandAttachments, setExpandAttachments] = useState<boolean>(false);
|
||||
|
||||
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
|
||||
|
||||
const isUser = useMemo(()=> {
|
||||
return username === message?.user
|
||||
}, [username, message])
|
||||
|
||||
|
||||
const isUser = useMemo(() => {
|
||||
return username === message?.user;
|
||||
}, [username, message]);
|
||||
|
||||
let cleanHTML = "";
|
||||
if (message?.htmlContent) {
|
||||
@ -62,199 +65,203 @@ export const ShowMessageV2Replies = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={{
|
||||
padding: !isUser ? '0px 15px' : 'unset',
|
||||
flexShrink: 0,
|
||||
width: '100%'
|
||||
}}
|
||||
className={isUser ? "" : "reply-other"}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
padding: !isUser ? "0px 15px" : "unset",
|
||||
flexShrink: 0,
|
||||
width: "100%",
|
||||
'border-radius': '4px',
|
||||
'background': !isUser ? 'rgba(255, 255, 255, 0.80)' : 'unset',
|
||||
padding: '7px',
|
||||
|
||||
}}
|
||||
className={isUser ? "" : "reply-other"}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexDirection: "column",
|
||||
gap: 1,
|
||||
flexGrow: 1,
|
||||
overflow: "auto",
|
||||
width: "100%",
|
||||
padding: "0 15px",
|
||||
"border-radius": "4px",
|
||||
background: !isUser ? "rgba(255, 255, 255, 0.80)" : "unset",
|
||||
padding: "7px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
onClick={()=> {
|
||||
setIsExpanded((prev)=> !prev)
|
||||
}}
|
||||
sx={{
|
||||
cursor: 'pointer',
|
||||
display: "flex",
|
||||
gap: '20px',
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "flex-start",
|
||||
alignItems: "center",
|
||||
flexDirection: "column",
|
||||
gap: 1,
|
||||
flexGrow: 1,
|
||||
overflow: "auto",
|
||||
width: "100%",
|
||||
flexDirection: !isUser ? 'row-reverse' : 'unset'
|
||||
padding: "0 15px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
onClick={() => {
|
||||
setIsExpanded(prev => !prev);
|
||||
}}
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
display: "flex",
|
||||
gap: "20px",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "flex-start",
|
||||
gap: "6px"
|
||||
width: "100%",
|
||||
flexDirection: !isUser ? "row-reverse" : "unset",
|
||||
}}
|
||||
>
|
||||
<AvatarWrapper height="40px" user={message?.user} />
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
flexDirection: 'column',
|
||||
gap: '1px',
|
||||
justifyContent: "flex-start",
|
||||
maxWidth: '160px',
|
||||
minWidth: '120px'
|
||||
|
||||
}}>
|
||||
<ShowMessageNameP
|
||||
sx={{
|
||||
color: !isUser ? 'black' : 'unset'
|
||||
}}
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "flex-start",
|
||||
gap: "6px",
|
||||
}}
|
||||
>
|
||||
{message?.user}
|
||||
</ShowMessageNameP>
|
||||
<ShowMessageTimeP
|
||||
sx={{
|
||||
color: !isUser ? 'black' : 'unset'
|
||||
}}
|
||||
>
|
||||
{formatEmailDate(message?.createdAt)}
|
||||
</ShowMessageTimeP>
|
||||
|
||||
<AvatarWrapper height="40px" user={message?.user} />
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "1px",
|
||||
justifyContent: "flex-start",
|
||||
maxWidth: "160px",
|
||||
minWidth: "120px",
|
||||
}}
|
||||
>
|
||||
<ShowMessageNameP
|
||||
sx={{
|
||||
color: !isUser ? "black" : "unset",
|
||||
}}
|
||||
>
|
||||
{message?.user}
|
||||
</ShowMessageNameP>
|
||||
<ShowMessageTimeP
|
||||
sx={{
|
||||
color: !isUser ? "black" : "unset",
|
||||
}}
|
||||
>
|
||||
{formatEmailDate(message?.createdAt)}
|
||||
</ShowMessageTimeP>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "flex-start",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
<ShowMessageSubjectP
|
||||
sx={{
|
||||
color: !isUser ? 'black' : 'unset'
|
||||
}}
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "flex-start",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
{message?.subject}
|
||||
</ShowMessageSubjectP>
|
||||
|
||||
<ShowMessageSubjectP
|
||||
sx={{
|
||||
color: !isUser ? "black" : "unset",
|
||||
}}
|
||||
>
|
||||
{message?.subject}
|
||||
</ShowMessageSubjectP>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
{isExpanded && (
|
||||
<>
|
||||
{message?.attachments?.length > 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
marginTop: "10px",
|
||||
}}
|
||||
>
|
||||
{message?.attachments?.length > 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
marginTop: "10px",
|
||||
}}
|
||||
>
|
||||
{message?.attachments
|
||||
.map((file: any, index: number) => {
|
||||
const isFirst = index === 0
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: expandAttachments ? "flex" : !expandAttachments && isFirst ? 'flex' : 'none',
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-start",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
{isExpanded && (
|
||||
<>
|
||||
{message?.attachments?.length > 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
marginTop: "10px",
|
||||
}}
|
||||
>
|
||||
{message?.attachments?.length > 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "5px",
|
||||
cursor: "pointer",
|
||||
width: "auto",
|
||||
width: "100%",
|
||||
marginTop: "10px",
|
||||
}}
|
||||
>
|
||||
<FileElement
|
||||
fileInfo={{ ...file, mimeTypeSaved: file?.type }}
|
||||
title={file?.filename}
|
||||
mode="mail"
|
||||
otherUser={message?.user}
|
||||
>
|
||||
<MailAttachmentImg src={AttachmentMailSVG} />
|
||||
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
}}
|
||||
>
|
||||
{file?.originalFilename || file?.filename}
|
||||
</Typography>
|
||||
</FileElement>
|
||||
{message?.attachments?.length > 1 && isFirst && (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "5px",
|
||||
}}
|
||||
onClick={() => {
|
||||
setExpandAttachments(prev => !prev);
|
||||
}}
|
||||
>
|
||||
<MoreImg
|
||||
{message?.attachments.map((file: any, index: number) => {
|
||||
const isFirst = index === 0;
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
marginLeft: "5px",
|
||||
transform: expandAttachments
|
||||
? "rotate(180deg)"
|
||||
: "unset",
|
||||
display: expandAttachments
|
||||
? "flex"
|
||||
: !expandAttachments && isFirst
|
||||
? "flex"
|
||||
: "none",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-start",
|
||||
width: "100%",
|
||||
}}
|
||||
src={MoreSVG}
|
||||
/>
|
||||
<MoreP>
|
||||
({message?.attachments?.length - 1} more)
|
||||
</MoreP>
|
||||
</Box>
|
||||
)}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "5px",
|
||||
cursor: "pointer",
|
||||
width: "auto",
|
||||
}}
|
||||
>
|
||||
<FileElement
|
||||
fileInfo={{
|
||||
...file,
|
||||
mimeTypeSaved: file?.type,
|
||||
}}
|
||||
title={file?.filename}
|
||||
mode="mail"
|
||||
otherUser={message?.user}
|
||||
>
|
||||
<MailAttachmentImg src={AttachmentMailSVG} />
|
||||
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
}}
|
||||
>
|
||||
{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={{
|
||||
marginLeft: "5px",
|
||||
transform: expandAttachments
|
||||
? "rotate(180deg)"
|
||||
: "unset",
|
||||
}}
|
||||
src={MoreSVG}
|
||||
/>
|
||||
<MoreP>
|
||||
({message?.attachments?.length - 1} more)
|
||||
</MoreP>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
})
|
||||
}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
<Spacer height="7px" />
|
||||
{message?.textContentV2 && (
|
||||
<DisplayHtml html={message?.textContentV2} textColor={!isUser ? "black": "white"}/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
<Spacer height="7px" />
|
||||
{message?.textContentV2 && (
|
||||
<DisplayHtml
|
||||
html={message?.textContentV2}
|
||||
textColor={!isUser ? "black" : "white"}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@ -38,7 +38,7 @@ import {
|
||||
ThreadSingleLastMessageP,
|
||||
ThreadSingleLastMessageSpanP,
|
||||
ThreadSingleTitle,
|
||||
} from "./Mail-styles";
|
||||
} from "../Home/Home-styles";
|
||||
import { Spacer } from "../../components/common/Spacer";
|
||||
const initialValue: Descendant[] = [
|
||||
{
|
||||
|
@ -37,7 +37,7 @@ import {
|
||||
SingleThreadParent,
|
||||
ThreadContainer,
|
||||
ThreadContainerFullWidth,
|
||||
} from "./Mail-styles";
|
||||
} from "../Home/Home-styles";
|
||||
import { Spacer } from "../../components/common/Spacer";
|
||||
import ReturnSVG from "../../assets/svgs/Return.svg";
|
||||
import LazyLoad from "../../components/common/LazyLoad";
|
||||
|
Loading…
x
Reference in New Issue
Block a user