From 383e4d838046af9661dc44c284bc077f5752278a Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Mon, 21 Jul 2025 10:24:04 +0330 Subject: [PATCH] email actions in single --- src/components/newMessage/NewMessage.tsx | 1 - src/langs/fa.json | 9 +- src/pages/Trash/hooks/useTrashData.tsx | 2 +- src/pages/archive/List.tsx | 2 +- src/pages/archive/hooks/useArchiveData.tsx | 6 +- src/pages/draft/hooks/useDraftData.tsx | 4 +- src/pages/favorite/List.tsx | 2 +- src/pages/favorite/hooks/useFavoriteData.tsx | 2 +- src/pages/received/Components/Header.tsx | 17 +-- .../received/Components/MessageArchive.tsx | 33 ++++++ .../received/Components/MessageFavorite.tsx | 32 ++++++ src/pages/received/Components/MessageSpam.tsx | 34 ++++++ .../received/Components/MessageUnread.tsx | 25 +++++ src/pages/received/Detail.tsx | 10 +- src/pages/received/enum/Enum.ts | 9 ++ src/pages/received/hooks/useEmailData.tsx | 64 ++++++++++- src/pages/received/service/EmailService.ts | 100 ++++++++++++++++++ src/pages/received/types/Types.ts | 7 ++ src/pages/spam/List.tsx | 2 +- src/pages/spam/hooks/useSpamData.tsx | 2 +- 20 files changed, 333 insertions(+), 30 deletions(-) create mode 100644 src/pages/received/Components/MessageArchive.tsx create mode 100644 src/pages/received/Components/MessageFavorite.tsx create mode 100644 src/pages/received/Components/MessageSpam.tsx create mode 100644 src/pages/received/Components/MessageUnread.tsx create mode 100644 src/pages/received/enum/Enum.ts diff --git a/src/components/newMessage/NewMessage.tsx b/src/components/newMessage/NewMessage.tsx index 8aaf773..e77340b 100644 --- a/src/components/newMessage/NewMessage.tsx +++ b/src/components/newMessage/NewMessage.tsx @@ -46,7 +46,6 @@ const NewMessage: FC = () => { const { data: draftDetail } = useGetMessageDetail( draftData?.id.toString() || '', draftData?.mailbox || '', - !!draftData?.id ) // Load draft data into form when draft detail is available diff --git a/src/langs/fa.json b/src/langs/fa.json index b4290c5..2f8f144 100644 --- a/src/langs/fa.json +++ b/src/langs/fa.json @@ -133,7 +133,14 @@ "attachments": "فایل های ضمیمه", "answer": "پاسخ", "forward": "ارسال به دیگران", - "message": "متن پبام" + "message": "متن پبام", + "INBOX": "دریافتی‌ها", + "DRAFTS": "پیش‌نویس‌ها", + "Archive": "آرشیو شده‌ها", + "FAVORITE": "نشان‌شده‌ها", + "Junk": "هرزنامه‌ها", + "SENT": "ارسال شده‌ها", + "TRASH": "سطل زباله" }, "auth": { "welcome": "خوش آمدید", diff --git a/src/pages/Trash/hooks/useTrashData.tsx b/src/pages/Trash/hooks/useTrashData.tsx index 83e858e..9aa3c1e 100644 --- a/src/pages/Trash/hooks/useTrashData.tsx +++ b/src/pages/Trash/hooks/useTrashData.tsx @@ -6,7 +6,7 @@ export const useGetTrashMessages = (query: MessageListQueryDto) => { return useQuery({ queryKey: ['trash-messages', query], queryFn: () => getTrashMessages(query), - staleTime: 5 * 60 * 1000, // 5 minutes + staleTime: 0 }); }; diff --git a/src/pages/archive/List.tsx b/src/pages/archive/List.tsx index 1688014..8fd936a 100644 --- a/src/pages/archive/List.tsx +++ b/src/pages/archive/List.tsx @@ -145,7 +145,7 @@ const List: FC = () => { selectedActions={selectedActions} onSelectionChange={handleSelectionChange} selectedRows={selectedMessages} - onRowClick={(id) => navigate(`/mail/${id}`)} + onRowClick={(id, item) => navigate(`/mail/${id}/${item.mailbox}`)} noDataMessage={
diff --git a/src/pages/archive/hooks/useArchiveData.tsx b/src/pages/archive/hooks/useArchiveData.tsx index 4a8aaa4..2427690 100644 --- a/src/pages/archive/hooks/useArchiveData.tsx +++ b/src/pages/archive/hooks/useArchiveData.tsx @@ -6,8 +6,6 @@ export const useGetArchiveMessages = (query: MessageListQueryDto) => { return useQuery({ queryKey: ['archive-messages', query], queryFn: () => getArchiveMessages(query), - staleTime: 5 * 60 * 1000, // 5 minutes + staleTime: 0 }); -}; - -// Note: useUnarchiveMessage is now handled by useEmailActions hook \ No newline at end of file +}; \ No newline at end of file diff --git a/src/pages/draft/hooks/useDraftData.tsx b/src/pages/draft/hooks/useDraftData.tsx index 4972bcd..8179cfc 100644 --- a/src/pages/draft/hooks/useDraftData.tsx +++ b/src/pages/draft/hooks/useDraftData.tsx @@ -6,7 +6,7 @@ export const useGetDrafts = (query: MessageListQueryDto) => { return useQuery({ queryKey: ['drafts', query], queryFn: () => api.getDrafts(query), - staleTime: 1000 * 60 * 5, // 5 minutes + staleTime: 0 }); }; @@ -15,7 +15,7 @@ export const useGetDraftDetail = (messageId: string, enabled: boolean = true) => queryKey: ['draft', messageId], queryFn: () => api.getDraftDetail(messageId), enabled: !!messageId && enabled, - staleTime: 1000 * 60 * 10, // 10 minutes + staleTime: 0 }); }; diff --git a/src/pages/favorite/List.tsx b/src/pages/favorite/List.tsx index 9ab999c..163466f 100644 --- a/src/pages/favorite/List.tsx +++ b/src/pages/favorite/List.tsx @@ -157,7 +157,7 @@ const List: FC = () => { selectedActions={selectedActions} onSelectionChange={handleSelectionChange} selectedRows={selectedMessages} - onRowClick={(id) => navigate(`/mail/${id}`)} + onRowClick={(id, item) => navigate(`/mail/${id}/${item.mailbox}`)} noDataMessage={
diff --git a/src/pages/favorite/hooks/useFavoriteData.tsx b/src/pages/favorite/hooks/useFavoriteData.tsx index edd6cf7..1f12632 100644 --- a/src/pages/favorite/hooks/useFavoriteData.tsx +++ b/src/pages/favorite/hooks/useFavoriteData.tsx @@ -6,7 +6,7 @@ export const useGetFavoriteMessages = (query: MessageListQueryDto) => { return useQuery({ queryKey: ['favorite-messages', query], queryFn: () => getFavoriteMessages(query), - staleTime: 5 * 60 * 1000, // 5 minutes + staleTime: 0 }); }; diff --git a/src/pages/received/Components/Header.tsx b/src/pages/received/Components/Header.tsx index 534a65f..211cc7b 100644 --- a/src/pages/received/Components/Header.tsx +++ b/src/pages/received/Components/Header.tsx @@ -1,10 +1,15 @@ import Button from '@/components/Button' -import { ArrowRight, InfoCircle, SmsNotification, Star, VolumeMute, More, DocumentDownload, ArchiveTick, Printer, Trash } from 'iconsax-react' +import { ArrowRight, VolumeMute, More, DocumentDownload, Printer, Trash } from 'iconsax-react' import { useNavigate } from 'react-router-dom' import { FC } from 'react' import { useTranslation } from 'react-i18next' +import MessageUnread from './MessageUnread' +import MessageArchive from './MessageArchive' +import MessageSpam from './MessageSpam' +import { MailboxEnum } from '../enum/Enum' +import MessageFavorite from './MessageFavorite' -const Header: FC = () => { +const Header: FC<{ mailBoxName: MailboxEnum }> = ({ mailBoxName }) => { const navigate = useNavigate() const { t } = useTranslation() @@ -13,10 +18,10 @@ const Header: FC = () => {
navigate(-1)} /> - - - - + + + + diff --git a/src/pages/received/Components/MessageArchive.tsx b/src/pages/received/Components/MessageArchive.tsx new file mode 100644 index 0000000..e885291 --- /dev/null +++ b/src/pages/received/Components/MessageArchive.tsx @@ -0,0 +1,33 @@ +import { useMoveToArchive, useUnArchiveMessage } from '../hooks/useEmailData' +import { ArchiveTick } from 'iconsax-react' +import { FC, useState } from 'react' +import { useNavigate, useParams } from 'react-router-dom' + +const MessageArchive: FC<{ isActiveArchive: boolean }> = ({ isActiveArchive }) => { + + const navigate = useNavigate() + const [isArchive, setIsArchive] = useState(isActiveArchive) + const { id, mailbox } = useParams<{ id: string, mailbox: string }>() + const { mutate: moveToArchive } = useMoveToArchive() + const { mutate: unArchiveMessage } = useUnArchiveMessage() + return ( + { + if (isArchive) { + unArchiveMessage({ messageId: id || '', mailbox: mailbox || '' }, { + onSuccess: () => { + navigate(-1) + } + }) + } else { + moveToArchive({ messageId: id || '', mailbox: mailbox || '' }, { + onSuccess: () => { + navigate(-1) + } + }) + } + setIsArchive(!isArchive) + }} size={18} variant={isArchive ? 'Bold' : 'Outline'} color='black' className='xl:block hidden' /> + ) +} + +export default MessageArchive \ No newline at end of file diff --git a/src/pages/received/Components/MessageFavorite.tsx b/src/pages/received/Components/MessageFavorite.tsx new file mode 100644 index 0000000..a8c8bfa --- /dev/null +++ b/src/pages/received/Components/MessageFavorite.tsx @@ -0,0 +1,32 @@ +import { Star1 } from 'iconsax-react' +import { FC, useState } from 'react' +import { useNavigate, useParams } from 'react-router-dom' +import { useMoveToFavorite, useUnFavoriteMessage } from '../hooks/useEmailData' + +const MessageFavorite: FC<{ isActiveFavorite: boolean }> = ({ isActiveFavorite }) => { + const [isFavorite, setIsFavorite] = useState(isActiveFavorite) + const { id, mailbox } = useParams<{ id: string, mailbox: string }>() + const { mutate: moveToFavorite } = useMoveToFavorite() + const { mutate: unFavoriteMessage } = useUnFavoriteMessage() + const navigate = useNavigate() + return ( + { + if (isFavorite) { + unFavoriteMessage({ messageId: id || '', mailbox: mailbox || '' }, { + onSuccess: () => { + navigate(-1) + } + }) + } else { + moveToFavorite({ messageId: id || '', mailbox: mailbox || '' }, { + onSuccess: () => { + navigate(-1) + } + }) + } + setIsFavorite(!isFavorite) + }} size={18} variant={isFavorite ? 'Bold' : 'Outline'} color={isFavorite ? '#FFC107' : 'black'} className='xl:block hidden' /> + ) +} + +export default MessageFavorite \ No newline at end of file diff --git a/src/pages/received/Components/MessageSpam.tsx b/src/pages/received/Components/MessageSpam.tsx new file mode 100644 index 0000000..42cb791 --- /dev/null +++ b/src/pages/received/Components/MessageSpam.tsx @@ -0,0 +1,34 @@ +import { InfoCircle } from 'iconsax-react' +import { FC, useState } from 'react' +import { useNavigate, useParams } from 'react-router-dom' +import { useMoveToJunk, useMoveToNotJunk } from '../hooks/useEmailData' + +const MessageSpam: FC<{ isActiveSpam: boolean }> = ({ isActiveSpam }) => { + + const navigate = useNavigate() + const [isSpam, setIsSpam] = useState(isActiveSpam) + const { id, mailbox } = useParams<{ id: string, mailbox: string }>() + const { mutate: moveToSpam } = useMoveToJunk() + const { mutate: moveToNotSpam } = useMoveToNotJunk() + + return ( + { + if (isSpam) { + moveToNotSpam({ messageId: id || '', mailbox: mailbox || '' }, { + onSuccess: () => { + navigate(-1) + } + }) + } else { + moveToSpam({ messageId: id || '', mailbox: mailbox || '' }, { + onSuccess: () => { + navigate(-1) + } + }) + } + setIsSpam(!isSpam) + }} size={18} variant={isSpam ? 'Bold' : 'Outline'} color='black' className='xl:block hidden' /> + ) +} + +export default MessageSpam \ No newline at end of file diff --git a/src/pages/received/Components/MessageUnread.tsx b/src/pages/received/Components/MessageUnread.tsx new file mode 100644 index 0000000..4d4510b --- /dev/null +++ b/src/pages/received/Components/MessageUnread.tsx @@ -0,0 +1,25 @@ +import { SmsNotification } from 'iconsax-react' +import { FC, useState } from 'react' +import { useMarkAsRead, useMarkAsUnread } from '../hooks/useEmailData' +import { useParams } from 'react-router-dom' + +const MessageUnread: FC = () => { + + const [isRead, setIsRead] = useState(true) + const { id, mailbox } = useParams<{ id: string, mailbox: string }>() + const { mutate: markAsRead } = useMarkAsRead() + const { mutate: markAsUnread } = useMarkAsUnread() + + return ( + { + if (isRead) { + markAsUnread({ messageId: id || '', mailbox: mailbox || '' }) + } else { + markAsRead({ messageId: id || '', mailbox: mailbox || '' }) + } + setIsRead(!isRead) + }} size={18} variant={isRead ? 'Outline' : 'Bold'} color='black' className='xl:block hidden' /> + ) +} + +export default MessageUnread \ No newline at end of file diff --git a/src/pages/received/Detail.tsx b/src/pages/received/Detail.tsx index e76f239..7bc8af7 100644 --- a/src/pages/received/Detail.tsx +++ b/src/pages/received/Detail.tsx @@ -28,7 +28,7 @@ const DetailEmail: FC = () => { const [priority, setPriority] = useState('') // API hooks - const { data: messageDetail, isLoading, error } = useGetMessageDetail(id || '', mailbox || '', !!id) + const { data: messageDetail, isLoading, error } = useGetMessageDetail(id || '', mailbox || '') const sendEmailMutation = useSendEmail() // const saveDraftMutation = useSaveDraft() @@ -135,7 +135,7 @@ const DetailEmail: FC = () => { return (
-
+
@@ -145,13 +145,13 @@ const DetailEmail: FC = () => {
diff --git a/src/pages/received/enum/Enum.ts b/src/pages/received/enum/Enum.ts new file mode 100644 index 0000000..e1338f5 --- /dev/null +++ b/src/pages/received/enum/Enum.ts @@ -0,0 +1,9 @@ +export const enum MailboxEnum { + INBOX = "INBOX", + DRAFTS = "Drafts", + ARCHIVE = "Archive", //should created when user created + FAVORITE = "FAVORITE", //should created when user created + Junk = "Junk", + SENT = "Sent Mail", + TRASH = "Trash", +} diff --git a/src/pages/received/hooks/useEmailData.tsx b/src/pages/received/hooks/useEmailData.tsx index e61e783..61c0e1e 100644 --- a/src/pages/received/hooks/useEmailData.tsx +++ b/src/pages/received/hooks/useEmailData.tsx @@ -1,6 +1,6 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import * as api from '../service/EmailService'; -import { SendEmailDto, MessageListQueryDto } from '../types/Types'; +import { SendEmailDto, MessageListQueryDto, SingleActionRequest } from '../types/Types'; export const useSendEmail = () => { return useMutation({ @@ -18,7 +18,7 @@ export const useGetDraft = (query: MessageListQueryDto) => { return useQuery({ queryKey: ['draft', query], queryFn: () => api.getDraft(query), - staleTime: 1000 * 60 * 5, // 5 minutes + staleTime: 0 }); }; @@ -31,12 +31,12 @@ export const useGetInbox = (query: MessageListQueryDto) => { }); }; -export const useGetMessageDetail = (messageId: string, mailbox: string, enabled: boolean = true) => { +export const useGetMessageDetail = (messageId: string, mailbox: string) => { return useQuery({ queryKey: ['message', messageId, mailbox], queryFn: () => api.getMessageDetail(messageId, mailbox), - enabled: !!messageId && enabled, - staleTime: 0, // Always fetch fresh data for message details + enabled: !!messageId && !!mailbox, + staleTime: 0 }); }; @@ -52,3 +52,57 @@ export const useDeleteMessage = () => { }); }; + +export const useMoveToArchive = () => { + return useMutation({ + mutationFn: ({ messageId, mailbox }: SingleActionRequest) => api.moveToArchive(messageId, mailbox), + }); +}; + +export const useMoveToTrash = () => { + return useMutation({ + mutationFn: ({ messageId, mailbox }: SingleActionRequest) => api.moveToTrash(messageId, mailbox), + }); +}; + +export const useMoveToFavorite = () => { + return useMutation({ + mutationFn: ({ messageId, mailbox }: SingleActionRequest) => api.moveToFavorite(messageId, mailbox), + }); +}; + +export const useMoveToJunk = () => { + return useMutation({ + mutationFn: ({ messageId, mailbox }: SingleActionRequest) => api.moveToJunk(messageId, mailbox), + }); +}; + +export const useMoveToNotJunk = () => { + return useMutation({ + mutationFn: ({ messageId, mailbox }: SingleActionRequest) => api.moveToNotJunk(messageId, mailbox), + }); +}; + +export const useMarkAsRead = () => { + return useMutation({ + mutationFn: ({ messageId, mailbox }: SingleActionRequest) => api.markAsRead(messageId, mailbox), + }); +}; + +export const useMarkAsUnread = () => { + return useMutation({ + mutationFn: ({ messageId, mailbox }: SingleActionRequest) => api.markAsUnread(messageId, mailbox), + }); +}; + +export const useUnArchiveMessage = () => { + return useMutation({ + mutationFn: ({ messageId, mailbox }: SingleActionRequest) => api.unArchiveMessage(messageId, mailbox), + }); +}; + +export const useUnFavoriteMessage = () => { + return useMutation({ + mutationFn: ({ messageId, mailbox }: SingleActionRequest) => api.unFavoriteMessage(messageId, mailbox), + }); +}; \ No newline at end of file diff --git a/src/pages/received/service/EmailService.ts b/src/pages/received/service/EmailService.ts index 068ae13..6f56033 100644 --- a/src/pages/received/service/EmailService.ts +++ b/src/pages/received/service/EmailService.ts @@ -45,3 +45,103 @@ export const deleteMessage = async (messageId: string): Promise => { const { data } = await axios.delete(`/email/messages/${messageId}`); return data; }; + +export const moveToArchive = async ( + messageId: string, + mailbox: string +): Promise => { + const { data } = await axios.patch( + `/email/messages/${messageId}/archive?mailbox=${mailbox}` + ); + return data; +}; + +export const moveToTrash = async ( + messageId: string, + mailbox: string +): Promise => { + const { data } = await axios.patch( + `/email/messages/${messageId}/trash?mailbox=${mailbox}` + ); + return data; +}; + +export const moveToFavorite = async ( + messageId: string, + mailbox: string +): Promise => { + const { data } = await axios.patch( + `/email/messages/${messageId}/favorite?mailbox=${mailbox}` + ); + return data; +}; + +export const moveToJunk = async ( + messageId: string, + mailbox: string +): Promise => { + const { data } = await axios.patch( + `/email/messages/${messageId}/junk?mailbox=${mailbox}` + ); + return data; +}; + +export const moveToNotJunk = async ( + messageId: string, + mailbox: string +): Promise => { + const { data } = await axios.patch( + `/email/messages/${messageId}/not-junk?mailbox=${mailbox}` + ); + return data; +}; + +export const restoreMessage = async ( + messageId: string, + mailbox: string +): Promise => { + const { data } = await axios.patch( + `/email/messages/${messageId}/restore?mailbox=${mailbox}` + ); + return data; +}; + +export const unFavoriteMessage = async ( + messageId: string, + mailbox: string +): Promise => { + const { data } = await axios.patch( + `/email/messages/${messageId}/unfavorite?mailbox=${mailbox}` + ); + return data; +}; + +export const unArchiveMessage = async ( + messageId: string, + mailbox: string +): Promise => { + const { data } = await axios.patch( + `/email/messages/${messageId}/unarchive?mailbox=${mailbox}` + ); + return data; +}; + +export const markAsRead = async ( + messageId: string, + mailbox: string +): Promise => { + const { data } = await axios.patch( + `/email/messages/${messageId}/seen?mailbox=${mailbox}` + ); + return data; +}; + +export const markAsUnread = async ( + messageId: string, + mailbox: string +): Promise => { + const { data } = await axios.patch( + `/email/messages/${messageId}/unseen?mailbox=${mailbox}` + ); + return data; +}; diff --git a/src/pages/received/types/Types.ts b/src/pages/received/types/Types.ts index 5b686af..bc398bd 100644 --- a/src/pages/received/types/Types.ts +++ b/src/pages/received/types/Types.ts @@ -235,6 +235,8 @@ export interface MessageDetail { success: boolean; id: number; mailbox: string; + mailboxName?: string; + thread: string; user: string; envelope: { @@ -305,3 +307,8 @@ export interface BulkActionRequest { action: BulkActionType; mailbox: string; } + +export interface SingleActionRequest { + messageId: string; + mailbox: string; +} diff --git a/src/pages/spam/List.tsx b/src/pages/spam/List.tsx index 15636f0..8d1b8d5 100644 --- a/src/pages/spam/List.tsx +++ b/src/pages/spam/List.tsx @@ -144,7 +144,7 @@ const List: FC = () => { selectedActions={selectedActions} onSelectionChange={handleSelectionChange} selectedRows={selectedMessages} - onRowClick={(id) => navigate(`/mail/${id}`)} + onRowClick={(id, item) => navigate(`/mail/${id}/${item.mailbox}`)} noDataMessage={
diff --git a/src/pages/spam/hooks/useSpamData.tsx b/src/pages/spam/hooks/useSpamData.tsx index c2140f5..a38969b 100644 --- a/src/pages/spam/hooks/useSpamData.tsx +++ b/src/pages/spam/hooks/useSpamData.tsx @@ -6,6 +6,6 @@ export const useGetSpamMessages = (query: MessageListQueryDto) => { return useQuery({ queryKey: ['spam-messages', query], queryFn: () => getSpamMessages(query), - staleTime: 5 * 60 * 1000, // 5 minutes + staleTime: 0 }); }; \ No newline at end of file