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:
Qortal Dev 2024-08-26 16:20:21 -06:00
parent aaf9923103
commit c1d812a0e6
17 changed files with 861 additions and 1168 deletions

View File

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

View File

@ -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
View File

@ -0,0 +1 @@
export const appOwner = "Q-Mintership";

47
src/pages/Forum/Forum.tsx Normal file
View 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>
);
};

View File

@ -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>
);
};

View File

@ -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> */}
</>
)
}
);
};

View File

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

View File

@ -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>
);
}

View File

@ -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,
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>

View File

@ -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>
);
};

View File

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

View File

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

View File

@ -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 {

View File

@ -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} />
<Typography
sx={{
fontSize: "16px",
transition: "0.2s all",
"&:hover": {
textDecoration: "underline",
},
}}
>
<MailAttachmentImg src={AttachmentMailSVG} />
<Typography
{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>
);

View File

@ -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>
);
};

View File

@ -38,7 +38,7 @@ import {
ThreadSingleLastMessageP,
ThreadSingleLastMessageSpanP,
ThreadSingleTitle,
} from "./Mail-styles";
} from "../Home/Home-styles";
import { Spacer } from "../../components/common/Spacer";
const initialValue: Descendant[] = [
{

View File

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