diff --git a/.env b/.env index fb7a36c..c70c7dd 100644 --- a/.env +++ b/.env @@ -1,6 +1,6 @@ -VITE_TOKEN_NAME = 'dsc_token' -VITE_REFRESH_TOKEN_NAME = 'dsc_refresh_token' +VITE_TOKEN_NAME = 'dmail_token' +VITE_REFRESH_TOKEN_NAME = 'dmail_refresh_token' VITE_DANAK_BASE_URL ='https://api.danakcorp.com' -VITE_BASE_URL = 'http://192.168.1.134:4000' -# VITE_BASE_URL = 'https://dmail-api.danakcorp.com' +# VITE_BASE_URL = 'http://192.168.1.134:4000' +VITE_BASE_URL = 'https://dmail-api.danakcorp.com' VITE_SERVICE_ID = 'e51afdc3-ea0b-49cf-8f49-2a6f131b024e' diff --git a/src/App.tsx b/src/App.tsx index 800223f..6b1e830 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import { FC } from 'react' +import { FC, useEffect, useState } from 'react' import './assets/fonts/irancell/style.css' import 'react-quill-new/dist/quill.snow.css'; import 'react-loading-skeleton/dist/skeleton.css' @@ -12,8 +12,10 @@ import NewMessage from './components/newMessage/NewMessage'; import { QueryCache, QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { IApiErrorRepsonse } from './types/error.types'; import ToastContainer from './components/Toast'; -import { getRefreshToken, removeRefreshToken, removeToken, setRefreshToken, setToken } from './config/func'; +import { getRefreshToken, getToken, removeRefreshToken, removeToken, setRefreshToken, setToken } from './config/func'; import { refreshToken } from './pages/auth/service/AuthService'; +import { Paths } from './utils/Paths'; +import Login from './pages/auth/Login'; declare global { interface Window { @@ -96,12 +98,31 @@ const queryClient = new QueryClient({ }); const App: FC = () => { + + const [isLogin, setIsLogin] = useState<'checking' | 'isLogin' | 'isNotLogin'>('checking') + useEffect(() => { + const checkLogin = async () => { + const token = await getToken(); + if (token) { + setIsLogin('isLogin') + } else { + setIsLogin('isNotLogin') + if (window.location.href.split('auth').length === 1) { + window.location.href = Paths.login + } + } + } + checkLogin(); + }, []); + return ( -
+ { + isLogin === 'isLogin' ?
: isLogin === 'isNotLogin' ? : null + } diff --git a/src/components/Toast.tsx b/src/components/Toast.tsx index fe41eb7..6bce2a4 100644 --- a/src/components/Toast.tsx +++ b/src/components/Toast.tsx @@ -36,7 +36,7 @@ const ToastContainer: React.FC = () => { }, []); return ( -
+
{toasts.map((toast) => (
{ const { t } = useTranslation('global') const [value, setValue] = useState(''); const { setOpenNewMessage, openNewMessage } = useSharedStore() + const { email: userEmail } = useAuthStore() const [isClosing, setIsClosing] = useState(false) const [shouldRender, setShouldRender] = useState(false) const [isOpening, setIsOpening] = useState(false) + // Form state + const [to, setTo] = useState('') + const [toEmails, setToEmails] = useState([]) + const [subject, setSubject] = useState('') + const [priority, setPriority] = useState('') + + + // API hooks + const sendEmailMutation = useSendEmail() + const saveDraftMutation = useSaveDraft() + useEffect(() => { if (openNewMessage) { setShouldRender(true) @@ -42,6 +58,108 @@ const NewMessage: FC = () => { setOpenNewMessage(false) }, 300) } + + const parseEmailAddresses = (emails: string[]) => { + return emails + .filter(email => email.length > 0) + .map(email => ({ address: email })) + } + + const isValidEmail = (email: string) => { + const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/ + return emailRegex.test(email) + } + + const handleToKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && to.trim()) { + e.preventDefault() + const email = to.trim() + + if (!isValidEmail(email)) { + toast('لطفا یک ایمیل معتبر وارد کنید', 'error') + return + } + + if (toEmails.includes(email)) { + toast('این ایمیل قبلاً اضافه شده است', 'error') + return + } + + setToEmails([...toEmails, email]) + setTo('') + } + } + + const removeEmail = (emailToRemove: string) => { + setToEmails(toEmails.filter(email => email !== emailToRemove)) + } + + + + const handleSend = async () => { + if (toEmails.length === 0 || !subject) { + toast('لطفا تمام فیلدهای مورد نیاز را پر کنید', 'error') + return + } + + const emailData: SendEmailDto = { + from: { address: userEmail || 'sender@example.com' }, + to: parseEmailAddresses(toEmails), + subject, + html: value, + text: value.replace(/<[^>]*>/g, ''), // Strip HTML for text version + } + + try { + await sendEmailMutation.mutateAsync(emailData) + toast('ایمیل با موفقیت ارسال شد', 'success') + handleClose() + // Reset form + setTo('') + setToEmails([]) + setSubject('') + setValue('') + setPriority('') + } catch { + toast('خطا در ارسال ایمیل', 'error') + } + } + + const handleSaveDraft = async () => { + if (toEmails.length === 0 && !subject && !value) { + toast('حداقل یکی از فیلدها باید پر باشد', 'error') + return + } + + const emailData: SendEmailDto = { + from: { address: userEmail || 'sender@example.com' }, + to: parseEmailAddresses(toEmails), + subject, + html: value, + text: value.replace(/<[^>]*>/g, ''), + isDraft: true, + } + + try { + await saveDraftMutation.mutateAsync(emailData) + toast('پیش‌نویس ذخیره شد', 'success') + handleClose() + // Reset form + setTo('') + setToEmails([]) + setSubject('') + setValue('') + setPriority('') + } catch { + toast('خطا در ذخیره پیش‌نویس', 'error') + } + } + + const priorityOptions = [ + { value: 'high', label: 'بالا' }, + { value: 'medium', label: 'متوسط' }, + { value: 'low', label: 'پایین' }, + ] return ( <> @@ -66,12 +184,44 @@ const NewMessage: FC = () => { {/* Content */}
- +
+ +
+
+ {/* Email Tags */} + {toEmails.map((email, index) => ( +
+ {email} + removeEmail(email)} + /> +
+ ))} + + {/* Input Field */} + setTo(e.target.value)} + onKeyDown={handleToKeyDown} + placeholder={toEmails.length === 0 ? "ایمیل را وارد کرده و Enter بزنید" : ""} + className="flex-1 min-w-32 bg-transparent outline-none" + /> +
+
+
setSubject(e.target.value)} />
@@ -101,19 +251,25 @@ const NewMessage: FC = () => {
+ {formik.touched.email && formik.errors.email && ( + + )} +
+ +
+ + {formik.touched.password && formik.errors.password && ( + + )} +
+
+ +
- -
diff --git a/src/pages/auth/service/AuthService.ts b/src/pages/auth/service/AuthService.ts index e176ba9..bee69db 100644 --- a/src/pages/auth/service/AuthService.ts +++ b/src/pages/auth/service/AuthService.ts @@ -10,7 +10,7 @@ import { } from "../types/AuthTypes"; export const loginWithPassword = async (params: LoginWithPasswordType) => { - const { data } = await axios.post(`/auth/login/password`, params); + const { data } = await axios.post(`/auth/login`, params); return data; }; diff --git a/src/pages/received/Detail.tsx b/src/pages/received/Detail.tsx index 78c3b6a..e520ad8 100644 --- a/src/pages/received/Detail.tsx +++ b/src/pages/received/Detail.tsx @@ -1,6 +1,7 @@ import Button from '@/components/Button' import { FC, useState } from 'react' import { useTranslation } from 'react-i18next' +import { useParams } from 'react-router-dom' import AvatarImage from '@/assets/images/avatar.svg' import { DocumentDownload, EmojiHappy } from 'iconsax-react' import ForwardIcon from '@/assets/images/forward.svg' @@ -8,12 +9,125 @@ import AnswerIcon from '@/assets/images/answer.svg' import Header from './Components/Header' import ReactQuill from 'react-quill-new'; import Select from '@/components/Select' +import { useGetMessageDetail, useSendEmail, useSaveDraft } from './hooks/useEmailData' +import { useAuthStore } from '../auth/store/AuthStore' +import { SendEmailDto } from './types/Types' +import { toast } from '@/components/Toast' +import { formatDate } from '@/config/func' const DetailEmail: FC = () => { const { t } = useTranslation() + const { id } = useParams<{ id: string }>() + const { email: userEmail } = useAuthStore() const [showAnswer, setShowAnswer] = useState(false) const [value, setValue] = useState('') + const [priority, setPriority] = useState('') + + // API hooks + const { data: messageDetail, isLoading, error } = useGetMessageDetail(id || '', !!id) + const sendEmailMutation = useSendEmail() + const saveDraftMutation = useSaveDraft() + + const priorityOptions = [ + { value: 'high', label: 'بالا' }, + { value: 'medium', label: 'متوسط' }, + { value: 'low', label: 'پایین' }, + ] + + const handleSendReply = async () => { + if (!value.trim()) { + toast('لطفا متن پیام را وارد کنید', 'error') + return + } + + if (!messageDetail) { + toast('اطلاعات پیام یافت نشد', 'error') + return + } + + const replyData: SendEmailDto = { + from: { address: userEmail || 'sender@example.com' }, + to: [{ address: messageDetail.from.address, name: messageDetail.from.name }], + subject: `Re: ${messageDetail.subject}`, + html: value, + text: value.replace(/<[^>]*>/g, ''), + reference: { + inReplyTo: messageDetail.messageId, + originalMessageId: messageDetail.id.toString() + } + } + + try { + await sendEmailMutation.mutateAsync(replyData) + toast('پاسخ با موفقیت ارسال شد', 'success') + setShowAnswer(false) + setValue('') + setPriority('') + } catch { + toast('خطا در ارسال پاسخ', 'error') + } + } + + const handleSaveDraft = async () => { + if (!value.trim()) { + toast('متن پیام خالی است', 'error') + return + } + + if (!messageDetail) { + toast('اطلاعات پیام یافت نشد', 'error') + return + } + + const draftData: SendEmailDto = { + from: { address: userEmail || 'sender@example.com' }, + to: [{ address: messageDetail.from.address, name: messageDetail.from.name }], + subject: `Re: ${messageDetail.subject}`, + html: value, + text: value.replace(/<[^>]*>/g, ''), + isDraft: true, + reference: { + inReplyTo: messageDetail.messageId, + originalMessageId: messageDetail.id.toString() + } + } + + try { + await saveDraftMutation.mutateAsync(draftData) + toast('پیش‌نویس ذخیره شد', 'success') + } catch { + toast('خطا در ذخیره پیش‌نویس', 'error') + } + } + + const handleForward = () => { + // TODO: Implement forward functionality + toast('قابلیت ارسال به دیگران به زودی اضافه خواهد شد', 'info') + } + + if (isLoading) { + return ( +
+
+
+
+
+
+
+
+ ) + } + + if (error || !messageDetail) { + return ( +
+
+ خطا در بارگذاری پیام +
+
+ ) + } return (
@@ -23,7 +137,7 @@ const DetailEmail: FC = () => {
{t('mail.subject')}
-
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ
+
{messageDetail.subject || 'بدون موضوع'}
- ۵ اسفندماه ۱۴۰۳، ساعت ۱۲:۰۰ + {formatDate(messageDetail.date)}
-
مهرداد مظفری
+
{messageDetail.from.name || messageDetail.from.address}
-

- لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم است و برای شرایط فعلی تکنولوژی مورد نیاز و کاربردهای متنوع با هدف بهبود ابزارهای کاربردی می باشد کتابهای زیادی در شصت و سه درصد گذشته حال و آیندهلورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم است و برای شرایط فعلی تکنولوژی مورد نیاز و کاربردهای متنوع با هدف بهبود ابزارهای کاربردی می باشد کتابهای زیادی در شصت و سه درصد گذشته حال و آینده -

+
+ {messageDetail.html && messageDetail.html.length > 0 ? ( +
+ ) : ( +

{messageDetail.text || 'محتوای پیام'}

+ )} +
{t('mail.attachments')}
-
- Lorem Ipsum.pdf - -
+ {messageDetail.attachments && messageDetail.attachments.length > 0 ? ( + messageDetail.attachments.map((attachment, index) => ( +
+ {attachment.filename || `attachment-${index + 1}`} + +
+ )) + ) : ( +
+ Lorem Ipsum.pdf + +
+ )}
{ @@ -73,7 +200,7 @@ const DetailEmail: FC = () => {
{t('mail.answer')}
-
+
{t('mail.forward')}
@@ -108,19 +235,25 @@ const DetailEmail: FC = () => {