remove comments
This commit is contained in:
+1
-9
@@ -40,7 +40,6 @@ i18next.init({
|
||||
const queryClient = new QueryClient({
|
||||
queryCache: new QueryCache({
|
||||
onError: async (error: IApiErrorRepsonse) => {
|
||||
// نمایش پیغام خطا از API
|
||||
const errorMessage = error?.response?.error?.message?.[0] || undefined;
|
||||
|
||||
if (errorMessage) {
|
||||
@@ -49,9 +48,8 @@ const queryClient = new QueryClient({
|
||||
|
||||
if (error?.response?.status === 401) {
|
||||
try {
|
||||
// Use a flag to track if refresh is in progress
|
||||
|
||||
if (window.isRefreshingToken) {
|
||||
// Wait for the refresh to complete
|
||||
await new Promise(resolve => {
|
||||
const checkComplete = setInterval(() => {
|
||||
if (!window.isRefreshingToken) {
|
||||
@@ -63,7 +61,6 @@ const queryClient = new QueryClient({
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the flag to indicate refresh is in progress
|
||||
window.isRefreshingToken = true;
|
||||
|
||||
try {
|
||||
@@ -71,15 +68,12 @@ const queryClient = new QueryClient({
|
||||
const { data } = await refreshToken({ refreshToken: refreshTokenValue || '' });
|
||||
|
||||
if (data?.accessToken?.token) {
|
||||
// Save the new token
|
||||
await setToken(data?.accessToken?.token);
|
||||
|
||||
// If refresh token also returned, update it
|
||||
if (data.refreshToken?.token) {
|
||||
await setRefreshToken(data.refreshToken?.token);
|
||||
}
|
||||
|
||||
// Retry the failed request
|
||||
queryClient.invalidateQueries();
|
||||
return;
|
||||
} else {
|
||||
@@ -90,7 +84,6 @@ const queryClient = new QueryClient({
|
||||
}
|
||||
} catch (refreshError: unknown) {
|
||||
console.error('Token refresh failed:', refreshError);
|
||||
// Clear tokens and redirect to login
|
||||
await removeToken();
|
||||
await removeRefreshToken();
|
||||
window.location.href = '/auth/login';
|
||||
@@ -102,7 +95,6 @@ const queryClient = new QueryClient({
|
||||
queries: {
|
||||
refetchOnWindowFocus: false,
|
||||
retry: false
|
||||
// staleTime: 86400000 // 1 day in milliseconds
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -23,11 +23,9 @@ export const EmailNotifications: React.FC<EmailNotificationsProps> = ({
|
||||
onError: () => null,
|
||||
});
|
||||
|
||||
// Sound settings state
|
||||
const [soundEnabled, setSoundEnabled] = useState(true);
|
||||
const [soundVolume, setSoundVolume] = useState(0.7);
|
||||
|
||||
// Load sound settings from localStorage
|
||||
useEffect(() => {
|
||||
const savedEnabled = localStorage.getItem('notificationSoundEnabled');
|
||||
const savedVolume = localStorage.getItem('notificationSoundVolume');
|
||||
@@ -40,26 +38,21 @@ export const EmailNotifications: React.FC<EmailNotificationsProps> = ({
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Notification sound hook
|
||||
const { playSound } = useNotificationSound({
|
||||
enabled: soundEnabled,
|
||||
volume: soundVolume,
|
||||
});
|
||||
|
||||
const handleNewEmail = useCallback((email: EmailPayload) => {
|
||||
setNotifications(prev => [email, ...prev.slice(0, 9)]);
|
||||
|
||||
// Add to notifications list
|
||||
setNotifications(prev => [email, ...prev.slice(0, 9)]); // Keep only 10 latest
|
||||
|
||||
// Show browser notification if permission granted
|
||||
if (Notification.permission === 'granted') {
|
||||
new Notification(`📧 ایمیل جدید: ${email.subject}`, {
|
||||
body: `از: ${email.from.name || email.from.address}\n${email.preview}`,
|
||||
tag: `email-${email.messageId}`, // Prevent duplicates
|
||||
tag: `email-${email.messageId}`,
|
||||
});
|
||||
}
|
||||
|
||||
// Play notification sound
|
||||
playSound();
|
||||
}, [playSound]);
|
||||
|
||||
@@ -68,14 +61,12 @@ export const EmailNotifications: React.FC<EmailNotificationsProps> = ({
|
||||
}, []);
|
||||
|
||||
const handleEmailRead = useCallback((data: { messageId: number }) => {
|
||||
// Remove from notifications if it was there
|
||||
setNotifications(prev =>
|
||||
prev.filter(notification => notification.messageId !== data.messageId)
|
||||
);
|
||||
}, []);
|
||||
|
||||
const handleEmailSent = useCallback((data: EmailStatusPayload) => {
|
||||
// Remove from notifications if it was there
|
||||
setNotifications(prev =>
|
||||
prev.filter(notification => notification.messageId !== data.messageId)
|
||||
);
|
||||
@@ -88,7 +79,6 @@ export const EmailNotifications: React.FC<EmailNotificationsProps> = ({
|
||||
onEmailSent: handleEmailSent,
|
||||
});
|
||||
|
||||
// Request notification permission on mount
|
||||
React.useEffect(() => {
|
||||
if (Notification.permission === 'default') {
|
||||
Notification.requestPermission();
|
||||
@@ -97,11 +87,9 @@ export const EmailNotifications: React.FC<EmailNotificationsProps> = ({
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
{/* Connection Status */}
|
||||
<div className={`absolute -top-2 -right-2 w-3 h-3 rounded-full ${isConnected ? 'bg-green-500' : 'bg-red-500'
|
||||
}`}></div>
|
||||
|
||||
{/* Test Sound Button */}
|
||||
<button
|
||||
onClick={() => {
|
||||
playSound();
|
||||
@@ -112,7 +100,6 @@ export const EmailNotifications: React.FC<EmailNotificationsProps> = ({
|
||||
<VolumeHigh size={20} color="black" />
|
||||
</button>
|
||||
|
||||
{/* Notification Bell */}
|
||||
<div
|
||||
className="relative cursor-pointer p-2 rounded-full hover:bg-gray-100 transition-colors"
|
||||
onClick={() => setIsVisible(!isVisible)}
|
||||
@@ -125,7 +112,6 @@ export const EmailNotifications: React.FC<EmailNotificationsProps> = ({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Notifications Dropdown */}
|
||||
{isVisible && (
|
||||
<div className="absolute top-full left-0 w-80 max-h-96 bg-white border border-gray-200 rounded-lg shadow-lg z-50 overflow-hidden">
|
||||
<div className="p-3 bg-gray-50 border-b border-gray-200 flex justify-between items-center">
|
||||
|
||||
@@ -11,7 +11,6 @@ interface EmojiReactionProps {
|
||||
const EmojiReaction: FC<EmojiReactionProps> = ({ onEmojiSelect, className = '' }) => {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
// استفاده از hook موجود برای تشخیص کلیک خارج از المان
|
||||
const containerRef = useOutsideClick(() => {
|
||||
if (isOpen) {
|
||||
setIsOpen(false)
|
||||
|
||||
@@ -7,7 +7,6 @@ import DefaulModal from './DefaulModal';
|
||||
import moment from 'moment-jalaali'
|
||||
import Button from './Button';
|
||||
|
||||
// تعریف نوع داده برای فیلدهای مختلف
|
||||
export type DateFieldType = {
|
||||
type: 'date';
|
||||
name: string;
|
||||
@@ -32,7 +31,6 @@ export type InputFieldType = {
|
||||
|
||||
export type FieldType = DateFieldType | SelectFieldType | InputFieldType;
|
||||
|
||||
// تعریف نوع داده برای مقادیر فیلترها
|
||||
export type FilterValues = Record<string, string | null>;
|
||||
|
||||
interface FiltersProps {
|
||||
@@ -41,7 +39,7 @@ interface FiltersProps {
|
||||
initialValues?: FilterValues;
|
||||
className?: string;
|
||||
fieldClassName?: string;
|
||||
searchField?: string; // نام فیلد جستجو که باید در انتها نمایش داده شود
|
||||
searchField?: string;
|
||||
}
|
||||
|
||||
const Filters: FC<FiltersProps> = ({
|
||||
@@ -50,23 +48,20 @@ const Filters: FC<FiltersProps> = ({
|
||||
initialValues = {},
|
||||
className = "mt-6 md:mt-8 xl:mt-10 flex flex-col md:flex-row justify-between items-start md:items-center gap-4",
|
||||
fieldClassName = "flex flex-col sm:flex-row gap-3 md:gap-4 w-full md:w-auto",
|
||||
searchField = "search" // پیشفرض فیلد سرچ با نام search
|
||||
searchField = "search"
|
||||
}) => {
|
||||
const [filters, setFilters] = useState<FilterValues>({});
|
||||
const [isFiltersOpen, setIsFiltersOpen] = useState(false);
|
||||
const [inputValues, setInputValues] = useState<Record<string, string>>({});
|
||||
const onChangeRef = useRef(onChange);
|
||||
|
||||
// آپدیت کردن ref وقتی onChange تغییر میکند
|
||||
useEffect(() => {
|
||||
onChangeRef.current = onChange;
|
||||
}, [onChange]);
|
||||
|
||||
// تنظیم مقادیر اولیه
|
||||
useEffect(() => {
|
||||
if (Object.keys(initialValues).length > 0) {
|
||||
setFilters(initialValues);
|
||||
// تنظیم مقادیر input برای فیلدهای نوع input
|
||||
const inputFields: Record<string, string> = {};
|
||||
fields.forEach(field => {
|
||||
if (field.type === 'input' && initialValues[field.name]) {
|
||||
@@ -75,7 +70,6 @@ const Filters: FC<FiltersProps> = ({
|
||||
});
|
||||
setInputValues(inputFields);
|
||||
} else {
|
||||
// تنظیم مقادیر پیشفرض از فیلدها
|
||||
const defaultValues: FilterValues = {};
|
||||
const defaultInputs: Record<string, string> = {};
|
||||
fields.forEach(field => {
|
||||
@@ -95,7 +89,6 @@ const Filters: FC<FiltersProps> = ({
|
||||
}
|
||||
}, [fields, initialValues, onChange]);
|
||||
|
||||
// Debounce برای فیلدهای input
|
||||
useEffect(() => {
|
||||
const timeouts: Record<string, number> = {};
|
||||
|
||||
@@ -170,13 +163,11 @@ const Filters: FC<FiltersProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
// جداسازی فیلد جستجو از سایر فیلدها
|
||||
const searchFieldObj = fields.find(field => field.name === searchField);
|
||||
const otherFields = fields.filter(field => field.name !== searchField);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{/* آیکون فیلتر برای موبایل */}
|
||||
<div className="flex md:hidden items-center justify-between w-full mb-2">
|
||||
<button
|
||||
onClick={() => setIsFiltersOpen(!isFiltersOpen)}
|
||||
@@ -192,7 +183,6 @@ const Filters: FC<FiltersProps> = ({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* فیلترها برای دسکتاپ (همیشه نمایش داده میشوند) */}
|
||||
<div className="hidden md:flex md:flex-row justify-between items-center gap-4 w-full">
|
||||
<div className={fieldClassName}>
|
||||
{otherFields.map(renderField)}
|
||||
@@ -204,7 +194,6 @@ const Filters: FC<FiltersProps> = ({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* فیلترها برای موبایل (در قالب مودال) */}
|
||||
<DefaulModal
|
||||
open={isFiltersOpen}
|
||||
close={() => setIsFiltersOpen(false)}
|
||||
|
||||
@@ -42,13 +42,11 @@ const Input: FC<Props> = (props: Props) => {
|
||||
|
||||
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (props.seprator) {
|
||||
const inputValue = event.target.value.replace(/,/g, ''); // حذف کاماها
|
||||
const inputValue = event.target.value.replace(/,/g, '');
|
||||
const formatted = formatNumber(inputValue);
|
||||
|
||||
// بهروزرسانی مقدار قالببندیشده
|
||||
setFormattedValue(formatted);
|
||||
|
||||
// ارسال مقدار خام به `onChange` والد
|
||||
props.onChange?.({
|
||||
...event,
|
||||
target: {
|
||||
|
||||
@@ -16,7 +16,6 @@ const Pagination: React.FC<PaginationProps> = ({
|
||||
onNext,
|
||||
onPrevious,
|
||||
}) => {
|
||||
// اگر hasNext فالس باشد، pagination نمایش داده نمیشود
|
||||
if (!hasNext && !previousCursor) return null;
|
||||
|
||||
return (
|
||||
|
||||
@@ -40,22 +40,19 @@ const RowActionsDropdown: React.FC<RowActionsDropdownProps> = ({ actions, classN
|
||||
const rect = buttonRef.current.getBoundingClientRect();
|
||||
const isMobile = window.innerWidth < 768;
|
||||
const dropdownWidth = isMobile ? 140 : 150;
|
||||
const dropdownHeight = actions.length * (isMobile ? 36 : 40); // تخمین ارتفاع منو
|
||||
const dropdownHeight = actions.length * (isMobile ? 36 : 40);
|
||||
|
||||
let left = rect.left + window.scrollX - dropdownWidth + rect.width;
|
||||
let top = rect.bottom + window.scrollY;
|
||||
|
||||
// بررسی اینکه منو از سمت راست خارج نشود
|
||||
if (left + dropdownWidth > window.innerWidth) {
|
||||
left = rect.left + window.scrollX - dropdownWidth;
|
||||
}
|
||||
|
||||
// بررسی اینکه منو از سمت چپ خارج نشود
|
||||
if (left < 0) {
|
||||
left = isMobile ? 20 : 40;
|
||||
}
|
||||
|
||||
// بررسی اینکه منو از پایین خارج نشود
|
||||
if (top + dropdownHeight > window.innerHeight + window.scrollY) {
|
||||
top = rect.top + window.scrollY - dropdownHeight;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ export const SoundSettings: React.FC<SoundSettingsProps> = ({ className = '' })
|
||||
volume: volume,
|
||||
});
|
||||
|
||||
// Load settings from localStorage on mount
|
||||
useEffect(() => {
|
||||
const savedEnabled = localStorage.getItem('notificationSoundEnabled');
|
||||
const savedVolume = localStorage.getItem('notificationSoundVolume');
|
||||
@@ -28,7 +27,6 @@ export const SoundSettings: React.FC<SoundSettingsProps> = ({ className = '' })
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Save settings to localStorage when changed
|
||||
useEffect(() => {
|
||||
localStorage.setItem('notificationSoundEnabled', isEnabled.toString());
|
||||
localStorage.setItem('notificationSoundVolume', volume.toString());
|
||||
@@ -37,7 +35,6 @@ export const SoundSettings: React.FC<SoundSettingsProps> = ({ className = '' })
|
||||
const handleVolumeChange = (newVolume: number) => {
|
||||
setVolume(newVolume);
|
||||
if (isEnabled) {
|
||||
// Test sound with new volume
|
||||
setTimeout(() => playSound(), 100);
|
||||
}
|
||||
};
|
||||
@@ -46,7 +43,6 @@ export const SoundSettings: React.FC<SoundSettingsProps> = ({ className = '' })
|
||||
const newEnabled = !isEnabled;
|
||||
setIsEnabled(newEnabled);
|
||||
if (newEnabled) {
|
||||
// Test sound when enabling
|
||||
setTimeout(() => playSound(), 100);
|
||||
}
|
||||
};
|
||||
@@ -59,7 +55,6 @@ export const SoundSettings: React.FC<SoundSettingsProps> = ({ className = '' })
|
||||
|
||||
return (
|
||||
<div className={`flex items-center gap-3 ${className}`}>
|
||||
{/* Sound Toggle */}
|
||||
<button
|
||||
onClick={toggleSound}
|
||||
className="p-2 rounded-lg hover:bg-gray-100 transition-colors"
|
||||
@@ -68,7 +63,6 @@ export const SoundSettings: React.FC<SoundSettingsProps> = ({ className = '' })
|
||||
{getVolumeIcon()}
|
||||
</button>
|
||||
|
||||
{/* Volume Slider */}
|
||||
{isEnabled && (
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
|
||||
@@ -35,21 +35,17 @@ const Table = <T extends RowDataType>({
|
||||
const longPressTimer = useRef<number | null>(null);
|
||||
const [longPressActiveId, setLongPressActiveId] = useState<string | number | null>(null);
|
||||
|
||||
// استفاده از selectedRows خارجی اگر موجود باشد، در غیر این صورت از internal استفاده کن
|
||||
const selectedRows = externalSelectedRows !== undefined ? externalSelectedRows : internalSelectedRows;
|
||||
|
||||
// پاک کردن انتخابها وقتی clearSelection تغییر کند
|
||||
React.useEffect(() => {
|
||||
if (clearSelection && externalSelectedRows === undefined) {
|
||||
setInternalSelectedRows([]);
|
||||
}
|
||||
// خروج از حالت انتخاب موبایل هم
|
||||
if (clearSelection) {
|
||||
setMobileSelectionMode(false);
|
||||
}
|
||||
}, [clearSelection, externalSelectedRows]);
|
||||
|
||||
// تمیز کردن timer هنگام unmount
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
if (longPressTimer.current) {
|
||||
@@ -58,7 +54,6 @@ const Table = <T extends RowDataType>({
|
||||
};
|
||||
}, []);
|
||||
|
||||
// خروج از حالت انتخاب اگر هیچ آیتمی انتخاب نشده، ورود اگر آیتمی انتخاب شده
|
||||
React.useEffect(() => {
|
||||
if (selectedRows.length === 0 && mobileSelectionMode) {
|
||||
setMobileSelectionMode(false);
|
||||
@@ -73,10 +68,9 @@ const Table = <T extends RowDataType>({
|
||||
setLongPressActiveId(item.id);
|
||||
longPressTimer.current = window.setTimeout(() => {
|
||||
setMobileSelectionMode(true);
|
||||
// انتخاب آیتم فعلی
|
||||
handleSingleRowSelect(item, true);
|
||||
setLongPressActiveId(null);
|
||||
}, 500); // 500ms برای long press
|
||||
}, 500);
|
||||
};
|
||||
|
||||
const handleLongPressEnd = () => {
|
||||
@@ -89,11 +83,9 @@ const Table = <T extends RowDataType>({
|
||||
|
||||
const handleMobileRowClick = (item: T) => {
|
||||
if (mobileSelectionMode) {
|
||||
// در حالت انتخاب، آیتم را انتخاب/عدم انتخاب کن
|
||||
const isSelected = isRowSelected(item);
|
||||
handleSingleRowSelect(item, !isSelected);
|
||||
} else if (longPressActiveId !== item.id) {
|
||||
// در حالت عادی، row click عادی را اجرا کن
|
||||
handleRowClick(item);
|
||||
}
|
||||
};
|
||||
@@ -120,12 +112,10 @@ const Table = <T extends RowDataType>({
|
||||
const newSelectedRows = selectedRows.length === data.length ? [] : [...data];
|
||||
|
||||
if (externalSelectedRows !== undefined) {
|
||||
// اگر selectedRows از خارج کنترل میشود، فقط callback را فراخوانی کن
|
||||
if (onSelectionChange) {
|
||||
onSelectionChange(newSelectedRows);
|
||||
}
|
||||
} else {
|
||||
// در غیر این صورت internal state را بهروزرسانی کن
|
||||
setInternalSelectedRows(newSelectedRows);
|
||||
if (onSelectionChange) {
|
||||
onSelectionChange(newSelectedRows);
|
||||
@@ -139,12 +129,10 @@ const Table = <T extends RowDataType>({
|
||||
: selectedRows.filter((row) => row.id !== item.id);
|
||||
|
||||
if (externalSelectedRows !== undefined) {
|
||||
// اگر selectedRows از خارج کنترل میشود، فقط callback را فراخوانی کن
|
||||
if (onSelectionChange) {
|
||||
onSelectionChange(newSelectedRows);
|
||||
}
|
||||
} else {
|
||||
// در غیر این صورت internal state را بهروزرسانی کن
|
||||
setInternalSelectedRows(newSelectedRows);
|
||||
if (onSelectionChange) {
|
||||
onSelectionChange(newSelectedRows);
|
||||
@@ -156,7 +144,6 @@ const Table = <T extends RowDataType>({
|
||||
return selectedRows.some((row) => row.id === item.id);
|
||||
};
|
||||
|
||||
// Gmail-style layout برای وقتی header نمایش داده نمیشود
|
||||
const renderGmailStyleRows = () => {
|
||||
const hasActions = (actionsPosition === 'top' || actionsPosition === 'above-header') && (actions || (selectable && selectedRows.length > 0 && selectedActions));
|
||||
const roundedClass = hasActions ? 'rounded-b-2xl' : 'rounded-2xl';
|
||||
@@ -183,7 +170,6 @@ const Table = <T extends RowDataType>({
|
||||
return (
|
||||
<div className={`bg-white ${roundedClass} overflow-hidden`}>
|
||||
{data.map((item, rowIndex) => {
|
||||
// Gmail-style: خوانده نشده = پسزمینه سفید، خوانده شده = پسزمینه خاکستری
|
||||
const isUnread = 'seen' in item && !item.seen;
|
||||
const bgColor = isUnread ? 'bg-white' : 'bg-gray-50';
|
||||
const hoverColor = isUnread ? 'hover:bg-gray-50' : 'hover:bg-gray-100';
|
||||
@@ -213,16 +199,11 @@ const Table = <T extends RowDataType>({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Inline Actions - بعد از checkbox */}
|
||||
|
||||
|
||||
<div className="flex-1 min-w-0">
|
||||
{/* محتوای اصلی - ستون اول */}
|
||||
<div className={`flex items-center mb-0.5 text-sm ${fontWeight}`}>
|
||||
{columns[0]?.render ? columns[0].render(item) : (item[columns[0]?.key] as React.ReactNode)}
|
||||
</div>
|
||||
|
||||
{/* اطلاعات اضافی - سه ستون آخر در یک خط */}
|
||||
{columns.length > 1 && (
|
||||
<div className={`flex items-center gap-3 text-xs text-gray-500 ${fontWeight}`}>
|
||||
{columns.slice(1).map((col) => (
|
||||
@@ -269,7 +250,6 @@ const Table = <T extends RowDataType>({
|
||||
);
|
||||
}
|
||||
return data.map((item, rowIndex) => {
|
||||
// Gmail-style: خوانده نشده = پسزمینه سفید، خوانده شده = پسزمینه خاکستری
|
||||
const isUnread = 'seen' in item && !item.seen;
|
||||
const bgColor = isUnread ? 'bg-white' : 'bg-gray-50';
|
||||
const hoverColor = isUnread ? 'hover:bg-gray-50' : 'hover:bg-gray-100';
|
||||
@@ -378,11 +358,9 @@ const Table = <T extends RowDataType>({
|
||||
);
|
||||
};
|
||||
|
||||
// اگر header نشان داده نمیشود، از Gmail-style layout فقط در موبایل استفاده کن
|
||||
if (!showHeader) {
|
||||
return (
|
||||
<div className={`relative mt-6 md:mt-9 w-full ${className}`}>
|
||||
{/* نسخه موبایل - Gmail style */}
|
||||
<div className={`md:hidden`}>
|
||||
{(actionsPosition === 'top' || actionsPosition === 'above-header') && (
|
||||
<div className="h-[64px] bg-white flex items-center justify-between px-4 rounded-t-2xl border-b border-[#EAEDF5]">
|
||||
@@ -394,7 +372,6 @@ const Table = <T extends RowDataType>({
|
||||
<button
|
||||
onClick={() => {
|
||||
setMobileSelectionMode(false);
|
||||
// پاک کردن انتخابها
|
||||
if (externalSelectedRows !== undefined) {
|
||||
if (onSelectionChange) {
|
||||
onSelectionChange([]);
|
||||
@@ -416,7 +393,6 @@ const Table = <T extends RowDataType>({
|
||||
{renderGmailStyleRows()}
|
||||
</div>
|
||||
|
||||
{/* نسخه دسکتاپ - Table معمولی */}
|
||||
<div className={`hidden md:block`}>
|
||||
{(actionsPosition === 'top' || actionsPosition === 'above-header') && (
|
||||
<div className="h-[74px] bg-white flex items-center px-6 rounded-t-3xl">
|
||||
@@ -509,7 +485,6 @@ const MyComponent = () => {
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
setCurrentPage(page);
|
||||
// اینجا میتوانید API call جدید برای دریافت دادههای صفحه جدید بزنید
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -13,24 +13,20 @@ let addToast: (toast: Toast) => void;
|
||||
const ToastContainer: React.FC = () => {
|
||||
const [toasts, setToasts] = useState<Toast[]>([]);
|
||||
|
||||
// ثبت toast جدید
|
||||
const showToast = (toast: Toast) => {
|
||||
setToasts((prev) => [...prev, toast]);
|
||||
|
||||
// حذف خودکار بعد از ۳ ثانیه
|
||||
setTimeout(() => {
|
||||
setToasts((prev) => prev.map(t =>
|
||||
t.id === toast.id ? { ...t, isExiting: true } : t
|
||||
));
|
||||
|
||||
// حذف از DOM بعد از اتمام انیمیشن
|
||||
setTimeout(() => {
|
||||
setToasts((prev) => prev.filter((t) => t.id !== toast.id));
|
||||
}, 300); // مدت زمان انیمیشن خروج
|
||||
}, 300);
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
// تخصیص تابع نمایش toast به متغیر سراسری
|
||||
useEffect(() => {
|
||||
addToast = showToast;
|
||||
}, []);
|
||||
|
||||
@@ -38,7 +38,6 @@ const EmailInput: FC<EmailInputProps> = ({
|
||||
return
|
||||
}
|
||||
|
||||
// Always search via API for fresh results
|
||||
if (onSearchSuggestions) {
|
||||
if (searchTimeoutRef.current) {
|
||||
clearTimeout(searchTimeoutRef.current)
|
||||
@@ -51,7 +50,6 @@ const EmailInput: FC<EmailInputProps> = ({
|
||||
setSelectedSuggestionIndex(-1)
|
||||
}
|
||||
|
||||
// Update filtered suggestions when API suggestions change
|
||||
useEffect(() => {
|
||||
if (suggestions.length > 0) {
|
||||
const filtered = suggestions
|
||||
@@ -63,7 +61,6 @@ const EmailInput: FC<EmailInputProps> = ({
|
||||
setFilteredSuggestions(filtered)
|
||||
setShowSuggestions(filtered.length > 0 && to.trim().length > 0)
|
||||
} else if (to.trim() === '') {
|
||||
// Clear suggestions when input is empty
|
||||
setFilteredSuggestions([])
|
||||
setShowSuggestions(false)
|
||||
}
|
||||
|
||||
@@ -20,35 +20,29 @@ export const useNewMessage = () => {
|
||||
} = useSharedStore();
|
||||
const { email: userEmail } = useAuthStore();
|
||||
|
||||
// Form state
|
||||
const [toEmails, setToEmails] = useState<string[]>([]);
|
||||
const [subject, setSubject] = useState("");
|
||||
const [content, setContent] = useState("");
|
||||
const [priority, setPriority] = useState("");
|
||||
const [attachments, setAttachments] = useState<EmailAttachmentDto[]>([]);
|
||||
|
||||
// Loading states
|
||||
const [isSending, setIsSending] = useState(false);
|
||||
const [isSavingDraft, setIsSavingDraft] = useState(false);
|
||||
|
||||
// API hooks
|
||||
const sendEmailMutation = useSendEmail();
|
||||
const updateDraftMutation = useUpdateDraft();
|
||||
const sendDraftMutation = useSendDraft();
|
||||
|
||||
// Email suggestions
|
||||
const {
|
||||
suggestions: emailSuggestions,
|
||||
searchSuggestions,
|
||||
} = useEmailSuggestions();
|
||||
|
||||
// Get draft detail if draftData exists
|
||||
const { data: draftDetail } = useGetMessageDetail(
|
||||
draftData?.id.toString() || "",
|
||||
draftData?.mailbox || ""
|
||||
);
|
||||
|
||||
// Load draft data into form
|
||||
useEffect(() => {
|
||||
if (draftDetail) {
|
||||
const toEmailsList =
|
||||
@@ -81,7 +75,6 @@ export const useNewMessage = () => {
|
||||
return emailRegex.test(email);
|
||||
};
|
||||
|
||||
// Function to search email suggestions
|
||||
const searchEmailSuggestions = (query: string) => {
|
||||
searchSuggestions(query);
|
||||
};
|
||||
@@ -237,7 +230,6 @@ export const useNewMessage = () => {
|
||||
};
|
||||
|
||||
return {
|
||||
// Form state
|
||||
toEmails,
|
||||
subject,
|
||||
content,
|
||||
@@ -245,11 +237,9 @@ export const useNewMessage = () => {
|
||||
attachments,
|
||||
emailSuggestions,
|
||||
|
||||
// Loading states
|
||||
isSending,
|
||||
isSavingDraft,
|
||||
|
||||
// Form handlers
|
||||
addEmail,
|
||||
removeEmail,
|
||||
setSubject,
|
||||
@@ -257,10 +247,8 @@ export const useNewMessage = () => {
|
||||
setPriority,
|
||||
setAttachments,
|
||||
|
||||
// Search functions
|
||||
searchEmailSuggestions,
|
||||
|
||||
// Actions
|
||||
handleSend,
|
||||
handleSaveDraft,
|
||||
resetForm,
|
||||
|
||||
@@ -20,7 +20,6 @@ export const useReply = ({
|
||||
const [content, setContent] = useState<string>("");
|
||||
const [isExpanded, setIsExpanded] = useState<boolean>(false);
|
||||
|
||||
// API hooks
|
||||
const sendEmailMutation = useSendEmail();
|
||||
|
||||
const resetForm = () => {
|
||||
@@ -39,7 +38,6 @@ export const useReply = ({
|
||||
return false;
|
||||
}
|
||||
|
||||
// Format date to Persian
|
||||
const originalDate = new Date(originalMessage.date);
|
||||
const persianDate = new Intl.DateTimeFormat("fa-IR", {
|
||||
weekday: "long",
|
||||
@@ -143,12 +141,10 @@ ${
|
||||
};
|
||||
|
||||
return {
|
||||
// State
|
||||
content,
|
||||
isExpanded,
|
||||
isSending: sendEmailMutation.isPending,
|
||||
|
||||
// Handlers
|
||||
setContent,
|
||||
expandReply,
|
||||
collapseReply,
|
||||
@@ -156,7 +152,6 @@ ${
|
||||
handleSaveDraft,
|
||||
resetForm,
|
||||
|
||||
// Original message data
|
||||
originalMessage,
|
||||
};
|
||||
};
|
||||
|
||||
+4
-23
@@ -41,7 +41,7 @@ export const removeRefreshToken = async () => {
|
||||
export const timeAgo = (date: string | Date): string => {
|
||||
const now = new Date();
|
||||
const past = new Date(date);
|
||||
const diff = Math.floor((now.getTime() - past.getTime()) / 1000); // اختلاف بر حسب ثانیه
|
||||
const diff = Math.floor((now.getTime() - past.getTime()) / 1000);
|
||||
|
||||
const units = [
|
||||
{ name: "سال", value: 60 * 60 * 24 * 365 },
|
||||
@@ -82,48 +82,33 @@ export const formatDate = (dateString: string) => {
|
||||
};
|
||||
|
||||
export const detectTextDirection = (text: string): "ltr" | "rtl" => {
|
||||
// حذف تگهای HTML برای تحلیل فقط متن
|
||||
const plainText = text.replace(/<[^>]*>/g, " ").trim();
|
||||
|
||||
// اگر متن خالی است، از RTL استفاده کن
|
||||
if (!plainText) return "rtl";
|
||||
|
||||
// الگوی کاراکترهای انگلیسی و اعداد
|
||||
const englishPattern = /[a-zA-Z0-9\s.,!?;:'"()\-@#$%^&*+=<>{}[\]\\|`~_]/g;
|
||||
const englishMatches = plainText.match(englishPattern) || [];
|
||||
|
||||
// الگوی کاراکترهای فارسی و عربی (برای آینده ممکن است نیاز باشد)
|
||||
// const persianArabicPattern = /[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]/g;
|
||||
|
||||
// محاسبه درصد کاراکترهای انگلیسی
|
||||
const totalChars = plainText.replace(/\s/g, "").length;
|
||||
const englishChars = englishMatches.join("").replace(/\s/g, "").length;
|
||||
const englishPercentage =
|
||||
totalChars > 0 ? (englishChars / totalChars) * 100 : 0;
|
||||
|
||||
// اگر بیش از 70% متن انگلیسی است، LTR استفاده کن
|
||||
return englishPercentage > 70 ? "ltr" : "rtl";
|
||||
};
|
||||
|
||||
export const sanitizeEmailHTML = (html: string): string => {
|
||||
try {
|
||||
// Remove style tags and their content
|
||||
let sanitized = html.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");
|
||||
|
||||
// Remove link tags (CSS files)
|
||||
sanitized = sanitized.replace(/<link[^>]*>/gi, "");
|
||||
|
||||
// Remove script tags for security
|
||||
sanitized = sanitized.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "");
|
||||
|
||||
// Remove potentially harmful attributes
|
||||
sanitized = sanitized.replace(/\s*on\w+\s*=\s*["'][^"']*["']/gi, "");
|
||||
|
||||
// تشخیص جهت متن اصلی
|
||||
const detectedDirection = detectTextDirection(sanitized);
|
||||
|
||||
// فقط font-family، font-size و font-weight رو از inline styles حذف کن
|
||||
// text-align و direction رو فقط اگر با جهت تشخیص داده شده مطابقت نداشته باشد حذف کن
|
||||
sanitized = sanitized.replace(
|
||||
/style\s*=\s*["']([^"']*?)["']/gi,
|
||||
(_, styles) => {
|
||||
@@ -132,19 +117,17 @@ export const sanitizeEmailHTML = (html: string): string => {
|
||||
.replace(/font-size\s*:\s*[^;]*;?/gi, "")
|
||||
.replace(/font-weight\s*:\s*[^;]*;?/gi, "");
|
||||
|
||||
// اگر جهت تشخیص داده شده LTR است، direction و text-align را حفظ کن
|
||||
if (detectedDirection === "ltr") {
|
||||
// direction و text-align را حفظ کن
|
||||
// TODO: Implement LTR
|
||||
} else {
|
||||
// اگر RTL است، این استایلها را حذف کن تا استایل سایت اعمال شود
|
||||
cleanedStyles = cleanedStyles
|
||||
.replace(/text-align\s*:\s*[^;]*;?/gi, "")
|
||||
.replace(/direction\s*:\s*[^;]*;?/gi, "");
|
||||
}
|
||||
|
||||
cleanedStyles = cleanedStyles
|
||||
.replace(/;\s*;/g, ";") // حذف سمیکالنهای اضافی
|
||||
.replace(/^\s*;\s*|\s*;\s*$/g, ""); // حذف سمیکالنهای ابتدا و انتها
|
||||
.replace(/;\s*;/g, ";")
|
||||
.replace(/^\s*;\s*|\s*;\s*$/g, "");
|
||||
|
||||
return cleanedStyles ? `style="${cleanedStyles}"` : "";
|
||||
}
|
||||
@@ -157,7 +140,6 @@ export const sanitizeEmailHTML = (html: string): string => {
|
||||
}
|
||||
};
|
||||
|
||||
// Interface برای mailbox structure
|
||||
interface MailboxItem {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -165,7 +147,6 @@ interface MailboxItem {
|
||||
total: number;
|
||||
}
|
||||
|
||||
// Utility function برای گرفتن mailbox id از localStorage
|
||||
export const getMailboxId = (mailboxName: string): string | null => {
|
||||
try {
|
||||
const mailboxesStr = localStorage.getItem("mailboxes");
|
||||
|
||||
@@ -20,9 +20,7 @@ import { toast } from '../components/Toast';
|
||||
export const useEmailActions = (customRefetch?: () => void) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
// تمام query key های موجود در سیستم
|
||||
const invalidateAllQueries = () => {
|
||||
// صفحههای اصلی
|
||||
queryClient.invalidateQueries({ queryKey: ['inbox'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['drafts'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['archive-messages'] });
|
||||
@@ -31,11 +29,9 @@ export const useEmailActions = (customRefetch?: () => void) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['trash-messages'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['search-messages'] });
|
||||
|
||||
// پیامهای تکی
|
||||
queryClient.invalidateQueries({ queryKey: ['message'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['draft'] });
|
||||
|
||||
// صفحههایی که از React Query استفاده نمیکنند (مثل sent)
|
||||
if (customRefetch) {
|
||||
customRefetch();
|
||||
}
|
||||
@@ -162,7 +158,6 @@ export const useEmailActions = (customRefetch?: () => void) => {
|
||||
restoreMutation.isPending;
|
||||
|
||||
return {
|
||||
// Bulk actions
|
||||
bulkAction: bulkActionMutation.mutate,
|
||||
bulkMarkAsSeen: (messageIds: number[], mailbox: string) =>
|
||||
bulkActionMutation.mutate({ messageIds, action: BulkActionType.SEEN, mailbox: mailbox }),
|
||||
@@ -183,7 +178,6 @@ export const useEmailActions = (customRefetch?: () => void) => {
|
||||
bulkRestore: (messageIds: number[], mailbox: string) =>
|
||||
bulkActionMutation.mutate({ messageIds, action: BulkActionType.RESTORE, mailbox: mailbox }),
|
||||
|
||||
// Single actions (keeping for backward compatibility)
|
||||
markAsSeen: markAsSeenMutation.mutate,
|
||||
archive: archiveMutation.mutate,
|
||||
favorite: favoriteMutation.mutate,
|
||||
|
||||
@@ -54,7 +54,6 @@ export const useEmailEvents = (
|
||||
useEffect(() => {
|
||||
if (!socket) return;
|
||||
|
||||
// Register event handlers
|
||||
if (handlers.onNewEmail) {
|
||||
socket.on("new_email", handlers.onNewEmail);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ export const useEmailWebSocket = ({
|
||||
transports: ["websocket", "polling"],
|
||||
});
|
||||
|
||||
// Connection events
|
||||
newSocket.on("connect", () => {
|
||||
setIsConnected(true);
|
||||
onConnect?.();
|
||||
@@ -43,7 +42,6 @@ export const useEmailWebSocket = ({
|
||||
onError?.(error.error);
|
||||
});
|
||||
|
||||
// Health monitoring
|
||||
newSocket.on("ping", () => {
|
||||
newSocket.emit("pong", { timestamp: new Date().toISOString() });
|
||||
});
|
||||
|
||||
@@ -8,11 +8,9 @@ const useDetectLatinAndNumbers = () => {
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
const textContent = node.nodeValue;
|
||||
if (textContent) {
|
||||
// اگر عدد داشت
|
||||
if (/\d/.test(textContent)) {
|
||||
element.classList.add("has-number");
|
||||
}
|
||||
// اگر حرف لاتین داشت
|
||||
if (/[a-zA-Z]/.test(textContent)) {
|
||||
element.classList.add("has-latin");
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ const List: FC = () => {
|
||||
const [selectedMessages, setSelectedMessages] = useState<TrashMessage[]>([]);
|
||||
const [selectAll, setSelectAll] = useState(false);
|
||||
|
||||
// تابع برای گرفتن فقط ساعت
|
||||
const formatTime = (dateString: string) => {
|
||||
try {
|
||||
if (!dateString) return '--:--';
|
||||
@@ -49,7 +48,6 @@ const List: FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// ستونهای دسکتاپ
|
||||
const desktopColumns: ColumnType<TrashMessage>[] = [
|
||||
{
|
||||
key: 'subject',
|
||||
@@ -78,7 +76,6 @@ const List: FC = () => {
|
||||
},
|
||||
];
|
||||
|
||||
// ستونهای موبایل (سه خطی)
|
||||
const mobileColumns: ColumnType<TrashMessage>[] = [
|
||||
{
|
||||
key: 'content',
|
||||
@@ -106,7 +103,6 @@ const List: FC = () => {
|
||||
}
|
||||
];
|
||||
|
||||
// انتخاب ستونها بر اساس سایز صفحه
|
||||
const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -170,7 +166,6 @@ const List: FC = () => {
|
||||
|
||||
const handleSelectionChange = (selectedRows: TrashMessage[]) => {
|
||||
setSelectedMessages(selectedRows);
|
||||
// تنظیم وضعیت select all بر اساس انتخاب شدهها
|
||||
const messages = trashData?.data?.trashMails || [];
|
||||
setSelectAll(selectedRows.length === messages.length && messages.length > 0);
|
||||
};
|
||||
|
||||
@@ -10,7 +10,6 @@ export const getTrashMessages = async (
|
||||
!!query.search || !!query.datestart || query.dateend || query.from;
|
||||
const url = isSearch ? "search" : "messages/trash";
|
||||
|
||||
// اضافه کردن mailbox id از localStorage
|
||||
const mailboxId = getMailboxId("Trash");
|
||||
const queryWithMailbox = {
|
||||
...query,
|
||||
@@ -23,8 +22,6 @@ export const getTrashMessages = async (
|
||||
return data;
|
||||
};
|
||||
|
||||
// Note: deleteMessagePermanently and restoreMessage are now handled by EmailActionsService
|
||||
|
||||
export const emptyTrash = async () => {
|
||||
const mailboxId = getMailboxId(MailboxEnum.TRASH);
|
||||
const { data } = await axios.patch(
|
||||
|
||||
@@ -28,7 +28,6 @@ const List: FC = () => {
|
||||
const [selectedMessages, setSelectedMessages] = useState<SearchMessage[]>([]);
|
||||
const [selectAll, setSelectAll] = useState(false);
|
||||
|
||||
// تابع برای گرفتن فقط ساعت
|
||||
const formatTime = (dateString: string) => {
|
||||
try {
|
||||
if (!dateString) return '--:--';
|
||||
@@ -45,7 +44,6 @@ const List: FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// ستونهای دسکتاپ
|
||||
const desktopColumns: ColumnType<SearchMessage>[] = [
|
||||
{
|
||||
key: 'subject',
|
||||
@@ -81,7 +79,6 @@ const List: FC = () => {
|
||||
},
|
||||
];
|
||||
|
||||
// ستونهای موبایل (سه خطی)
|
||||
const mobileColumns: ColumnType<SearchMessage>[] = [
|
||||
{
|
||||
key: 'content',
|
||||
@@ -112,7 +109,6 @@ const List: FC = () => {
|
||||
}
|
||||
];
|
||||
|
||||
// انتخاب ستونها بر اساس سایز صفحه
|
||||
const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -171,7 +167,6 @@ const List: FC = () => {
|
||||
|
||||
const handleSelectionChange = (selectedRows: SearchMessage[]) => {
|
||||
setSelectedMessages(selectedRows);
|
||||
// تنظیم وضعیت select all بر اساس انتخاب شدهها
|
||||
const messages = searchData?.data?.results || [];
|
||||
setSelectAll(selectedRows.length === messages.length && messages.length > 0);
|
||||
};
|
||||
|
||||
@@ -29,7 +29,6 @@ const List: FC = () => {
|
||||
const [selectedMessages, setSelectedMessages] = useState<InboxMessage[]>([]);
|
||||
const [selectAll, setSelectAll] = useState(false);
|
||||
|
||||
// تابع برای گرفتن فقط ساعت
|
||||
const formatTime = (dateString: string) => {
|
||||
try {
|
||||
if (!dateString) return '--:--';
|
||||
@@ -46,7 +45,6 @@ const List: FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// ستونهای دسکتاپ
|
||||
const desktopColumns: ColumnType<InboxMessage>[] = [
|
||||
{
|
||||
key: 'subject',
|
||||
@@ -89,7 +87,6 @@ const List: FC = () => {
|
||||
},
|
||||
];
|
||||
|
||||
// ستونهای موبایل (سه خطی)
|
||||
const mobileColumns: ColumnType<InboxMessage>[] = [
|
||||
{
|
||||
key: 'content',
|
||||
@@ -117,7 +114,6 @@ const List: FC = () => {
|
||||
}
|
||||
];
|
||||
|
||||
// انتخاب ستونها بر اساس سایز صفحه
|
||||
const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -179,7 +175,6 @@ const List: FC = () => {
|
||||
|
||||
const handleSelectionChange = (selectedRows: InboxMessage[]) => {
|
||||
setSelectedMessages(selectedRows);
|
||||
// تنظیم وضعیت select all بر اساس انتخاب شدهها
|
||||
const messages = sentData?.data?.sentMails || [];
|
||||
setSelectAll(selectedRows.length === messages.length && messages.length > 0);
|
||||
};
|
||||
|
||||
@@ -14,7 +14,6 @@ export const getSentMessages = async (
|
||||
!!query.search || !!query.datestart || query.dateend || query.from;
|
||||
const url = isSearch ? "search" : "messages/sent";
|
||||
|
||||
// اضافه کردن mailbox id از localStorage
|
||||
const mailboxId = getMailboxId("Sent Mail");
|
||||
const queryWithMailbox = {
|
||||
...query,
|
||||
|
||||
@@ -10,7 +10,6 @@ export const getSpamMessages = async (
|
||||
!!query.search || !!query.datestart || query.dateend || query.from;
|
||||
const url = isSearch ? "search" : "messages/junk";
|
||||
|
||||
// اضافه کردن mailbox id از localStorage
|
||||
const mailboxId = getMailboxId("Junk");
|
||||
const queryWithMailbox = {
|
||||
...query,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import axios from "../config/axios";
|
||||
import { BulkActionRequest } from "../pages/received/types/Types";
|
||||
|
||||
// Bulk action for multiple messages
|
||||
export const bulkAction = async (request: BulkActionRequest) => {
|
||||
const { data } = await axios.post(
|
||||
`/email/messages/bulk-action?mailbox=${request.mailbox}`,
|
||||
@@ -10,7 +9,6 @@ export const bulkAction = async (request: BulkActionRequest) => {
|
||||
return data;
|
||||
};
|
||||
|
||||
// Mark message as seen
|
||||
export const markMessageAsSeen = async (messageId: number, mailbox: string) => {
|
||||
const { data } = await axios.patch(
|
||||
`/email/messages/${messageId}/seen?mailbox=${mailbox}`
|
||||
@@ -18,7 +16,6 @@ export const markMessageAsSeen = async (messageId: number, mailbox: string) => {
|
||||
return data;
|
||||
};
|
||||
|
||||
// Move message to archive
|
||||
export const moveMessageToArchive = async (
|
||||
messageId: number,
|
||||
mailbox: string
|
||||
@@ -29,7 +26,6 @@ export const moveMessageToArchive = async (
|
||||
return data;
|
||||
};
|
||||
|
||||
// Move message to favorite
|
||||
export const moveMessageToFavorite = async (
|
||||
messageId: number,
|
||||
mailbox: string
|
||||
@@ -40,7 +36,6 @@ export const moveMessageToFavorite = async (
|
||||
return data;
|
||||
};
|
||||
|
||||
// Move message to trash
|
||||
export const moveMessageToTrash = async (
|
||||
messageId: number,
|
||||
mailbox: string
|
||||
@@ -51,7 +46,6 @@ export const moveMessageToTrash = async (
|
||||
return data;
|
||||
};
|
||||
|
||||
// Delete message permanently (only for trash mailbox)
|
||||
export const deleteMessagePermanently = async (
|
||||
messageId: number,
|
||||
mailbox: string
|
||||
@@ -62,13 +56,11 @@ export const deleteMessagePermanently = async (
|
||||
return data;
|
||||
};
|
||||
|
||||
// Send email
|
||||
export const sendEmail = async (messageId: number) => {
|
||||
const { data } = await axios.post(`/email/send?messageId=${messageId}`);
|
||||
return data;
|
||||
};
|
||||
|
||||
// Update draft
|
||||
export const updateDraft = async (messageId: number, draftData: unknown) => {
|
||||
const { data } = await axios.patch(
|
||||
`/email/messages/drafts/${messageId}`,
|
||||
@@ -77,7 +69,6 @@ export const updateDraft = async (messageId: number, draftData: unknown) => {
|
||||
return data;
|
||||
};
|
||||
|
||||
// Delete draft
|
||||
export const deleteDraft = async (messageId: number, mailbox: string) => {
|
||||
const { data } = await axios.delete(
|
||||
`/email/messages/drafts/${messageId}?mailbox=${mailbox}`
|
||||
@@ -85,13 +76,11 @@ export const deleteDraft = async (messageId: number, mailbox: string) => {
|
||||
return data;
|
||||
};
|
||||
|
||||
// Send draft
|
||||
export const sendDraft = async (messageId: number) => {
|
||||
const { data } = await axios.post(`/email/messages/drafts/${messageId}/send`);
|
||||
return data;
|
||||
};
|
||||
|
||||
// Unfavorite message
|
||||
export const unfavoriteMessage = async (messageId: number, mailbox: string) => {
|
||||
const { data } = await axios.patch(
|
||||
`/email/messages/${messageId}/unfavorite?mailbox=${mailbox}`
|
||||
@@ -99,7 +88,6 @@ export const unfavoriteMessage = async (messageId: number, mailbox: string) => {
|
||||
return data;
|
||||
};
|
||||
|
||||
// Unarchive message
|
||||
export const unarchiveMessage = async (messageId: number, mailbox: string) => {
|
||||
const { data } = await axios.patch(
|
||||
`/email/messages/${messageId}/unarchive?mailbox=${mailbox}`
|
||||
@@ -107,7 +95,6 @@ export const unarchiveMessage = async (messageId: number, mailbox: string) => {
|
||||
return data;
|
||||
};
|
||||
|
||||
// Restore message from trash
|
||||
export const restoreMessage = async (messageId: number, mailbox: string) => {
|
||||
const { data } = await axios.patch(
|
||||
`/email/messages/${messageId}/restore?mailbox=${mailbox}`
|
||||
|
||||
@@ -31,7 +31,6 @@ const SideBar: FC = () => {
|
||||
|
||||
|
||||
const iconSizeSideBar = 20
|
||||
// if (!mailboxCount?.data) return null
|
||||
return (
|
||||
<>
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user