propmt input in mobile + print email + ltr and rtl new message + najva token
This commit is contained in:
@@ -18,6 +18,7 @@ import { Paths } from './utils/Paths';
|
||||
import Login from './pages/auth/Login';
|
||||
import { EmailWebSocketProvider } from './contexts/EmailWebSocketContext';
|
||||
import SocketManagment from './lib/SocketManagment';
|
||||
import NajvaToken from './components/NajvaToken';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@@ -135,6 +136,7 @@ const App: FC = () => {
|
||||
<EmailWebSocketProvider token={userToken}>
|
||||
<Main />
|
||||
<SocketManagment />
|
||||
<NajvaToken />
|
||||
</EmailWebSocketProvider>
|
||||
) : isLogin === 'isNotLogin' ? (
|
||||
<Login />
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { FC, useEffect } from 'react'
|
||||
import { updateProfile } from '../pages/profile/service/ProfileService'
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
NAJVA: {
|
||||
getUserToken: () => Promise<string>
|
||||
najvaUserSubscribed: (najvaUserToken: string) => void
|
||||
}
|
||||
}
|
||||
}
|
||||
const NajvaToken: FC = () => {
|
||||
|
||||
const handleNajvaToken = async () => {
|
||||
const userToken = await window.NAJVA?.getUserToken()
|
||||
if (userToken) {
|
||||
updateProfile({
|
||||
pushToken: userToken,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
handleNajvaToken()
|
||||
// window.NAJVA.najvaUserSubscribed = function (najvaUserToken) {
|
||||
// console.log('najvaUserToken', najvaUserToken)
|
||||
// }
|
||||
}, [])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export default NajvaToken
|
||||
@@ -1,5 +1,6 @@
|
||||
import { FC } from 'react'
|
||||
import { FC, useMemo } from 'react'
|
||||
import ReactQuill from 'react-quill-new'
|
||||
import 'react-quill-new/dist/quill.snow.css'
|
||||
|
||||
interface EmailEditorProps {
|
||||
value: string
|
||||
@@ -10,19 +11,85 @@ interface EmailEditorProps {
|
||||
const EmailEditor: FC<EmailEditorProps> = ({
|
||||
value,
|
||||
onChange,
|
||||
placeholder = ''
|
||||
placeholder = 'متن پیام خود را وارد کنید...'
|
||||
}) => {
|
||||
const modules = useMemo(() => ({
|
||||
toolbar: [
|
||||
['bold', 'italic', 'underline'],
|
||||
[{ 'list': 'ordered' }, { 'list': 'bullet' }],
|
||||
[{ 'align': [] }],
|
||||
[{ 'direction': 'rtl' }],
|
||||
['link'],
|
||||
[{ 'color': [] }, { 'background': [] }],
|
||||
[{ 'font': [] }, { 'size': ['small', false, 'large', 'huge'] }],
|
||||
['clean']
|
||||
],
|
||||
}), [])
|
||||
|
||||
const formats = [
|
||||
'bold', 'italic', 'underline',
|
||||
'list', 'bullet',
|
||||
'align', 'direction',
|
||||
'link',
|
||||
'color', 'background',
|
||||
'font', 'size'
|
||||
]
|
||||
|
||||
return (
|
||||
<div dir="rtl" className="text-right">
|
||||
<style>{`
|
||||
.ql-editor {
|
||||
direction: rtl;
|
||||
text-align: start;
|
||||
unicode-bidi: plaintext;
|
||||
font-family: 'Irancell', 'Vazirmatn', sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
min-height: 120px;
|
||||
}
|
||||
.ql-editor p {
|
||||
unicode-bidi: plaintext;
|
||||
text-align: start;
|
||||
direction: auto;
|
||||
}
|
||||
.ql-editor.ql-blank::before {
|
||||
direction: rtl;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
color: #9ca3af;
|
||||
}
|
||||
.ql-toolbar {
|
||||
border-top: 1px solid #e5e7eb;
|
||||
border-left: 1px solid #e5e7eb;
|
||||
border-right: 1px solid #e5e7eb;
|
||||
border-radius: 8px 8px 0 0;
|
||||
}
|
||||
.ql-container {
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
border-left: 1px solid #e5e7eb;
|
||||
border-right: 1px solid #e5e7eb;
|
||||
border-radius: 0 0 8px 8px;
|
||||
}
|
||||
.ql-snow .ql-picker-label {
|
||||
color: #374151;
|
||||
}
|
||||
.ql-snow .ql-stroke {
|
||||
stroke: #6b7280;
|
||||
}
|
||||
.ql-snow .ql-fill {
|
||||
fill: #6b7280;
|
||||
}
|
||||
`}</style>
|
||||
<ReactQuill
|
||||
theme="snow"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder={placeholder}
|
||||
style={{ minHeight: '120px' }}
|
||||
className='text-sm'
|
||||
modules={modules}
|
||||
formats={formats}
|
||||
className="bg-white rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -146,7 +146,7 @@ const NewMessage: FC = () => {
|
||||
</div>
|
||||
|
||||
<div className='flex gap-2 items-end'>
|
||||
<div className='flex flex-1 items-end gap-2'>
|
||||
<div className='flex lg:flex-row flex-col flex-1 items-end gap-2'>
|
||||
<Input
|
||||
label={t('new_message.subject')}
|
||||
value={subject}
|
||||
|
||||
@@ -146,7 +146,9 @@ export const useNewMessage = () => {
|
||||
const draftUpdateData = {
|
||||
to: parseEmailAddresses(toEmails),
|
||||
subject,
|
||||
html: ensureString(content),
|
||||
html: `<div dir="auto" style="text-align: start; unicode-bidi: plaintext; font-family: 'Irancell', 'Vazirmatn', sans-serif;">${ensureString(
|
||||
content
|
||||
)}</div>`,
|
||||
text: ensureString(content).replace(/<[^>]*>/g, ""),
|
||||
...(attachments.length > 0 && { attachments }),
|
||||
};
|
||||
@@ -170,6 +172,9 @@ export const useNewMessage = () => {
|
||||
messageId: editingDraftId.toString(),
|
||||
draftData: {
|
||||
...draftUpdateData,
|
||||
html: `<div dir="auto" style="text-align: start; unicode-bidi: plaintext; font-family: 'Irancell', 'Vazirmatn', sans-serif;">${ensureString(
|
||||
content
|
||||
)}</div>`,
|
||||
isDraft: true,
|
||||
uploadOnly: true,
|
||||
},
|
||||
@@ -195,7 +200,9 @@ export const useNewMessage = () => {
|
||||
from: { address: userEmail || "sender@example.com" },
|
||||
to: parseEmailAddresses(toEmails),
|
||||
subject,
|
||||
html: ensureString(content),
|
||||
html: `<div dir="auto" style="text-align: start; unicode-bidi: plaintext; font-family: 'Irancell', 'Vazirmatn', sans-serif;">${ensureString(
|
||||
content
|
||||
)}</div>`,
|
||||
text: ensureString(content).replace(/<[^>]*>/g, ""),
|
||||
...(attachments.length > 0 && { attachments }),
|
||||
...(isDraft && { isDraft: true, uploadOnly: true }),
|
||||
|
||||
@@ -40,7 +40,7 @@ export const useEmailWebSocket = ({
|
||||
});
|
||||
|
||||
newSocket.on("connection_established", (data) => {
|
||||
console.log("🎉 اتصال WebSocket ایمیل تأیید شد:", data);
|
||||
console.log("🎉 اتصال We bSocket ایمیل تأیید شد:", data);
|
||||
});
|
||||
|
||||
newSocket.on("connection_error", (error) => {
|
||||
|
||||
+7
-1
@@ -238,7 +238,13 @@ textarea::placeholder {
|
||||
@apply rounded-t-xl;
|
||||
}
|
||||
.ql-editor {
|
||||
@apply !text-right;
|
||||
text-align: start !important;
|
||||
unicode-bidi: plaintext !important;
|
||||
}
|
||||
.ql-editor p {
|
||||
unicode-bidi: plaintext !important;
|
||||
text-align: start !important;
|
||||
direction: auto !important;
|
||||
}
|
||||
|
||||
.dltr {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import './index.css'
|
||||
import './assets/fonts/irancell/style.css'
|
||||
import App from './App.tsx'
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
|
||||
@@ -6,8 +6,6 @@ import { MessageListQueryDto, TrashResponse } from "../types/TrashTypes";
|
||||
export const getTrashMessages = async (
|
||||
query: MessageListQueryDto
|
||||
): Promise<TrashResponse> => {
|
||||
console.log("query", query);
|
||||
|
||||
const isSearch =
|
||||
!!query.search || !!query.datestart || query.dateend || query.from;
|
||||
const url = isSearch ? "search" : "messages/trash";
|
||||
|
||||
@@ -5,8 +5,6 @@ import { MessageListQueryDto, ArchiveResponse } from "../types/ArchiveTypes";
|
||||
export const getArchiveMessages = async (
|
||||
query: MessageListQueryDto
|
||||
): Promise<ArchiveResponse> => {
|
||||
console.log("query", query);
|
||||
|
||||
const isSearch =
|
||||
!!query.search || !!query.datestart || query.dateend || query.from;
|
||||
const url = isSearch ? "search" : "messages/archive";
|
||||
|
||||
@@ -10,8 +10,6 @@ import {
|
||||
export const getDrafts = async (
|
||||
query: MessageListQueryDto
|
||||
): Promise<InboxResponse> => {
|
||||
console.log("query", query);
|
||||
|
||||
const isSearch =
|
||||
!!query.search || !!query.datestart || query.dateend || query.from;
|
||||
const url = isSearch ? "search" : "messages/drafts";
|
||||
|
||||
@@ -5,8 +5,6 @@ import { MessageListQueryDto, FavoriteResponse } from "../types/FavoriteTypes";
|
||||
export const getFavoriteMessages = async (
|
||||
query: MessageListQueryDto
|
||||
): Promise<FavoriteResponse> => {
|
||||
console.log("query", query);
|
||||
|
||||
const isSearch =
|
||||
!!query.search || !!query.datestart || query.dateend || query.from;
|
||||
const url = isSearch ? "search" : "messages/favorite";
|
||||
|
||||
@@ -12,6 +12,7 @@ export type UpdateProfileType = {
|
||||
cityId?: string;
|
||||
postalCode?: string;
|
||||
userAddress?: string;
|
||||
pushToken?: string;
|
||||
};
|
||||
|
||||
export type UpdateEmailType = {
|
||||
|
||||
@@ -9,12 +9,89 @@ import MessageSpam from './MessageSpam'
|
||||
import { MailboxEnum } from '../enum/Enum'
|
||||
import MessageFavorite from './MessageFavorite'
|
||||
import MessageTrash from './MessageTrash'
|
||||
import { MessageDetail } from '../types/Types'
|
||||
import { sanitizeEmailHTML, detectTextDirection } from '@/config/func'
|
||||
|
||||
const Header: FC<{ mailBoxName: MailboxEnum, flagged: boolean }> = ({ mailBoxName, flagged }) => {
|
||||
const Header: FC<{
|
||||
mailBoxName: MailboxEnum,
|
||||
flagged: boolean,
|
||||
messageDetail?: MessageDetail
|
||||
}> = ({ mailBoxName, flagged, messageDetail }) => {
|
||||
|
||||
const navigate = useNavigate()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const handlePrint = () => {
|
||||
if (!messageDetail) return
|
||||
|
||||
// Create a new window for printing
|
||||
const printWindow = window.open('', '_blank', 'width=800,height=600')
|
||||
if (!printWindow) return
|
||||
|
||||
// Get the email content
|
||||
let content = ''
|
||||
if (messageDetail.html && messageDetail.html.length > 0) {
|
||||
content = sanitizeEmailHTML(messageDetail.html.join(''))
|
||||
} else {
|
||||
content = messageDetail.text || 'محتوای پیام'
|
||||
}
|
||||
|
||||
// Detect text direction
|
||||
const direction = detectTextDirection(content)
|
||||
|
||||
// Create print content
|
||||
const printContent = `
|
||||
<!DOCTYPE html>
|
||||
<html dir="${direction}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>پرینت ایمیل</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
margin: 20px;
|
||||
direction: ${direction};
|
||||
}
|
||||
.email-header {
|
||||
border-bottom: 2px solid #333;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.email-content {
|
||||
font-size: 14px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
@media print {
|
||||
body { margin: 0; }
|
||||
.no-print { display: none; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="email-header">
|
||||
<h2>موضوع: ${messageDetail.subject || 'بدون موضوع'}</h2>
|
||||
<p><strong>فرستنده:</strong> ${messageDetail.from.name || messageDetail.from.address} <${messageDetail.from.address}></p>
|
||||
<p><strong>تاریخ:</strong> ${new Date(messageDetail.date).toLocaleDateString('fa-IR')}</p>
|
||||
</div>
|
||||
<div class="email-content">
|
||||
${content}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
|
||||
// Write content and print
|
||||
printWindow.document.write(printContent)
|
||||
printWindow.document.close()
|
||||
|
||||
// Wait for content to load then print
|
||||
printWindow.onload = () => {
|
||||
printWindow.print()
|
||||
printWindow.close()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex justify-between items-center border-b border-border pb-6'>
|
||||
<div className='flex flex-1 lg:gap-4'>
|
||||
@@ -42,6 +119,7 @@ const Header: FC<{ mailBoxName: MailboxEnum, flagged: boolean }> = ({ mailBoxNam
|
||||
</Button>
|
||||
<Button
|
||||
variant='secondary'
|
||||
onClick={handlePrint}
|
||||
>
|
||||
<div className='flex gap-2 items-center xl:px-5'>
|
||||
<Printer size={20} color='black' />
|
||||
|
||||
@@ -141,7 +141,11 @@ const DetailEmail: FC = () => {
|
||||
|
||||
return (
|
||||
<div className='bg-white rounded-4xl p-8'>
|
||||
<Header flagged={messageDetail?.flagged || false} mailBoxName={messageDetail?.mailboxName as MailboxEnum} />
|
||||
<Header
|
||||
flagged={messageDetail?.flagged || false}
|
||||
mailBoxName={messageDetail?.mailboxName as MailboxEnum}
|
||||
messageDetail={messageDetail}
|
||||
/>
|
||||
|
||||
<div className='mt-6 flex xl:flex-row flex-col justify-between xl:items-center'>
|
||||
<div className='flex xl:flex-row flex-col-reverse gap-4 xl:items-center'>
|
||||
|
||||
@@ -206,24 +206,12 @@ const List: FC = () => {
|
||||
};
|
||||
|
||||
const messages = inboxData?.data?.results || [];
|
||||
const pager = inboxData?.data?.pager;
|
||||
const rootData = inboxData?.data as unknown as Record<string, unknown>;
|
||||
|
||||
// Debug pagination data
|
||||
console.log('Full response data:', inboxData?.data);
|
||||
console.log('Pager data:', pager);
|
||||
console.log('Root nextCursor:', rootData?.nextCursor);
|
||||
console.log('Root previousCursor:', rootData?.previousCursor);
|
||||
|
||||
// Get cursors from response
|
||||
const responseNextCursor = (typeof rootData?.nextCursor === 'string' && rootData.nextCursor !== '') ? rootData.nextCursor as string : undefined;
|
||||
const responsePreviousCursor = (typeof rootData?.previousCursor === 'string' && rootData.previousCursor !== '') ? rootData.previousCursor as string : undefined;
|
||||
|
||||
// Debug pagination values
|
||||
console.log('responseNextCursor:', responseNextCursor);
|
||||
console.log('responsePreviousCursor:', responsePreviousCursor);
|
||||
console.log('Will render pagination?', !!(responseNextCursor || responsePreviousCursor));
|
||||
|
||||
return (
|
||||
<div className='mt-2 md:mt-4 px-2 md:px-0'>
|
||||
<h1 className="text-lg mb-4 md:mb-0">{t('received.title')}</h1>
|
||||
|
||||
@@ -36,8 +36,6 @@ export const getDraft = async (
|
||||
export const getInbox = async (
|
||||
query: MessageListQueryDto
|
||||
): Promise<InboxResponse> => {
|
||||
console.log("query", query);
|
||||
|
||||
const isSearch =
|
||||
!!query.search ||
|
||||
!!query.datestart ||
|
||||
|
||||
@@ -10,8 +10,6 @@ import {
|
||||
export const getSentMessages = async (
|
||||
query: MessageListQueryDto
|
||||
): Promise<InboxResponse> => {
|
||||
console.log("query", query);
|
||||
|
||||
const isSearch =
|
||||
!!query.search || !!query.datestart || query.dateend || query.from;
|
||||
const url = isSearch ? "search" : "messages/sent";
|
||||
|
||||
@@ -6,8 +6,6 @@ import { MailboxEnum } from "@/pages/received/enum/Enum";
|
||||
export const getSpamMessages = async (
|
||||
query: MessageListQueryDto
|
||||
): Promise<SpamResponse> => {
|
||||
console.log("query", query);
|
||||
|
||||
const isSearch =
|
||||
!!query.search || !!query.datestart || query.dateend || query.from;
|
||||
const url = isSearch ? "search" : "messages/junk";
|
||||
|
||||
Reference in New Issue
Block a user