dark mode v1
This commit is contained in:
+20
-17
@@ -19,6 +19,7 @@ import Login from './pages/auth/Login';
|
||||
import { EmailWebSocketProvider } from './contexts/EmailWebSocketContext';
|
||||
import SocketManagment from './lib/SocketManagment';
|
||||
import NajvaToken from './components/NajvaToken';
|
||||
import { ThemeProvider } from './contexts/ThemeContext';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@@ -121,23 +122,25 @@ const App: FC = () => {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<I18nextProvider i18n={i18next}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{isLogin === 'isLogin' && userToken ? (
|
||||
<EmailWebSocketProvider token={userToken}>
|
||||
<Main />
|
||||
<SocketManagment />
|
||||
<NajvaToken />
|
||||
</EmailWebSocketProvider>
|
||||
) : isLogin === 'isNotLogin' ? (
|
||||
<Login />
|
||||
) : null}
|
||||
<NewMessage />
|
||||
<ToastContainer />
|
||||
</QueryClientProvider>
|
||||
</I18nextProvider>
|
||||
</BrowserRouter>
|
||||
<ThemeProvider>
|
||||
<BrowserRouter>
|
||||
<I18nextProvider i18n={i18next}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{isLogin === 'isLogin' && userToken ? (
|
||||
<EmailWebSocketProvider token={userToken}>
|
||||
<Main />
|
||||
<SocketManagment />
|
||||
<NajvaToken />
|
||||
</EmailWebSocketProvider>
|
||||
) : isLogin === 'isNotLogin' ? (
|
||||
<Login />
|
||||
) : null}
|
||||
<NewMessage />
|
||||
<ToastContainer />
|
||||
</QueryClientProvider>
|
||||
</I18nextProvider>
|
||||
</BrowserRouter>
|
||||
</ThemeProvider>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+19
-18
@@ -1,39 +1,40 @@
|
||||
import { FC, ReactNode } from 'react'
|
||||
import { FC, ReactNode, ButtonHTMLAttributes } from 'react'
|
||||
import { clx } from '../helpers/utils'
|
||||
import MoonLoader from "react-spinners/ClipLoader"
|
||||
|
||||
|
||||
interface ButtonProps {
|
||||
label?: string;
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
loading?: boolean;
|
||||
onClick?: () => void;
|
||||
type?: 'button' | 'submit' | 'reset';
|
||||
disabled?: boolean;
|
||||
type ButtonBaseProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'className' | 'children'>
|
||||
|
||||
interface ButtonProps extends ButtonBaseProps {
|
||||
label?: string
|
||||
children?: ReactNode
|
||||
className?: string
|
||||
loading?: boolean
|
||||
variant?: 'primary' | 'secondary'
|
||||
}
|
||||
|
||||
const Button: FC<ButtonProps> = (props) => {
|
||||
|
||||
const { label, children, className, loading, variant, type, disabled, ...rest } = props
|
||||
|
||||
const buttonClass = clx(
|
||||
'w-full bg-primary text-white cursor-pointer rounded-xl font-normal text-xs md:text-sm h-10 px-3 md:px-4 transition-all duration-200 hover:bg-opacity-90 disabled:opacity-50 disabled:cursor-not-allowed',
|
||||
props.variant === 'secondary' && 'bg-[#ECEEF5] text-black',
|
||||
props.className
|
||||
'w-full bg-primary text-primary-foreground cursor-pointer rounded-xl font-normal text-xs md:text-sm h-10 px-3 md:px-4 transition-all duration-200 hover:bg-opacity-90 disabled:opacity-50 disabled:cursor-not-allowed',
|
||||
variant === 'secondary' && 'bg-[#ECEEF5] text-black',
|
||||
className
|
||||
)
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={props.onClick}
|
||||
type={props.type || 'button'}
|
||||
disabled={props.disabled || props.loading}
|
||||
type={type || 'button'}
|
||||
disabled={disabled || loading}
|
||||
className={buttonClass}
|
||||
{...rest}
|
||||
>
|
||||
{props.loading ? (
|
||||
<MoonLoader className='mt-1.5' size={15} color={props.variant === 'secondary' ? '#000' : '#fff'} />
|
||||
{loading ? (
|
||||
<MoonLoader className='mt-1.5' size={15} color={'currentColor'} />
|
||||
|
||||
) : (
|
||||
props.children || props.label
|
||||
children || label
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FC, Fragment } from 'react'
|
||||
import Td from './Td'
|
||||
import Td from '@/components/Td'
|
||||
import Skeleton from 'react-loading-skeleton'
|
||||
|
||||
type Props = {
|
||||
@@ -8,15 +8,18 @@ type Props = {
|
||||
}
|
||||
|
||||
const DefaultTableSkeleton: FC<Props> = ({ tdCount, trCount = 5 }) => {
|
||||
const isDark = typeof document !== 'undefined' && document.documentElement.classList.contains('dark')
|
||||
const baseColor = isDark ? '#2d2d30' : '#e5e7eb'
|
||||
const highlightColor = isDark ? '#3c3c3c' : '#f3f4f6'
|
||||
return (
|
||||
<Fragment>
|
||||
{
|
||||
Array.from({ length: trCount }).map((_, rowIndex) => (
|
||||
<tr className="w-full h-[64px] md:h-[74px] bg-white border-t border-[#EAEDF5]" key={rowIndex}>
|
||||
<tr className="w-full h-[64px] md:h-[74px] bg-surface border-t border-surface" key={rowIndex}>
|
||||
{
|
||||
Array.from({ length: tdCount }).map((_, colIndex) => (
|
||||
<Td text={''} key={colIndex}>
|
||||
<Skeleton />
|
||||
<Skeleton baseColor={baseColor} highlightColor={highlightColor} />
|
||||
</Td>
|
||||
))
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useEmailWebSocket } from '@/hooks/useEmailWebSocket';
|
||||
import { useEmailEvents, EmailPayload, UnreadCountPayload, EmailStatusPayload } from '@/hooks/useEmailEvents';
|
||||
import { useNotificationSound } from '@/hooks/useNotificationSound';
|
||||
import { Notification as NotificationIcon, VolumeHigh } from 'iconsax-react';
|
||||
import { getIconColor } from '@/utils/colorUtils';
|
||||
|
||||
interface EmailNotificationsProps {
|
||||
userToken: string;
|
||||
@@ -94,17 +95,17 @@ export const EmailNotifications: React.FC<EmailNotificationsProps> = ({
|
||||
onClick={() => {
|
||||
playSound();
|
||||
}}
|
||||
className="p-2 rounded-full hover:bg-gray-100 transition-colors"
|
||||
className="p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
|
||||
title="تست صدا"
|
||||
>
|
||||
<VolumeHigh size={20} color="black" />
|
||||
<VolumeHigh size={20} color={getIconColor('primary')} />
|
||||
</button>
|
||||
|
||||
<div
|
||||
className="relative cursor-pointer p-2 rounded-full hover:bg-gray-100 transition-colors"
|
||||
className="relative cursor-pointer p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
|
||||
onClick={() => setIsVisible(!isVisible)}
|
||||
>
|
||||
<NotificationIcon size={24} color="black" />
|
||||
<NotificationIcon size={24} color={getIconColor('primary')} />
|
||||
{unreadCount > 0 && (
|
||||
<span className="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full min-w-[18px] h-[18px] flex items-center justify-center font-bold">
|
||||
{unreadCount > 99 ? '99+' : unreadCount}
|
||||
@@ -113,20 +114,20 @@ export const EmailNotifications: React.FC<EmailNotificationsProps> = ({
|
||||
</div>
|
||||
|
||||
{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">
|
||||
<div className="absolute top-full left-0 w-80 max-h-96 bg-white dark:bg-[#252526] border border-gray-200 dark:border-[#3e3e42] rounded-lg shadow-lg z-50 overflow-hidden">
|
||||
<div className="p-3 bg-gray-50 dark:bg-[#2d2d30] border-b border-gray-200 dark:border-[#3e3e42] flex justify-between items-center">
|
||||
<h3 className="font-semibold">📧 نوتیفیکیشنهای ایمیل</h3>
|
||||
<span className="text-sm text-gray-600">{unreadCount} خوانده نشده</span>
|
||||
<span className="text-sm text-gray-600 dark:text-gray-400">{unreadCount} خوانده نشده</span>
|
||||
</div>
|
||||
|
||||
<div className="max-h-72 overflow-y-auto">
|
||||
{notifications.length === 0 ? (
|
||||
<div className="p-4 text-center text-gray-500">ایمیل جدیدی وجود ندارد</div>
|
||||
<div className="p-4 text-center text-gray-500 dark:text-gray-400">ایمیل جدیدی وجود ندارد</div>
|
||||
) : (
|
||||
notifications.map((email) => (
|
||||
<div
|
||||
key={email.messageId}
|
||||
className="p-3 border-b border-gray-100 cursor-pointer hover:bg-gray-50 transition-colors"
|
||||
className="p-3 border-b border-gray-100 dark:border-[#2d2d30] cursor-pointer hover:bg-gray-50 dark:hover:bg-[#2d2d30] transition-colors"
|
||||
onClick={() => onEmailClick?.(email)}
|
||||
>
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
@@ -136,8 +137,8 @@ export const EmailNotifications: React.FC<EmailNotificationsProps> = ({
|
||||
{email.hasAttachments && <span>📎</span>}
|
||||
</div>
|
||||
<div className="font-medium text-sm mb-1 truncate">{email.subject}</div>
|
||||
<div className="text-xs text-gray-600 mb-1 line-clamp-2">{email.preview}</div>
|
||||
<div className="text-xs text-gray-400">
|
||||
<div className="text-xs text-gray-600 dark:text-gray-400 mb-1 line-clamp-2">{email.preview}</div>
|
||||
<div className="text-xs text-gray-400 dark:text-gray-500">
|
||||
{new Date(email.timestamp).toLocaleString('fa-IR')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { FC, InputHTMLAttributes, useEffect, useState } from 'react'
|
||||
import { clx } from '../helpers/utils';
|
||||
import { Eye, SearchNormal } from 'iconsax-react';
|
||||
import { getIconColor } from '@/utils/colorUtils';
|
||||
import Error from './Error';
|
||||
|
||||
type Variant = "floating_outlined" | "primary" | "search";
|
||||
@@ -34,8 +35,8 @@ const Input: FC<Props> = (props: Props) => {
|
||||
const [search, setSearch] = useState<string>('')
|
||||
|
||||
const inputClass = clx(
|
||||
'w-full bg-white h-10 text-black block px-4 text-xs rounded-xl border border-border',
|
||||
props.readOnly && 'bg-gray-100 border-0 text-description',
|
||||
'w-full input-surface h-10 block px-4 text-xs rounded-xl transition-colors',
|
||||
props.readOnly && 'bg-muted border-0 text-muted-foreground',
|
||||
props.variant === 'search' && 'ps-10',
|
||||
props.className
|
||||
);
|
||||
@@ -79,7 +80,7 @@ const Input: FC<Props> = (props: Props) => {
|
||||
|
||||
return (
|
||||
<div className='w-full'>
|
||||
<label className='text-sm'>
|
||||
<label className='text-sm text-primary-content'>
|
||||
{props.label}
|
||||
</label>
|
||||
|
||||
@@ -94,12 +95,12 @@ const Input: FC<Props> = (props: Props) => {
|
||||
|
||||
{
|
||||
props.type === 'password' &&
|
||||
<Eye onClick={() => setShowPassword((oldValue) => !oldValue)} size={20} color='#8C90A3' className='absolute top-0 bottom-0 cursor-pointer my-auto left-3' />
|
||||
<Eye onClick={() => setShowPassword((oldValue) => !oldValue)} size={20} color={getIconColor('muted')} className='absolute top-0 bottom-0 cursor-pointer my-auto left-3' />
|
||||
}
|
||||
|
||||
{
|
||||
props.variant === 'search' &&
|
||||
<SearchNormal size={20} color='#8C90A3' className='absolute pointer-events-none top-0 w-5 bottom-0 my-auto right-3' />
|
||||
<SearchNormal size={20} color={getIconColor('muted')} className='absolute pointer-events-none top-0 w-5 bottom-0 my-auto right-3' />
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { More } from 'iconsax-react';
|
||||
import React, { useState, useRef, useEffect } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import { More } from 'iconsax-react'
|
||||
import { getIconColor } from '@/utils/colorUtils'
|
||||
|
||||
export interface RowActionItem {
|
||||
label: string;
|
||||
@@ -15,66 +16,66 @@ interface RowActionsDropdownProps {
|
||||
}
|
||||
|
||||
const RowActionsDropdown: React.FC<RowActionsDropdownProps> = ({ actions, className = '' }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [position, setPosition] = useState({ top: 0, left: 0 });
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [position, setPosition] = useState({ top: 0, left: 0 })
|
||||
const dropdownRef = useRef<HTMLDivElement>(null)
|
||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
||||
setIsOpen(false);
|
||||
setIsOpen(false)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
document.addEventListener('mousedown', handleClickOutside)
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
document.removeEventListener('mousedown', handleClickOutside)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const handleToggle = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
e.stopPropagation()
|
||||
|
||||
if (!isOpen && buttonRef.current) {
|
||||
const rect = buttonRef.current.getBoundingClientRect();
|
||||
const isMobile = window.innerWidth < 768;
|
||||
const dropdownWidth = isMobile ? 140 : 150;
|
||||
const dropdownHeight = actions.length * (isMobile ? 36 : 40);
|
||||
const rect = buttonRef.current.getBoundingClientRect()
|
||||
const isMobile = window.innerWidth < 768
|
||||
const dropdownWidth = isMobile ? 140 : 150
|
||||
const dropdownHeight = actions.length * (isMobile ? 36 : 40)
|
||||
|
||||
let left = rect.left + window.scrollX - dropdownWidth + rect.width;
|
||||
let top = rect.bottom + window.scrollY;
|
||||
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;
|
||||
left = rect.left + window.scrollX - dropdownWidth
|
||||
}
|
||||
|
||||
if (left < 0) {
|
||||
left = isMobile ? 20 : 40;
|
||||
left = isMobile ? 20 : 40
|
||||
}
|
||||
|
||||
if (top + dropdownHeight > window.innerHeight + window.scrollY) {
|
||||
top = rect.top + window.scrollY - dropdownHeight;
|
||||
top = rect.top + window.scrollY - dropdownHeight
|
||||
}
|
||||
|
||||
setPosition({ top, left });
|
||||
setPosition({ top, left })
|
||||
}
|
||||
|
||||
setIsOpen(!isOpen);
|
||||
};
|
||||
setIsOpen(!isOpen)
|
||||
}
|
||||
|
||||
const handleActionClick = (action: RowActionItem) => {
|
||||
action.onClick();
|
||||
setIsOpen(false);
|
||||
};
|
||||
action.onClick()
|
||||
setIsOpen(false)
|
||||
}
|
||||
|
||||
const renderDropdown = () => {
|
||||
if (!isOpen) return null;
|
||||
if (!isOpen) return null
|
||||
|
||||
return createPortal(
|
||||
<div
|
||||
ref={dropdownRef}
|
||||
className="fixed py-3 md:py-3 bg-white rounded-xl md:rounded-2xl shadow-lg z-[5] min-w-[140px] md:min-w-[150px]"
|
||||
className="fixed py-3 md:py-3 bg-white dark:bg-[#252526] rounded-xl md:rounded-2xl shadow-lg z-[5] min-w-[140px] md:min-w-[150px] border border-gray-200 dark:border-[#3e3e42]"
|
||||
style={{
|
||||
top: `${position.top}px`,
|
||||
left: `${position.left}px`,
|
||||
@@ -84,31 +85,30 @@ const RowActionsDropdown: React.FC<RowActionsDropdownProps> = ({ actions, classN
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => handleActionClick(action)}
|
||||
className={`w-full mt-1 text-right px-2 md:px-3 py-1 md:py-1.5 text-xs md:text-[13px] flex items-center gap-2 hover:bg-gray-50 ${index === 0 ? 'rounded-t-lg' : ''
|
||||
} ${action.className || ''}`}
|
||||
className={`w-full mt-1 text-right px-2 md:px-3 py-1 md:py-1.5 text-xs md:text-[13px] flex items-center gap-2 hover:bg-gray-50 dark:hover:bg-[#2d2d30] ${index === 0 ? 'rounded-t-lg' : ''} ${action.className || ''}`}
|
||||
>
|
||||
{action.icon && <span className="ml-2">{action.icon}</span>}
|
||||
<span className={action.label === 'حذف' ? 'text-red-500' : ''}>{action.label}</span>
|
||||
<span className={`${action.label === 'حذف' ? 'text-red-500' : ''} dark:text-gray-200`}>{action.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>,
|
||||
document.body
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<button
|
||||
ref={buttonRef}
|
||||
onClick={handleToggle}
|
||||
className="mt-2 hover:bg-gray-100 rounded-full transition-colors"
|
||||
className="mt-2 hover:bg-gray-100 dark:hover:bg-[#2d2d30] rounded-full transition-colors"
|
||||
>
|
||||
<More size={16} color="black" className="rotate-90" />
|
||||
<More size={16} color={getIconColor('primary')} className="rotate-90" />
|
||||
</button>
|
||||
|
||||
{renderDropdown()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default RowActionsDropdown;
|
||||
export default RowActionsDropdown
|
||||
@@ -1,6 +1,7 @@
|
||||
import { FC, SelectHTMLAttributes } from 'react'
|
||||
import { clx } from '../helpers/utils'
|
||||
import { ArrowDown2 } from 'iconsax-react'
|
||||
import { getIconColor } from '@/utils/colorUtils'
|
||||
|
||||
export type ItemsSelectType = {
|
||||
value: string,
|
||||
@@ -20,14 +21,14 @@ const Select: FC<Props> = (props: Props) => {
|
||||
<div className='w-full'>
|
||||
{
|
||||
props.label &&
|
||||
<label className='text-sm'>
|
||||
<label className='text-sm text-primary-content'>
|
||||
{props.label}
|
||||
</label>
|
||||
}
|
||||
<div className='relative'>
|
||||
<select {...props} className={clx(
|
||||
'w-full text-black relative block border appearance-none border-border px-2.5 h-10 text-sm rounded-2.5 bg-white',
|
||||
props.readOnly && 'bg-gray-100 border-0 text-description',
|
||||
'w-full input-surface relative block appearance-none px-2.5 h-10 text-sm rounded-2.5 transition-colors',
|
||||
props.readOnly && 'bg-muted border-0 text-muted-foreground',
|
||||
props.className,
|
||||
props.label && 'mt-1'
|
||||
)}>
|
||||
@@ -46,7 +47,7 @@ const Select: FC<Props> = (props: Props) => {
|
||||
}
|
||||
|
||||
</select>
|
||||
<ArrowDown2 size={16} color='black' className={clx(
|
||||
<ArrowDown2 size={16} color={getIconColor('muted')} className={clx(
|
||||
'absolute z-0 top-3 left-2 pointer-events-none',
|
||||
)} />
|
||||
</div>
|
||||
|
||||
+21
-21
@@ -15,7 +15,7 @@ const Table = <T extends RowDataType>({
|
||||
className = '',
|
||||
rowClassName = '',
|
||||
noDataMessage = 'هیچ دادهای یافت نشد.',
|
||||
headerClassName = 'bg-gray-50',
|
||||
headerClassName = 'bg-muted',
|
||||
emptyRowsCount = 5,
|
||||
showHeader = true,
|
||||
actions = null,
|
||||
@@ -150,9 +150,9 @@ const Table = <T extends RowDataType>({
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className={`bg-white ${roundedClass} overflow-hidden`}>
|
||||
<div className={`bg-surface ${roundedClass} overflow-hidden`}>
|
||||
{Array.from({ length: emptyRowsCount }).map((_, index) => (
|
||||
<div key={index} className={`h-[48px] bg-gray-100 animate-pulse ${index !== 0 ? 'border-t border-[#EAEDF5]' : ''}`}></div>
|
||||
<div key={index} className={`h-[48px] bg-muted animate-pulse ${index !== 0 ? 'border-t border-surface' : ''}`}></div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
@@ -160,25 +160,25 @@ const Table = <T extends RowDataType>({
|
||||
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<div className={`bg-white ${roundedClass} text-center text-gray-500`}>
|
||||
<img src={NoData} alt='no data' className='w-[100px] mx-auto pt-[70px]' />
|
||||
<div className={`bg-surface ${roundedClass} text-center text-muted-foreground`}>
|
||||
<img src={NoData} alt='no data' className='w-[100px] mx-auto pt-[70px] filterWhite' />
|
||||
<div className='-mt-5 pb-[70px]'>{noDataMessage}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`bg-white ${roundedClass} overflow-hidden`}>
|
||||
<div className={`bg-surface ${roundedClass} overflow-hidden`}>
|
||||
{data.map((item, rowIndex) => {
|
||||
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';
|
||||
const bgColor = isUnread ? 'bg-surface' : 'bg-muted';
|
||||
const hoverColor = isUnread ? 'hover:bg-muted' : 'hover:bg-accent';
|
||||
const fontWeight = isUnread ? 'font-semibold' : 'font-normal';
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item.id}
|
||||
className={`w-full min-h-[85px] ${bgColor} ${hoverColor} ${mobileSelectionMode || !onRowClick ? '' : 'cursor-pointer'} ${getRowClassName(item, rowIndex)} px-4 py-3 flex items-center gap-3 ${rowIndex !== 0 ? 'border-t border-[#EAEDF5]' : ''} ${longPressActiveId === item.id ? 'bg-blue-50' : ''}`}
|
||||
className={`w-full min-h-[85px] ${bgColor} ${hoverColor} ${mobileSelectionMode || !onRowClick ? '' : 'cursor-pointer'} ${getRowClassName(item, rowIndex)} px-4 py-3 flex items-center gap-3 ${rowIndex !== 0 ? 'border-t border-surface' : ''} ${longPressActiveId === item.id ? 'bg-accent' : ''}`}
|
||||
onClick={() => handleMobileRowClick(item)}
|
||||
onTouchStart={() => handleLongPressStart(item)}
|
||||
onTouchEnd={handleLongPressEnd}
|
||||
@@ -205,7 +205,7 @@ const Table = <T extends RowDataType>({
|
||||
</div>
|
||||
|
||||
{columns.length > 1 && (
|
||||
<div className={`flex items-center gap-3 text-xs text-gray-500 ${fontWeight}`}>
|
||||
<div className={`flex items-center gap-3 text-xs text-muted-foreground ${fontWeight}`}>
|
||||
{columns.slice(1).map((col) => (
|
||||
<div key={col.key} className="truncate">
|
||||
{col.render ? col.render(item) : (item[col.key] as React.ReactNode)}
|
||||
@@ -242,8 +242,8 @@ const Table = <T extends RowDataType>({
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<tr>
|
||||
<td colSpan={columns.length + (selectable ? 1 : 0) + (inlineActions ? 1 : 0) + (rowActions ? 1 : 0)} className="px-3 md:px-6 pb-6 md:pb-14 border-t border-border text-center text-gray-500">
|
||||
<img src={NoData} alt='no data' className='w-[100px] mx-auto pt-14' />
|
||||
<td colSpan={columns.length + (selectable ? 1 : 0) + (inlineActions ? 1 : 0) + (rowActions ? 1 : 0)} className="px-3 md:px-6 pb-6 md:pb-14 border-t border-border text-center text-gray-500 dark:text-gray-400">
|
||||
<img src={NoData} alt='no data' className='w-[100px] mx-auto pt-14 filterWhite' />
|
||||
<div className='-mt-5'>{noDataMessage}</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -251,14 +251,14 @@ const Table = <T extends RowDataType>({
|
||||
}
|
||||
return data.map((item, rowIndex) => {
|
||||
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';
|
||||
const bgColor = isUnread ? 'bg-surface' : 'bg-muted';
|
||||
const hoverColor = isUnread ? 'hover:bg-muted' : 'hover:bg-accent';
|
||||
const fontWeight = isUnread ? 'font-semibold' : 'font-normal';
|
||||
|
||||
return (
|
||||
<tr
|
||||
key={item.id}
|
||||
className={`w-full md:h-[64px] !h-[74px] ${bgColor} border-t border-[#EAEDF5] ${hoverColor} ${onRowClick ? 'cursor-pointer' : ''} ${getRowClassName(item, rowIndex)}`}
|
||||
className={`w-full md:h-[64px] !h-[74px] ${bgColor} border-t border-surface ${hoverColor} ${onRowClick ? 'cursor-pointer' : ''} ${getRowClassName(item, rowIndex)}`}
|
||||
onClick={() => handleRowClick(item)}
|
||||
>
|
||||
{selectable && (
|
||||
@@ -318,7 +318,7 @@ const Table = <T extends RowDataType>({
|
||||
if (!showHeader || actionsPosition === 'header-replace') return null;
|
||||
|
||||
return (
|
||||
<thead className={`h-[60px] md:h-[69px] bg-white text-sm text-[#8C90A3] ${headerClassName}`}>
|
||||
<thead className={`h-[60px] md:h-[69px] bg-surface text-sm text-muted-foreground ${headerClassName}`}>
|
||||
<tr>
|
||||
{selectable && (
|
||||
<th
|
||||
@@ -349,7 +349,7 @@ const Table = <T extends RowDataType>({
|
||||
if (!actions && (!selectable || selectedRows.length === 0 || !selectedActions)) return null;
|
||||
|
||||
return (
|
||||
<div className="h-[64px] rounded-t-2xl md:rounded-t-3xl md:h-[74px] bg-white flex items-center px-3 md:px-6">
|
||||
<div className="h-[64px] rounded-t-2xl md:rounded-t-3xl md:h-[74px] bg-surface flex items-center px-3 md:px-6">
|
||||
<div className='flex items-center gap-4 md:gap-4'>
|
||||
{actions}
|
||||
{selectable && selectedRows.length > 0 && selectedActions && selectedActions}
|
||||
@@ -363,7 +363,7 @@ const Table = <T extends RowDataType>({
|
||||
<div className={`relative mt-6 md:mt-9 w-full ${className}`}>
|
||||
<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]">
|
||||
<div className="h-[64px] bg-surface flex items-center justify-between px-4 rounded-t-2xl border-b border-surface">
|
||||
<div className='flex items-center gap-4 md:gap-2'>
|
||||
{actions}
|
||||
{selectable && selectedRows.length > 0 && selectedActions && selectedActions}
|
||||
@@ -395,14 +395,14 @@ const Table = <T extends RowDataType>({
|
||||
|
||||
<div className={`hidden md:block`}>
|
||||
{(actionsPosition === 'top' || actionsPosition === 'above-header') && (
|
||||
<div className="h-[74px] bg-white flex items-center px-6 rounded-t-3xl">
|
||||
<div className="h-[74px] bg-surface flex items-center px-6 rounded-t-3xl">
|
||||
<div className='flex items-center gap-4'>
|
||||
{actions}
|
||||
{selectable && selectedRows.length > 0 && selectedActions && selectedActions}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className={`overflow-x-auto overflow-hidden ${(actionsPosition === 'top' || actionsPosition === 'above-header') && (actions || (selectable && selectedRows.length > 0 && selectedActions)) ? 'rounded-b-3xl' : 'rounded-3xl'} bg-white`}>
|
||||
<div className={`overflow-x-auto overflow-hidden ${(actionsPosition === 'top' || actionsPosition === 'above-header') && (actions || (selectable && selectedRows.length > 0 && selectedActions)) ? 'rounded-b-3xl' : 'rounded-3xl'} bg-surface`}>
|
||||
<table className="w-full text-sm border-collapse min-w-[600px]">
|
||||
<tbody>
|
||||
{renderTableBody()}
|
||||
@@ -428,7 +428,7 @@ const Table = <T extends RowDataType>({
|
||||
<div className={`relative mt-6 md:mt-9 w-full ${className}`}>
|
||||
{(actionsPosition === 'top' || actionsPosition === 'above-header') && renderActions()}
|
||||
|
||||
<div className={`overflow-x-auto overflow-hidden ${showHeader && actionsPosition !== 'header-replace' ? 'rounded-2xl md:rounded-3xl' : 'rounded-b-2xl md:rounded-b-3xl'} bg-white`}>
|
||||
<div className={`overflow-x-auto overflow-hidden ${showHeader && actionsPosition !== 'header-replace' ? 'rounded-2xl md:rounded-3xl' : 'rounded-b-2xl md:rounded-b-3xl'} bg-surface`}>
|
||||
<table className="w-full text-sm border-collapse min-w-[600px]">
|
||||
{renderHeader()}
|
||||
{actionsPosition === 'header-replace' && (
|
||||
|
||||
@@ -25,7 +25,7 @@ const Tabs: FC<Props> = (props: Props) => {
|
||||
<Swiper
|
||||
slidesPerView='auto'
|
||||
spaceBetween={20}
|
||||
className='px-4 md:px-8 xl:px-10 max-w-full w-fit items-center text-description mx-auto backdrop-blur-md border-2 border-white gap-4 md:gap-6 xl:gap-10 flex h-[60px] md:h-[70px] xl:h-[80px] rounded-[20px] md:rounded-[28px] xl:rounded-[32px] bg-white bg-opacity-45'>
|
||||
className='px-4 md:px-8 xl:px-10 max-w-full w-fit items-center text-description dark:text-gray-300 mx-auto backdrop-blur-md border-2 border-white dark:border-white/10 gap-4 md:gap-6 xl:gap-10 flex h-[60px] md:h-[70px] xl:h-[80px] rounded-[20px] md:rounded-[28px] xl:rounded-[32px] bg-white/45 dark:bg-white/10'>
|
||||
{
|
||||
props.items.map((item: Item, index: number) => {
|
||||
return (
|
||||
@@ -33,7 +33,7 @@ const Tabs: FC<Props> = (props: Props) => {
|
||||
'flex flex-col max-w-fit mt-[10px] md:mt-[12px] xl:mt-[15px] items-center gap-1 md:gap-2 cursor-pointer',
|
||||
index === 0 && 'pr-[15px] md:pr-[20px] xl:pr-[30px]',
|
||||
index === props.items.length - 1 && props.items.length > 3 && 'pl-[5px]',
|
||||
props.active === item.value && 'text-black'
|
||||
props.active === item.value && 'text-black dark:text-white'
|
||||
)}>
|
||||
{item.icon}
|
||||
<div className='text-[10px] md:text-xs'>
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { Moon, Sun1 } from 'iconsax-react'
|
||||
import { useTheme } from '@/contexts/ThemeContext'
|
||||
import Button from '@/components/Button'
|
||||
import { getIconColor } from '@/utils/colorUtils'
|
||||
|
||||
const ThemeToggle = () => {
|
||||
const { theme, toggleTheme } = useTheme()
|
||||
|
||||
const isDark = theme === 'dark'
|
||||
const label = isDark ? 'تغییر به حالت روشن' : 'تغییر به حالت تاریک'
|
||||
|
||||
return (
|
||||
<Button
|
||||
onClick={toggleTheme}
|
||||
variant="secondary"
|
||||
title={label}
|
||||
className={`group relative w-16 h-8 p-0 rounded-full transition-all duration-300
|
||||
bg-secondary/90 dark:bg-secondary ring-1 ring-inset ring-border/60
|
||||
hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring`}
|
||||
>
|
||||
{/* Thumb */}
|
||||
<span
|
||||
className={`pointer-events-none absolute top-1 left-1 w-6 h-6 rounded-full border border-border z-0
|
||||
bg-card shadow-[0_2px_6px_rgba(0,0,0,0.08)] dark:shadow-[0_2px_8px_rgba(0,0,0,0.35)]
|
||||
transition-transform duration-300 ease-out will-change-transform ${isDark ? 'translate-x-8' : 'translate-x-0'}`}
|
||||
/>
|
||||
|
||||
{/* Icons */}
|
||||
<span className="pointer-events-none absolute inset-0 z-10 grid grid-cols-2 place-items-center">
|
||||
<Sun1
|
||||
size={18}
|
||||
color={isDark ? getIconColor('muted') : getIconColor('primary')}
|
||||
className={`transition-opacity duration-300 ${isDark ? 'opacity-60' : 'opacity-100'}`}
|
||||
/>
|
||||
<Moon
|
||||
size={18}
|
||||
color={isDark ? getIconColor('primary') : getIconColor('muted')}
|
||||
className={`transition-opacity duration-300 ${isDark ? 'opacity-100' : 'opacity-60'}`}
|
||||
/>
|
||||
</span>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export default ThemeToggle
|
||||
@@ -53,7 +53,7 @@ const UploadBox: FC<Props> = (props: Props) => {
|
||||
<div>
|
||||
<div className='text-sm'>{props.label}</div>
|
||||
<div className='w-full h-10 mt-1 border border-border rounded-xl items-center px-2 flex gap-2.5'>
|
||||
<button {...getRootProps()} className=' w-fit h-7 rounded-lg text-xs px-6 bg-secondary'>
|
||||
<button {...getRootProps()} className=' w-fit h-7 rounded-lg text-xs px-6 bg-secondary dark:bg-[#2d2d30] dark:text-gray-200'>
|
||||
<input {...getInputProps()} />
|
||||
|
||||
{t('select_file')}
|
||||
@@ -62,9 +62,9 @@ const UploadBox: FC<Props> = (props: Props) => {
|
||||
<div className='lg:flex gap-2 hidden'>
|
||||
{
|
||||
files?.map((item, index) => (
|
||||
<div key={index} className='flex bg-gray-200 py-1.5 px-2 rounded-lg items-center gap-1'>
|
||||
<div key={index} className='flex bg-gray-200 dark:bg-[#2d2d30] py-1.5 px-2 rounded-lg items-center gap-1'>
|
||||
<CloseCircle onClick={() => handleRemove(index)} size={16} color='red' />
|
||||
<div className='text-xs'>{item.name}</div>
|
||||
<div className='text-xs dark:text-gray-300'>{item.name}</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
@@ -73,9 +73,9 @@ const UploadBox: FC<Props> = (props: Props) => {
|
||||
<div className='lg:hidden gap-2 flex flex-wrap mt-4'>
|
||||
{
|
||||
files?.map((item, index) => (
|
||||
<div key={index} className='flex bg-gray-200 py-1.5 px-2 rounded-lg items-center gap-1'>
|
||||
<div key={index} className='flex bg-gray-200 dark:bg-[#2d2d30] py-1.5 px-2 rounded-lg items-center gap-1'>
|
||||
<CloseCircle onClick={() => handleRemove(index)} size={16} color='red' />
|
||||
<div className='text-xs'>{item.name}</div>
|
||||
<div className='text-xs dark:text-gray-300'>{item.name}</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { FC, useCallback, useEffect, useState } from 'react'
|
||||
import { useDropzone } from 'react-dropzone';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { clx } from '../helpers/utils';
|
||||
import { getIconColor } from '@/utils/colorUtils';
|
||||
|
||||
type Props = {
|
||||
label: string;
|
||||
@@ -77,22 +78,22 @@ const UploadBoxDraggble: FC<Props> = (props: Props) => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div {...getRootProps()} className='w-full py-8 border border-dashed border-description flex flex-col justify-center items-center gap-4 rounded-2xl'>
|
||||
<div {...getRootProps()} className='w-full py-8 border border-dashed border-description dark:border-[#3e3e42] flex flex-col justify-center items-center gap-4 rounded-2xl'>
|
||||
<input {...getInputProps()} />
|
||||
<Gallery
|
||||
className='size-8'
|
||||
color='#8C90A3'
|
||||
color={getIconColor('muted')}
|
||||
/>
|
||||
<div className='text-description text-xs'>
|
||||
<div className='text-description dark:text-gray-400 text-xs'>
|
||||
{props.label}
|
||||
</div>
|
||||
|
||||
<div className='flex items-center gap-2'>
|
||||
<DocumentUpload
|
||||
className='size-4'
|
||||
color='black'
|
||||
color={getIconColor('primary')}
|
||||
/>
|
||||
<div className='text-xs'>{t('upload')}</div>
|
||||
<div className='text-xs dark:text-gray-300'>{t('upload')}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -114,7 +115,7 @@ const UploadBoxDraggble: FC<Props> = (props: Props) => {
|
||||
'size-10 rounded-full object-cover',
|
||||
cover === item ? 'border-2 border-red-400' : ''
|
||||
)} />
|
||||
<div onClick={() => handleDeletePreview(index)} className='absolute -left-2 -top-2 shadow-md bg-white size-5 rounded-full flex justify-center items-center'>
|
||||
<div onClick={() => handleDeletePreview(index)} className='absolute -left-2 -top-2 shadow-md bg-white dark:bg-[#252526] size-5 rounded-full flex justify-center items-center'>
|
||||
<CloseCircle className='size-4 ' color='red' />
|
||||
</div>
|
||||
</div>
|
||||
@@ -126,10 +127,10 @@ const UploadBoxDraggble: FC<Props> = (props: Props) => {
|
||||
files.map((file, index) => {
|
||||
if (props.isFile) {
|
||||
return (
|
||||
<div key={index} className='flex border p-2 rounded-lg items-center gap-2'>
|
||||
<div key={index} className='flex border dark:border-[#3e3e42] p-2 rounded-lg items-center gap-2'>
|
||||
<div className='flex relative items-center gap-2'>
|
||||
<div className='text-xs'>{file.name}</div>
|
||||
<div className='absolute -left-4 -top-4 shadow-md bg-white size-5 rounded-full flex justify-center items-center'>
|
||||
<div className='text-xs dark:text-gray-300'>{file.name}</div>
|
||||
<div className='absolute -left-4 -top-4 shadow-md bg-white dark:bg-[#252526] size-5 rounded-full flex justify-center items-center'>
|
||||
<CloseCircle onClick={() => handleDelete(index)} className='size-4 ' color='red' />
|
||||
</div>
|
||||
</div>
|
||||
@@ -141,7 +142,7 @@ const UploadBoxDraggble: FC<Props> = (props: Props) => {
|
||||
<div key={index} className='flex items-center gap-2'>
|
||||
<div key={index} className='flex relative items-center gap-2'>
|
||||
<img src={URL.createObjectURL(file)} alt={file.name} className='size-10 rounded-full object-cover' />
|
||||
<div className='absolute -left-2 -top-2 shadow-md bg-white size-5 rounded-full flex justify-center items-center'>
|
||||
<div className='absolute -left-2 -top-2 shadow-md bg-white dark:bg-[#252526] size-5 rounded-full flex justify-center items-center'>
|
||||
<CloseCircle onClick={() => handleDelete(index)} className='size-4 ' color='red' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { CloseCircle, Paperclip2 } from 'iconsax-react'
|
||||
import { FC, useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useDropzone } from 'react-dropzone'
|
||||
import { getIconColor } from '@/utils/colorUtils'
|
||||
|
||||
|
||||
type Props = {
|
||||
@@ -36,15 +37,15 @@ const UploadButton: FC<Props> = (props) => {
|
||||
|
||||
return (
|
||||
<div className='flex flex-col gap-4 sm:flex-row'>
|
||||
<div {...getRootProps()} className='h-10 bg-[#ECEEF5] rounded-full flex gap-2 items-center px-3 cursor-pointer'>
|
||||
<div {...getRootProps()} className='h-10 bg-[#ECEEF5] dark:bg-[#2d2d30] text-black dark:text-white rounded-full flex gap-2 items-center px-3 cursor-pointer'>
|
||||
<input {...getInputProps()} />
|
||||
|
||||
<Paperclip2 size={20} color='#000' />
|
||||
<Paperclip2 size={20} color={getIconColor('primary')} />
|
||||
<div className='text-xs'>{title ? title : t('attachment_select')}</div>
|
||||
</div>
|
||||
|
||||
{files.map((file, index) => (
|
||||
<div key={file.name} className='bg-[#EBEDF5] text-xs flex gap-2 h-10 rounded-full items-center px-3'>
|
||||
<div key={file.name} className='bg-[#EBEDF5] dark:bg-[#2d2d30] text-xs dark:text-gray-300 flex gap-2 h-10 rounded-full items-center px-3'>
|
||||
<span>{file.name}</span>
|
||||
<CloseCircle size={19} color='#D52903' className='cursor-pointer' onClick={() => handleRemove(index)} />
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { FC } from 'react'
|
||||
import { EmojiHappy } from 'iconsax-react'
|
||||
import { getIconColor } from '@/utils/colorUtils'
|
||||
import Reply from './Reply'
|
||||
import Forward from './Forward'
|
||||
import { MessageDetail } from '@/pages/received/types/Types'
|
||||
@@ -21,7 +22,7 @@ const EmailActions: FC<EmailActionsProps> = ({
|
||||
{showReactions && (
|
||||
<EmojiHappy
|
||||
size={24}
|
||||
color='black'
|
||||
color={getIconColor('primary')}
|
||||
className='cursor-pointer hover:opacity-70 transition-opacity'
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -91,7 +91,7 @@ const EmailEditor: FC<EmailEditorProps> = ({
|
||||
placeholder={placeholder}
|
||||
modules={modules}
|
||||
formats={formats}
|
||||
className="bg-white rounded-lg"
|
||||
className="bg-white dark:bg-[#252526] rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -49,7 +49,7 @@ const EmailFormActions: FC<EmailFormActionsProps> = ({
|
||||
{/* Save Draft Button */}
|
||||
{onSaveDraft && (
|
||||
<Button
|
||||
className='!w-full sm:!w-fit px-6 md:px-10 bg-white text-black border border-primary order-2 sm:order-1'
|
||||
className='!w-full sm:!w-fit px-6 md:px-10 bg-white dark:bg-transparent text-black dark:text-white border border-primary order-2 sm:order-1'
|
||||
label={t('new_message.draft')}
|
||||
onClick={onSaveDraft}
|
||||
loading={isSavingDraft}
|
||||
@@ -59,7 +59,7 @@ const EmailFormActions: FC<EmailFormActionsProps> = ({
|
||||
{/* Close Button */}
|
||||
{onClose && !onSaveDraft && (
|
||||
<Button
|
||||
className='!w-full sm:!w-fit px-6 md:px-10 bg-white text-black border border-primary order-2 sm:order-1'
|
||||
className='!w-full sm:!w-fit px-6 md:px-10 bg-white dark:bg-transparent text-black dark:text-white border border-primary order-2 sm:order-1'
|
||||
label={t('common.close')}
|
||||
onClick={onClose}
|
||||
/>
|
||||
|
||||
@@ -169,11 +169,11 @@ const EmailInput: FC<EmailInputProps> = ({
|
||||
|
||||
return (
|
||||
<div className='w-full relative'>
|
||||
<div className="w-full bg-white min-h-10 text-black flex flex-wrap items-center gap-1 px-4 py-2 text-xs rounded-xl border border-border">
|
||||
<div className="w-full bg-white dark:bg-[#252526] min-h-10 text-black dark:text-white flex flex-wrap items-center gap-1 px-4 py-2 text-xs rounded-xl border border-border">
|
||||
{toEmails.map((email, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-center gap-1 bg-[#EBEDF5] text-xs h-7 rounded-full px-3"
|
||||
className="flex items-center gap-1 bg-[#EBEDF5] dark:bg-[#2d2d30] text-xs h-7 rounded-full px-3"
|
||||
>
|
||||
<span>{email}</span>
|
||||
<CloseCircle
|
||||
@@ -198,11 +198,11 @@ const EmailInput: FC<EmailInputProps> = ({
|
||||
</div>
|
||||
|
||||
{showSuggestions && filteredSuggestions.length > 0 && (
|
||||
<div className="absolute top-full left-0 right-0 bg-white border border-gray-200 rounded-lg shadow-lg z-[10000] mt-1">
|
||||
<div className="absolute top-full left-0 right-0 bg-white dark:bg-[#252526] border border-gray-200 dark:border-[#3e3e42] rounded-lg shadow-lg z-[10000] mt-1">
|
||||
{filteredSuggestions.map((suggestion, index) => (
|
||||
<div
|
||||
key={suggestion.address}
|
||||
className={`px-4 py-2 cursor-pointer text-sm hover:bg-gray-100 border-b border-gray-100 last:border-b-0 ${selectedSuggestionIndex === index ? 'bg-blue-50 text-blue-600' : 'text-gray-700'
|
||||
className={`px-4 py-2 cursor-pointer text-sm hover:bg-gray-100 dark:hover:bg-[#2d2d30] border-b border-gray-100 dark:border-[#2d2d30] last:border-b-0 ${selectedSuggestionIndex === index ? 'bg-blue-50 dark:bg-blue-900/20 text-blue-600 dark:text-blue-400' : 'text-gray-700 dark:text-gray-300'
|
||||
}`}
|
||||
onMouseDown={() => selectSuggestion(suggestion)}
|
||||
onMouseEnter={() => setSelectedSuggestionIndex(index)}
|
||||
@@ -210,7 +210,7 @@ const EmailInput: FC<EmailInputProps> = ({
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">{suggestion.name || suggestion.address}</span>
|
||||
{suggestion.name && (
|
||||
<span className="text-xs text-gray-500">{suggestion.address}</span>
|
||||
<span className="text-xs text-gray-500 dark:text-gray-400">{suggestion.address}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,6 +11,7 @@ import { useNewMessage } from './hooks/useNewMessage'
|
||||
import Intelligense from '@/assets/images/intelligense.svg'
|
||||
import { useGenerateEmail } from '@/pages/received/hooks/useEmailData'
|
||||
import { clx } from '@/helpers/utils'
|
||||
import { getIconColor } from '@/utils/colorUtils'
|
||||
|
||||
const NewMessage: FC = () => {
|
||||
const { t } = useTranslation('global')
|
||||
@@ -116,7 +117,7 @@ const NewMessage: FC = () => {
|
||||
}`}
|
||||
onClick={handleClose}
|
||||
/>
|
||||
<div className={`fixed bottom-2 inset-x-2 md:bottom-4 md:inset-x-4 xl:left-4 xl:right-auto xl:bottom-4 bg-white rounded-2xl md:rounded-3xl shadow-[0_0_20px_rgba(0,0,0,0.1)] z-[9999] p-4 md:p-6 xl:p-8 xl:w-[800px] max-h-[90vh] overflow-y-auto transition-transform duration-300 ease-out ${isClosing ? 'translate-y-[120%]' : isOpening ? 'translate-y-full' : 'translate-y-0'
|
||||
<div className={`fixed bottom-2 inset-x-2 md:bottom-4 md:inset-x-4 xl:left-4 xl:right-auto xl:bottom-4 bg-white dark:bg-[#252526] rounded-2xl md:rounded-3xl shadow-[0_0_20px_rgba(0,0,0,0.1)] z-[9999] p-4 md:p-6 xl:p-8 xl:w-[800px] max-h-[90vh] overflow-y-auto transition-transform duration-300 ease-out ${isClosing ? 'translate-y-[120%]' : isOpening ? 'translate-y-full' : 'translate-y-0'
|
||||
}`}>
|
||||
{/* Header */}
|
||||
<div className='flex justify-between items-center mb-6 md:mb-8'>
|
||||
@@ -124,7 +125,7 @@ const NewMessage: FC = () => {
|
||||
{t('new_message.title')}
|
||||
</div>
|
||||
<div onClick={handleClose} className='cursor-pointer p-1'>
|
||||
<CloseCircle size={20} className='md:w-6 md:h-6' color='#292D32' />
|
||||
<CloseCircle size={20} className='md:w-6 md:h-6' color={getIconColor('primary')} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import { createContext, useContext, useEffect, useState, ReactNode } from 'react'
|
||||
|
||||
type Theme = 'light' | 'dark'
|
||||
|
||||
interface ThemeContextType {
|
||||
theme: Theme
|
||||
setTheme: (theme: Theme) => void
|
||||
toggleTheme: () => void
|
||||
}
|
||||
|
||||
const ThemeContext = createContext<ThemeContextType | undefined>(undefined)
|
||||
|
||||
export const useTheme = () => {
|
||||
const context = useContext(ThemeContext)
|
||||
if (context === undefined) {
|
||||
throw new Error('useTheme must be used within a ThemeProvider')
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
interface ThemeProviderProps {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export const ThemeProvider = ({ children }: ThemeProviderProps) => {
|
||||
const [theme, setTheme] = useState<Theme>(() => {
|
||||
// بررسی تم ذخیره شده در localStorage
|
||||
const savedTheme = localStorage.getItem('theme') as Theme
|
||||
if (savedTheme) {
|
||||
return savedTheme
|
||||
}
|
||||
|
||||
// بررسی تنظیمات سیستم
|
||||
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
return 'dark'
|
||||
}
|
||||
|
||||
return 'light'
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const root = document.documentElement
|
||||
|
||||
// حذف کلاسهای قبلی
|
||||
root.classList.remove('light', 'dark')
|
||||
|
||||
// اضافه کردن کلاس مناسب
|
||||
root.classList.add(theme)
|
||||
|
||||
// ذخیره در localStorage
|
||||
localStorage.setItem('theme', theme)
|
||||
|
||||
// بروزرسانی رنگ background در body برای CSS قدیمی
|
||||
if (theme === 'dark') {
|
||||
document.body.style.background = '#1e1e1e'
|
||||
} else {
|
||||
document.body.style.background = '#eceef6'
|
||||
}
|
||||
}, [theme])
|
||||
|
||||
const toggleTheme = () => {
|
||||
setTheme(prev => prev === 'light' ? 'dark' : 'light')
|
||||
}
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{ theme, setTheme, toggleTheme }}>
|
||||
{children}
|
||||
</ThemeContext.Provider>
|
||||
)
|
||||
}
|
||||
+168
-61
@@ -24,6 +24,7 @@ body {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
/* ریسپانسیو شدن برای صفحات کوچک */
|
||||
@@ -61,6 +62,11 @@ body,
|
||||
border-radius: 5px;
|
||||
height: 6px;
|
||||
outline: none;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.dark .slider {
|
||||
background: #2d2d30;
|
||||
}
|
||||
|
||||
.slider::-webkit-slider-thumb {
|
||||
@@ -72,6 +78,11 @@ body,
|
||||
background: #374151;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.dark .slider::-webkit-slider-thumb {
|
||||
background: #007acc;
|
||||
}
|
||||
|
||||
.slider::-moz-range-thumb {
|
||||
@@ -82,12 +93,23 @@ body,
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.dark .slider::-moz-range-thumb {
|
||||
background: #007acc;
|
||||
}
|
||||
}
|
||||
|
||||
input::placeholder,
|
||||
textarea::placeholder {
|
||||
color: #888888;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.dark input::placeholder,
|
||||
.dark textarea::placeholder {
|
||||
color: #969696;
|
||||
}
|
||||
|
||||
@theme {
|
||||
@@ -107,11 +129,23 @@ textarea::placeholder {
|
||||
width: 100% !important;
|
||||
margin: 0px;
|
||||
padding-right: 16px !important;
|
||||
transition: background-color 0.3s ease, border-color 0.3s ease;
|
||||
}
|
||||
|
||||
.dark .rmdp-input {
|
||||
background-color: #3c3c3c !important;
|
||||
border: 1px solid #3e3e42 !important;
|
||||
color: #d4d4d4 !important;
|
||||
}
|
||||
|
||||
.readOny .rmdp-input {
|
||||
background-color: #f5f5f5 !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.dark .readOny .rmdp-input {
|
||||
background-color: #252526 !important;
|
||||
}
|
||||
.rmdp-container {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -156,71 +190,108 @@ textarea::placeholder {
|
||||
|
||||
:root {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.205 0 0);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.97 0 0);
|
||||
--secondary-foreground: oklch(0.205 0 0);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
--background: #ffffff;
|
||||
--foreground: #000000;
|
||||
--card: #ffffff;
|
||||
--card-foreground: #000000;
|
||||
--popover: #ffffff;
|
||||
--popover-foreground: #000000;
|
||||
--primary: #000000;
|
||||
--primary-foreground: #ffffff;
|
||||
--secondary: #f5f5f5;
|
||||
--secondary-foreground: #000000;
|
||||
--muted: #f5f5f5;
|
||||
--muted-foreground: #888888;
|
||||
--accent: #f5f5f5;
|
||||
--accent-foreground: #000000;
|
||||
--destructive: #ef4444;
|
||||
--border: #e5e5e5;
|
||||
--input: #ffffff;
|
||||
--ring: #888888;
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.205 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.97 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.922 0 0);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
--sidebar: #ffffff;
|
||||
--sidebar-foreground: #000000;
|
||||
--sidebar-primary: #000000;
|
||||
--sidebar-primary-foreground: #ffffff;
|
||||
--sidebar-accent: #f5f5f5;
|
||||
--sidebar-accent-foreground: #000000;
|
||||
--sidebar-border: #e5e5e5;
|
||||
--sidebar-ring: #888888;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.205 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.205 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.922 0 0);
|
||||
--primary-foreground: oklch(0.205 0 0);
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.269 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.556 0 0);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.205 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.556 0 0);
|
||||
--background: #1e1e1e;
|
||||
--foreground: #d4d4d4;
|
||||
--card: #252526;
|
||||
--card-foreground: #d4d4d4;
|
||||
--popover: #252526;
|
||||
--popover-foreground: #d4d4d4;
|
||||
--primary: #ffffff;
|
||||
--primary-foreground: #1e1e1e;
|
||||
--secondary: #2d2d30;
|
||||
--secondary-foreground: #cccccc;
|
||||
--muted: #2d2d30;
|
||||
--muted-foreground: #969696;
|
||||
--accent: #37373d;
|
||||
--accent-foreground: #d4d4d4;
|
||||
--destructive: #f14c4c;
|
||||
--border: #3e3e42;
|
||||
--input: #3c3c3c;
|
||||
--ring: #ffffff;
|
||||
--chart-1: #ffffff;
|
||||
--chart-2: #f472b6;
|
||||
--chart-3: #fb7185;
|
||||
--chart-4: #f43f5e;
|
||||
--chart-5: #fb923c;
|
||||
--sidebar: #252526;
|
||||
--sidebar-foreground: #cccccc;
|
||||
--sidebar-primary: #ffffff;
|
||||
--sidebar-primary-foreground: #ffffff;
|
||||
--sidebar-accent: #2d2d30;
|
||||
--sidebar-accent-foreground: #cccccc;
|
||||
--sidebar-border: #3e3e42;
|
||||
--sidebar-ring: #ffffff;
|
||||
}
|
||||
|
||||
/* کلاسهای یکپارچه برای رنگبندی */
|
||||
.icon-color {
|
||||
@apply text-foreground;
|
||||
}
|
||||
|
||||
.icon-muted {
|
||||
@apply text-muted-foreground;
|
||||
}
|
||||
|
||||
.icon-primary {
|
||||
@apply text-primary;
|
||||
}
|
||||
|
||||
.bg-surface {
|
||||
@apply bg-card;
|
||||
}
|
||||
|
||||
.bg-surface-elevated {
|
||||
@apply bg-background;
|
||||
}
|
||||
|
||||
.text-primary-content {
|
||||
@apply text-foreground;
|
||||
}
|
||||
|
||||
.text-secondary-content {
|
||||
@apply text-muted-foreground;
|
||||
}
|
||||
|
||||
.border-surface {
|
||||
@apply border-border;
|
||||
}
|
||||
|
||||
.input-surface {
|
||||
@apply bg-input border border-border text-foreground;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
@@ -237,14 +308,34 @@ textarea::placeholder {
|
||||
font-family: irancell !important;
|
||||
direction: rtl;
|
||||
min-height: 100px !important;
|
||||
transition: background-color 0.3s ease, border-color 0.3s ease;
|
||||
}
|
||||
|
||||
.dark .ql-container {
|
||||
background-color: #3c3c3c !important;
|
||||
border-color: #3e3e42 !important;
|
||||
}
|
||||
|
||||
.ql-toolbar {
|
||||
@apply rounded-t-xl;
|
||||
transition: background-color 0.3s ease, border-color 0.3s ease;
|
||||
}
|
||||
|
||||
.dark .ql-toolbar {
|
||||
background-color: #252526 !important;
|
||||
border-color: #3e3e42 !important;
|
||||
}
|
||||
|
||||
.ql-editor {
|
||||
text-align: start !important;
|
||||
unicode-bidi: plaintext !important;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.dark .ql-editor {
|
||||
color: #d4d4d4 !important;
|
||||
}
|
||||
|
||||
.ql-editor p {
|
||||
unicode-bidi: plaintext !important;
|
||||
text-align: start !important;
|
||||
@@ -355,22 +446,33 @@ textarea::placeholder {
|
||||
.modalGlass {
|
||||
background: rgba(0, 0, 0, 0.436);
|
||||
background-blend-mode: multiply;
|
||||
|
||||
backdrop-filter: blur(7px);
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.dark .modalGlass {
|
||||
background: rgba(30, 30, 30, 0.85);
|
||||
}
|
||||
|
||||
.modalGlass2 {
|
||||
/* background: rgba(255, 255, 255, 0.502); */
|
||||
background-color: white;
|
||||
background-blend-mode: multiply;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
/* backdrop-filter: blur(5px); */
|
||||
.dark .modalGlass2 {
|
||||
background-color: #252526;
|
||||
}
|
||||
|
||||
.modalGlass3 {
|
||||
background: rgba(62, 61, 61, 0.2);
|
||||
background-blend-mode: multiply;
|
||||
|
||||
backdrop-filter: blur(44px);
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.dark .modalGlass3 {
|
||||
background: rgba(37, 37, 38, 0.7);
|
||||
}
|
||||
strong {
|
||||
font-weight: bold;
|
||||
@@ -412,3 +514,8 @@ strong {
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
.dark .filterWhite {
|
||||
filter: brightness(0) saturate(100%) invert(100%) sepia(0%) saturate(7416%)
|
||||
hue-rotate(296deg) brightness(109%) contrast(97%);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { ErrorType } from '@/helpers/types';
|
||||
import { toast } from '@/components/Toast';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import React from 'react';
|
||||
import { getIconColor } from '@/utils/colorUtils';
|
||||
|
||||
const List: FC = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -137,14 +138,14 @@ const List: FC = () => {
|
||||
</div>
|
||||
<Refresh2
|
||||
size={18}
|
||||
color='black'
|
||||
color={getIconColor('primary')}
|
||||
className={`cursor-pointer min-w-[18px] ${isFetching ? 'animate-spin-reverse' : ''}`}
|
||||
onClick={() => refetch()}
|
||||
/>
|
||||
<RowActionsDropdown actions={[
|
||||
{
|
||||
label: 'حذف همه',
|
||||
icon: <Trash size={18} color='black' />,
|
||||
icon: <Trash size={18} color={getIconColor('primary')} />,
|
||||
onClick: () => setShowModal(true)
|
||||
}
|
||||
]} />
|
||||
@@ -153,7 +154,7 @@ const List: FC = () => {
|
||||
|
||||
const selectedActions = (
|
||||
<>
|
||||
<RecoveryConvert size={18} color='black' onClick={() => handleRestoreSelected()} className="cursor-pointer" />
|
||||
<RecoveryConvert size={18} color={getIconColor('primary')} onClick={() => handleRestoreSelected()} className="cursor-pointer" />
|
||||
<Trash size={18} color='red' onClick={() => handleDeleteSelected()} className="cursor-pointer" />
|
||||
</>
|
||||
);
|
||||
@@ -199,7 +200,7 @@ const List: FC = () => {
|
||||
const getRowActions = (message: TrashMessage): RowActionItem[] => [
|
||||
{
|
||||
label: 'بازگردانی',
|
||||
icon: <RecoveryConvert size={16} color="black" />,
|
||||
icon: <RecoveryConvert size={16} color={getIconColor('primary')} />,
|
||||
onClick: () => emailActions.restore({ messageId: message.id, mailbox: message.mailbox }),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@ import { ArchiveMessage } from './types/ArchiveTypes';
|
||||
import { useEmailActions } from '@/hooks/useEmailActions';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import React from 'react';
|
||||
import { getIconColor } from '@/utils/colorUtils';
|
||||
|
||||
const List: FC = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -137,7 +138,7 @@ const List: FC = () => {
|
||||
</div>
|
||||
<Refresh2
|
||||
size={18}
|
||||
color='black'
|
||||
color={getIconColor('primary')}
|
||||
className={`cursor-pointer ${isFetching ? 'animate-spin-reverse' : ''}`}
|
||||
onClick={() => refetch()}
|
||||
/>
|
||||
@@ -146,8 +147,8 @@ const List: FC = () => {
|
||||
|
||||
const selectedActions = (
|
||||
<>
|
||||
<ArchiveSlash size={18} color='black' onClick={() => handleUnarchiveSelected()} className="cursor-pointer" />
|
||||
<Trash size={18} color='black' onClick={() => handleDeleteSelected()} className="cursor-pointer" />
|
||||
<ArchiveSlash size={18} color={getIconColor('primary')} onClick={() => handleUnarchiveSelected()} className="cursor-pointer" />
|
||||
<Trash size={18} color={getIconColor('primary')} onClick={() => handleDeleteSelected()} className="cursor-pointer" />
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -181,7 +182,7 @@ const List: FC = () => {
|
||||
const getRowActions = (message: ArchiveMessage): RowActionItem[] => [
|
||||
{
|
||||
label: 'خروج از آرشیو',
|
||||
icon: <ArchiveSlash size={16} color="black" />,
|
||||
icon: <ArchiveSlash size={16} color={getIconColor('primary')} />,
|
||||
onClick: () => emailActions.unarchive({ messageId: message.id, mailbox: message.mailbox }),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -63,18 +63,18 @@ const Login: FC = () => {
|
||||
return (
|
||||
<div className='w-full h-full flex justify-center lg:py-[75px] py-4 lg:items-center lg:px-10 px-4'>
|
||||
<div className='flex w-full max-h-[812px] max-w-[1200px] bg-secondary h-full rounded-3xl overflow-hidden'>
|
||||
<div className='flex-1 min-w-[50%] overflow-y-auto bg-white lg:px-9 px-4 py-7'>
|
||||
<div className='flex-1 min-w-[50%] overflow-y-auto bg-card lg:px-9 px-4 py-7'>
|
||||
<div className='flex-1 h-full flex flex-col'>
|
||||
<div className='flex relative flex-1 flex-col h-full justify-center'>
|
||||
<img src={LogoSmallImage} className='w-8 hidden xl:block' />
|
||||
<img src={LogoImage} className='w-44 right-0 left-0 mx-auto absolute top-10 xl:hidden' />
|
||||
<img src={LogoSmallImage} className='w-8 hidden xl:block filterWhite' />
|
||||
<img src={LogoImage} className='w-44 right-0 left-0 mx-auto absolute top-10 xl:hidden filterWhite' />
|
||||
|
||||
<div>
|
||||
<div className='mt-5'>
|
||||
<h2 className='text-2xl font-bold'>
|
||||
<h2 className='text-2xl font-bold text-primary-content'>
|
||||
{t('auth.welcome')}
|
||||
</h2>
|
||||
<p className='text-description text-sm mt-2'>
|
||||
<p className='text-secondary-content text-sm mt-2'>
|
||||
{t('auth.enter_info_login')}
|
||||
</p>
|
||||
</div>
|
||||
@@ -132,8 +132,8 @@ const Login: FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex-1 min-w-[50%] lg:flex hidden justify-center items-center'>
|
||||
<img src={LogoImage} className='h-14' />
|
||||
<div className='flex-1 min-w-[50%] lg:flex hidden justify-center items-center bg-surface'>
|
||||
<img src={LogoImage} className='h-14 filterWhite' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useCheckHasAccount, useLoginWithOtp } from '../hooks/useAuthData'
|
||||
import { ErrorType } from '../../../helpers/types'
|
||||
import { ArrowLeft } from 'iconsax-react'
|
||||
import { toast } from '../../../components/Toast'
|
||||
import { getIconColor } from '@/utils/colorUtils'
|
||||
const LoginStep1: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
@@ -56,10 +57,10 @@ const LoginStep1: FC = () => {
|
||||
return (
|
||||
<div>
|
||||
<div className='mt-5'>
|
||||
<h2 className='text-2xl font-bold'>
|
||||
<h2 className='text-2xl font-bold text-primary-content'>
|
||||
{t('auth.welcome')}
|
||||
</h2>
|
||||
<p className='text-description text-sm mt-2'>
|
||||
<p className='text-secondary-content text-sm mt-2'>
|
||||
{t('auth.enter_info_login')}
|
||||
</p>
|
||||
</div>
|
||||
@@ -91,11 +92,11 @@ const LoginStep1: FC = () => {
|
||||
onClick={() => formik.handleSubmit()}
|
||||
loading={loginWithOtp.isPending || checkHasAccount.isPending}
|
||||
/>
|
||||
<a href='https://accounts.danakcorp.com' target='_blank' className='mt-4 flex gap-1 text-sm justify-center text-description'>
|
||||
<a href='https://accounts.danakcorp.com' target='_blank' className='mt-4 flex gap-1 text-sm justify-center text-secondary-content'>
|
||||
<div>
|
||||
{t('auth.old_version')}
|
||||
</div>
|
||||
<ArrowLeft size={20} color='black' />
|
||||
<ArrowLeft size={20} color={getIconColor('muted')} />
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -95,10 +95,10 @@ const LoginStep2: FC = () => {
|
||||
return (
|
||||
<div>
|
||||
<div className='mt-5'>
|
||||
<h2 className='lg:text-2xl font-bold'>
|
||||
<h2 className='lg:text-2xl font-bold text-primary-content'>
|
||||
{t('auth.enter_otp')}
|
||||
</h2>
|
||||
<p className='text-description flex lg:flex-row flex-col lg:gap-1 gap-2 text-sm lg:mt-2 mt-3'>
|
||||
<p className='text-secondary-content flex lg:flex-row flex-col lg:gap-1 gap-2 text-sm lg:mt-2 mt-3'>
|
||||
<div className='flex gap-1'>
|
||||
<div>{t('auth.verfify_code_text_1')}</div>
|
||||
<div>{phone}</div>
|
||||
@@ -106,7 +106,7 @@ const LoginStep2: FC = () => {
|
||||
{t('auth.verfify_code_text_2')}
|
||||
</div>
|
||||
</div>
|
||||
<div onClick={() => setStepLogin(1)} className='text-black cursor-pointer'>
|
||||
<div onClick={() => setStepLogin(1)} className='text-primary-content cursor-pointer'>
|
||||
{t('auth.change_number')}
|
||||
</div>
|
||||
</p>
|
||||
@@ -129,7 +129,7 @@ const LoginStep2: FC = () => {
|
||||
type='tel'
|
||||
autoComplete="one-time-code"
|
||||
inputMode="numeric"
|
||||
className='w-full h-[50px] flex-1 mx-2 bg-white border rounded-2.5' />
|
||||
className='w-full h-[50px] flex-1 mx-2 input-surface rounded-2.5' />
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
@@ -142,12 +142,12 @@ const LoginStep2: FC = () => {
|
||||
<div className='flex mt-4 justify-end'>
|
||||
{
|
||||
value !== '00:00' ?
|
||||
<div className='flex gap-1 text-description text-xs'>
|
||||
<div className='flex gap-1 text-secondary-content text-xs'>
|
||||
<div>{value}</div>
|
||||
<div>{t('auth.counter_otp')}</div>
|
||||
</div>
|
||||
:
|
||||
<div onClick={reSend} className='flex cursor-pointer gap-1 pl-2 text-black text-sm'>
|
||||
<div onClick={reSend} className='flex cursor-pointer gap-1 pl-2 text-primary-content text-sm'>
|
||||
<div className=''>
|
||||
{t('auth.try_again')}
|
||||
</div>
|
||||
|
||||
@@ -55,10 +55,10 @@ const LoginStep3: FC = () => {
|
||||
return (
|
||||
<div>
|
||||
<div className='mt-5'>
|
||||
<h2 className='text-2xl font-bold'>
|
||||
<h2 className='text-2xl font-bold text-primary-content'>
|
||||
{t('auth.enter_password_step3')}
|
||||
</h2>
|
||||
<p className='text-description text-sm mt-2'>
|
||||
<p className='text-secondary-content text-sm mt-2'>
|
||||
{t('auth.enter_your_password')}
|
||||
</p>
|
||||
</div>
|
||||
@@ -90,10 +90,10 @@ const LoginStep3: FC = () => {
|
||||
/>
|
||||
|
||||
<div onClick={() => setStepLogin(1)} className='mt-6 cursor-pointer flex gap-2 items-center text-sm'>
|
||||
<p className='text-description'>
|
||||
<p className='text-secondary-content'>
|
||||
{t('auth.login_with_otp')}
|
||||
</p>
|
||||
<ArrowLeft size={20} color='black' />
|
||||
<ArrowLeft size={20} color={'currentColor'} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useEmailActions } from '@/hooks/useEmailActions';
|
||||
import { useSharedStore } from '@/shared/store/sharedStore';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import React from 'react';
|
||||
import { getIconColor } from '@/utils/colorUtils';
|
||||
|
||||
const List: FC = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -155,7 +156,7 @@ const List: FC = () => {
|
||||
</div>
|
||||
<Refresh2
|
||||
size={18}
|
||||
color='black'
|
||||
color={getIconColor('primary')}
|
||||
className={`cursor-pointer ${isFetching ? 'animate-spin-reverse' : ''}`}
|
||||
onClick={() => refetch()}
|
||||
/>
|
||||
@@ -166,19 +167,19 @@ const List: FC = () => {
|
||||
<>
|
||||
<Send
|
||||
size={18}
|
||||
color='black'
|
||||
color={getIconColor('primary')}
|
||||
onClick={() => handleSendSelected()}
|
||||
className="cursor-pointer"
|
||||
/>
|
||||
<Edit
|
||||
size={18}
|
||||
color='black'
|
||||
color={getIconColor('primary')}
|
||||
onClick={() => handleEditSelected()}
|
||||
className="cursor-pointer"
|
||||
/>
|
||||
<Trash
|
||||
size={18}
|
||||
color='black'
|
||||
color={getIconColor('primary')}
|
||||
onClick={() => handleDeleteSelected()}
|
||||
className="cursor-pointer"
|
||||
/>
|
||||
@@ -243,12 +244,12 @@ const List: FC = () => {
|
||||
const getRowActions = (message: DraftMessage): RowActionItem[] => [
|
||||
{
|
||||
label: 'ارسال',
|
||||
icon: <Send size={16} color="black" />,
|
||||
icon: <Send size={16} color={getIconColor('primary')} />,
|
||||
onClick: () => handleSendDraft(message.id),
|
||||
},
|
||||
{
|
||||
label: 'ویرایش',
|
||||
icon: <Edit size={16} color="black" />,
|
||||
icon: <Edit size={16} color={getIconColor('primary')} />,
|
||||
onClick: () => handleEditDraft(message.id),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -11,6 +11,7 @@ import { useEmailActions } from '@/hooks/useEmailActions';
|
||||
import Favorite from '../received/Components/Favorite';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import React from 'react';
|
||||
import { getIconColor } from '@/utils/colorUtils';
|
||||
|
||||
const List: FC = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -138,7 +139,7 @@ const List: FC = () => {
|
||||
</div>
|
||||
<Refresh2
|
||||
size={18}
|
||||
color='black'
|
||||
color={getIconColor('primary')}
|
||||
className={`cursor-pointer ${isFetching ? 'animate-spin-reverse' : ''}`}
|
||||
onClick={() => refetch()}
|
||||
/>
|
||||
@@ -148,8 +149,8 @@ const List: FC = () => {
|
||||
const selectedActions = (
|
||||
<>
|
||||
<Star1 variant='Bold' size={18} color="#FFC107" onClick={() => handleUnfavoriteSelected()} className="cursor-pointer" />
|
||||
<Archive size={18} color='black' onClick={() => handleArchiveSelected()} className="cursor-pointer" />
|
||||
<Trash size={18} color='black' onClick={() => handleDeleteSelected()} className="cursor-pointer" />
|
||||
<Archive size={18} color={getIconColor('primary')} onClick={() => handleArchiveSelected()} className="cursor-pointer" />
|
||||
<Trash size={18} color={getIconColor('primary')} onClick={() => handleDeleteSelected()} className="cursor-pointer" />
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -202,7 +203,7 @@ const List: FC = () => {
|
||||
},
|
||||
{
|
||||
label: 'بایگانی',
|
||||
icon: <Archive size={16} color="black" />,
|
||||
icon: <Archive size={16} color={getIconColor('primary')} />,
|
||||
onClick: () => emailActions.archive({ messageId: message.id, mailbox: message.mailbox }),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { CloseCircle, Notification } from 'iconsax-react';
|
||||
import { FC, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { getIconColor } from '@/utils/colorUtils';
|
||||
import { clx } from '../../helpers/utils';
|
||||
import StatusCircle from '../../components/StatusCircle';
|
||||
import { useOutsideClick } from '../../hooks/useOutSideClick';
|
||||
@@ -81,7 +82,7 @@ const Notifications: FC = () => {
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<div onClick={() => setShowModal(!showModal)} className='relative cursor-pointer'>
|
||||
<Notification size={18} color="black" />
|
||||
<Notification size={18} color={getIconColor('primary')} />
|
||||
<div className="absolute top-0 right-0 -mt-1 -mr-1 rounded-full bg-red-500 text-white text-[8px] size-3 flex pt-0.5 pl-[1px] justify-center items-center">
|
||||
{data?.pages[0]?.data?.notificationCount}
|
||||
</div>
|
||||
@@ -91,19 +92,19 @@ const Notifications: FC = () => {
|
||||
<div id='notificationsContainer' className="xl:h-[calc(100%-110px)] h-[70%] p-6 z-[300] xl:w-[300px] w-full xl:left-7 left-0 xl:top-[90px] top-0 overflow-y-auto xl:rounded-3xl rounded-b-3xl rounded-t-none fixed modalGlass3 ">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>{t('notif.natification')}</div>
|
||||
<div className="size-7 rounded-full bg-white/30 flex justify-center items-center">
|
||||
<CloseCircle size={20} color="black" onClick={() => setShowModal(false)} />
|
||||
<div className="size-7 rounded-full bg-white/30 dark:bg-white/10 flex justify-center items-center">
|
||||
<CloseCircle size={20} color={getIconColor('primary')} onClick={() => setShowModal(false)} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div className="mt-6 flex h-8 border border-white border-opacity-35 text-xs rounded-lg overflow-hidden">
|
||||
<div className="mt-6 flex h-8 border border-white border-opacity-35 dark:border-white/20 text-xs rounded-lg overflow-hidden">
|
||||
<div
|
||||
onClick={() => setActiveTab('unread')}
|
||||
className={clx(
|
||||
'flex-1 border-l cursor-pointer text-white border-white border-opacity-70 bg-transparent flex justify-center items-center',
|
||||
activeTab === 'unread' ? 'bg-white/70 text-black' : ''
|
||||
activeTab === 'unread' ? 'bg-white/70 text-black dark:bg-white/15 dark:text-white' : ''
|
||||
)}
|
||||
>
|
||||
خوانده نشده
|
||||
@@ -112,7 +113,7 @@ const Notifications: FC = () => {
|
||||
onClick={() => setActiveTab('read')}
|
||||
className={clx(
|
||||
'flex-1 cursor-pointer text-white border-white bg-transparent flex justify-center items-center',
|
||||
activeTab === 'read' ? 'bg-white/70 text-black' : ''
|
||||
activeTab === 'read' ? 'bg-white/70 text-black dark:bg-white/15 dark:text-white' : ''
|
||||
)}
|
||||
>
|
||||
خوانده شده
|
||||
@@ -124,7 +125,7 @@ const Notifications: FC = () => {
|
||||
loading={readAll.isPending}
|
||||
onClick={handleAllRead}
|
||||
className={clx(
|
||||
'flex-1 border bg-transparent text-xs h-7 rounded-md cursor-pointer text-white border-white border-opacity-70 flex justify-center items-center',
|
||||
'flex-1 border bg-transparent text-xs h-7 rounded-md cursor-pointer text-white dark:text-white border-white border-opacity-70 dark:border-white/40 flex justify-center items-center',
|
||||
)}
|
||||
>
|
||||
{t('notif.all_read')}
|
||||
@@ -136,12 +137,12 @@ const Notifications: FC = () => {
|
||||
dataLength={posts.length}
|
||||
next={fetchNextPage}
|
||||
hasMore={hasNextPage}
|
||||
loader={<div className='flex justify-center'><MoonLoader color="black" size={20} /></div>}
|
||||
loader={<div className='flex justify-center'><MoonLoader color={getIconColor('primary')} size={20} /></div>}
|
||||
scrollableTarget="notificationsContainer"
|
||||
>
|
||||
{
|
||||
posts.map((item: NotificationItemType) => (
|
||||
<div onClick={() => handleRedirect(item.type)} className="bg-white cursor-pointer h-fit gap-4 items-start rounded-xl bg-opacity-60 p-3 mb-4 flex" key={item.id}>
|
||||
<div onClick={() => handleRedirect(item.type)} className="bg-white/60 dark:bg-white/10 dark:text-white cursor-pointer h-fit gap-4 items-start rounded-xl p-3 mb-4 flex" key={item.id}>
|
||||
{
|
||||
!item.isRead &&
|
||||
<div className="mt-1">
|
||||
@@ -150,7 +151,7 @@ const Notifications: FC = () => {
|
||||
}
|
||||
<div>
|
||||
<div className="truncate max-w-[200px]">{item.message}</div>
|
||||
<div className="mt-2 flex gap-2 text-[10px] text-description">
|
||||
<div className="mt-2 flex gap-2 text-[10px] text-description dark:text-gray-400">
|
||||
<div>{timeAgo(item.createdAt)}</div>
|
||||
<div className="flex gap-1 items-center">
|
||||
<StatusCircle color="#8C90A3" />
|
||||
|
||||
@@ -100,13 +100,13 @@ const Profile: FC = () => {
|
||||
getProfile.isPending ?
|
||||
<div></div>
|
||||
:
|
||||
<div className='bg-white rounded-3xl xl:p-6 p-4 mt-8 text-sm'>
|
||||
<div className='bg-card border border-border rounded-3xl xl:p-6 p-4 mt-8 text-sm'>
|
||||
<div className='xl:mt-10 mt-4 flex xl:flex-row flex-col xl:gap-0 gap-7 xl:items-center border-b pb-7'>
|
||||
<div className='flex-1'>
|
||||
<div>
|
||||
{t('profile.image_profile')}
|
||||
</div>
|
||||
<div className='text-description text-xs mt-2'>
|
||||
<div className='text-muted-foreground text-xs mt-2'>
|
||||
{t('profile.image_profile_desc')}
|
||||
</div>
|
||||
</div>
|
||||
@@ -123,7 +123,7 @@ const Profile: FC = () => {
|
||||
<div>{t('profile.upload_image')}</div>
|
||||
</div>
|
||||
</Button>
|
||||
<p className='mt-3 items-center flex gap-0.5 text-description'>
|
||||
<p className='mt-3 items-center flex gap-0.5 text-muted-foreground'>
|
||||
{t('profile.formats')}
|
||||
<div className='-mt-0.5'>{t('profile.format_image')}</div>
|
||||
<span>{t('profile.max_size')}</span>
|
||||
@@ -137,7 +137,7 @@ const Profile: FC = () => {
|
||||
<div>
|
||||
{t('profile.info_account')}
|
||||
</div>
|
||||
<div className='text-description text-xs mt-2'>
|
||||
<div className='text-muted-foreground text-xs mt-2'>
|
||||
{t('profile.auth_after_change_info')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -41,7 +41,7 @@ const Email: FC<Props> = (props: Props) => {
|
||||
/>
|
||||
{
|
||||
isReadOnly ?
|
||||
<div onClick={() => setIsReadOnly(false)} className='flex cursor-pointer relative -top-3 gap-1 text-[#0047FF] text-xs items-center'>
|
||||
<div onClick={() => setIsReadOnly(false)} className='flex cursor-pointer relative -top-3 gap-1 text-primary text-xs items-center'>
|
||||
{/* <Edit className='xl:size-[18px] size-4' color='#0047FF' />
|
||||
<div>
|
||||
{t('edit')}
|
||||
|
||||
@@ -65,7 +65,7 @@ const Phone: FC<Props> = (props: Props) => {
|
||||
/>
|
||||
{
|
||||
isReadOnly ?
|
||||
<div onClick={() => setIsReadOnly(false)} className='flex cursor-pointer relative -top-3 gap-1 text-[#0047FF] text-xs items-center'>
|
||||
<div onClick={() => setIsReadOnly(false)} className='flex cursor-pointer relative -top-3 gap-1 text-primary text-xs items-center'>
|
||||
{/* <Edit className='xl:size-[18px] size-4' color='#0047FF' />
|
||||
<div>
|
||||
{t('edit')}
|
||||
@@ -114,7 +114,7 @@ const Phone: FC<Props> = (props: Props) => {
|
||||
<div className='mt-14 flex justify-end border-t border-border pt-8'>
|
||||
<div className='flex gap-5'>
|
||||
<Button
|
||||
className='bg-white bg-opacity-40 text-description w-[150px] text-xs'
|
||||
className='bg-card text-muted-foreground w-[150px] text-xs border border-border'
|
||||
label='لغو'
|
||||
onClick={() => setShowModal(false)}
|
||||
/>
|
||||
|
||||
@@ -91,7 +91,7 @@ const Username: FC<Props> = (props: Props) => {
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div className='flex relative gap-1 text-[#0047FF] text-xs items-center'>
|
||||
<div className='flex relative gap-1 text-primary text-xs items-center'>
|
||||
{
|
||||
!canEdit ?
|
||||
<div onClick={() => setCanEdit(true)} className='flex gap-1 cursor-pointer relative -top-3'>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Button from '@/components/Button'
|
||||
import { ArrowRight, DocumentDownload, Printer } from 'iconsax-react'
|
||||
import { getIconColor } from '@/utils/colorUtils'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@@ -124,7 +125,7 @@ const Header: FC<{
|
||||
return (
|
||||
<div className='flex justify-between items-center border-b border-border pb-6'>
|
||||
<div className='flex flex-1 lg:gap-4'>
|
||||
<ArrowRight size={18} color='black' onClick={() => navigate(-1)} />
|
||||
<ArrowRight size={18} color={getIconColor('primary')} onClick={() => navigate(-1)} />
|
||||
<div className='flex xl:gap-4 gap-4 flex-1 lg:justify-start justify-center '>
|
||||
<MessageUnread />
|
||||
<MessageSpam isActiveSpam={mailBoxName === MailboxEnum.Junk} />
|
||||
@@ -143,7 +144,7 @@ const Header: FC<{
|
||||
onClick={handleDownloadEmail}
|
||||
>
|
||||
<div className='flex gap-2 items-center xl:px-5'>
|
||||
<DocumentDownload size={20} color='black' />
|
||||
<DocumentDownload size={20} color={getIconColor('primary')} />
|
||||
<span className='pt-0.5 xl:block hidden'>{t('mail.download')}</span>
|
||||
</div>
|
||||
</Button>
|
||||
@@ -152,7 +153,7 @@ const Header: FC<{
|
||||
onClick={handlePrint}
|
||||
>
|
||||
<div className='flex gap-2 items-center xl:px-5'>
|
||||
<Printer size={20} color='black' />
|
||||
<Printer size={20} color={getIconColor('primary')} />
|
||||
<span className='pt-0.5 xl:block hidden'>{t('mail.print')}</span>
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useMoveToArchive, useUnArchiveMessage } from '../hooks/useEmailData'
|
||||
import { ArchiveTick } from 'iconsax-react'
|
||||
import { getIconColor } from '@/utils/colorUtils'
|
||||
import { FC, useState } from 'react'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
|
||||
@@ -26,7 +27,7 @@ const MessageArchive: FC<{ isActiveArchive: boolean }> = ({ isActiveArchive }) =
|
||||
})
|
||||
}
|
||||
setIsArchive(!isArchive)
|
||||
}} className='lg:size-[18px] size-[20px]' variant={isArchive ? 'Bold' : 'Outline'} color='black' />
|
||||
}} className='lg:size-[18px] size-[20px]' variant={isArchive ? 'Bold' : 'Outline'} color={getIconColor('primary')} />
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { InfoCircle } from 'iconsax-react'
|
||||
import { getIconColor } from '@/utils/colorUtils'
|
||||
import { FC, useState } from 'react'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { useMoveToJunk, useMoveToNotJunk } from '../hooks/useEmailData'
|
||||
@@ -27,7 +28,7 @@ const MessageSpam: FC<{ isActiveSpam: boolean }> = ({ isActiveSpam }) => {
|
||||
})
|
||||
}
|
||||
setIsSpam(!isSpam)
|
||||
}} className='lg:size-[18px] size-[20px]' variant={isSpam ? 'Bold' : 'Outline'} color='black' />
|
||||
}} className='lg:size-[18px] size-[20px]' variant={isSpam ? 'Bold' : 'Outline'} color={getIconColor('primary')} />
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { SmsNotification } from 'iconsax-react'
|
||||
import { getIconColor } from '@/utils/colorUtils'
|
||||
import { FC, useState } from 'react'
|
||||
import { useMarkAsRead, useMarkAsUnread } from '../hooks/useEmailData'
|
||||
import { useParams } from 'react-router-dom'
|
||||
@@ -18,7 +19,7 @@ const MessageUnread: FC = () => {
|
||||
markAsRead({ messageId: id || '', mailbox: mailbox || '' })
|
||||
}
|
||||
setIsRead(!isRead)
|
||||
}} className='lg:size-[18px] size-[22px]' variant={isRead ? 'Outline' : 'Bold'} color='black' />
|
||||
}} className='lg:size-[18px] size-[22px]' variant={isRead ? 'Outline' : 'Bold'} color={getIconColor('primary')} />
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import ModalConfrim from '@/components/ModalConfrim';
|
||||
import { ErrorType } from '@/helpers/types';
|
||||
import { toast } from '@/components/Toast';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { getIconColor } from '@/utils/colorUtils';
|
||||
|
||||
const List: FC = () => {
|
||||
|
||||
@@ -159,7 +160,7 @@ const List: FC = () => {
|
||||
</div>
|
||||
<Refresh2
|
||||
size={18}
|
||||
color='black'
|
||||
color={getIconColor('primary')}
|
||||
className={`cursor-pointer min-w-[18px] ${isFetching ? 'animate-spin-reverse' : ''}`}
|
||||
onClick={() => refetch()}
|
||||
/>
|
||||
@@ -167,7 +168,7 @@ const List: FC = () => {
|
||||
<RowActionsDropdown actions={[
|
||||
{
|
||||
label: 'خواندن همه',
|
||||
icon: <img src={MarkAsRead} className='w-[18px]' />,
|
||||
icon: <img src={MarkAsRead} className='w-[18px] filterWhite' />,
|
||||
onClick: () => handleMarkAll()
|
||||
}
|
||||
]} />
|
||||
@@ -181,18 +182,18 @@ const List: FC = () => {
|
||||
<>
|
||||
<img
|
||||
src={MarkAsRead}
|
||||
className='w-[18px] cursor-pointer'
|
||||
className='w-[18px] cursor-pointer filterWhite'
|
||||
onClick={() => handleMarkAsReadSelected()}
|
||||
/>
|
||||
<ArchiveTick
|
||||
color='black'
|
||||
color={getIconColor('primary')}
|
||||
onClick={() => handleArchiveSelected()}
|
||||
className="cursor-pointer lg:size-[18px] size-[20px]"
|
||||
|
||||
/>
|
||||
<InfoCircle color='black' className="cursor-pointer lg:size-[18px] size-[20px]" />
|
||||
<InfoCircle color={getIconColor('primary')} className="cursor-pointer lg:size-[18px] size-[20px]" />
|
||||
<Star1
|
||||
color='black'
|
||||
color={getIconColor('primary')}
|
||||
className="cursor-pointer lg:size-[18px] size-[20px]"
|
||||
onClick={() => handleFavoriteSelected()}
|
||||
/>
|
||||
|
||||
@@ -10,6 +10,7 @@ import { SearchMessage } from './types/SearchTypes';
|
||||
import { useEmailActions } from '@/hooks/useEmailActions';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import React from 'react';
|
||||
import { getIconColor } from '@/utils/colorUtils';
|
||||
|
||||
const List: FC = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -143,19 +144,19 @@ const List: FC = () => {
|
||||
</div>
|
||||
<Refresh2
|
||||
size={18}
|
||||
color='black'
|
||||
color={getIconColor('primary')}
|
||||
className={`cursor-pointer ${isFetching ? 'animate-spin-reverse' : ''}`}
|
||||
onClick={() => refetch()}
|
||||
/>
|
||||
<More size={18} color='black' className='rotate-90 cursor-pointer' />
|
||||
<More size={18} color={getIconColor('primary')} className='rotate-90 cursor-pointer' />
|
||||
</div>
|
||||
);
|
||||
|
||||
const selectedActions = (
|
||||
<>
|
||||
<Star size={18} color='black' onClick={() => handleFavoriteSelected()} className="cursor-pointer" />
|
||||
<Archive size={18} color='black' onClick={() => handleArchiveSelected()} className="cursor-pointer" />
|
||||
<Trash size={18} color='black' onClick={() => handleDeleteSelected()} className="cursor-pointer" />
|
||||
<Star size={18} color={getIconColor('primary')} onClick={() => handleFavoriteSelected()} className="cursor-pointer" />
|
||||
<Archive size={18} color={getIconColor('primary')} onClick={() => handleArchiveSelected()} className="cursor-pointer" />
|
||||
<Trash size={18} color={getIconColor('primary')} onClick={() => handleDeleteSelected()} className="cursor-pointer" />
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -195,12 +196,12 @@ const List: FC = () => {
|
||||
const getRowActions = (message: SearchMessage): RowActionItem[] => [
|
||||
{
|
||||
label: 'اضافه به علاقهمندیها',
|
||||
icon: <Star size={16} color="black" />,
|
||||
icon: <Star size={16} color={getIconColor('primary')} />,
|
||||
onClick: () => emailActions.favorite({ messageId: message.id, mailbox: message.mailbox }),
|
||||
},
|
||||
{
|
||||
label: 'بایگانی',
|
||||
icon: <Archive size={16} color="black" />,
|
||||
icon: <Archive size={16} color={getIconColor('primary')} />,
|
||||
onClick: () => emailActions.archive({ messageId: message.id, mailbox: message.mailbox }),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -11,6 +11,7 @@ import { useEmailActions } from '@/hooks/useEmailActions';
|
||||
import { RowActionItem } from '../../components/RowActionsDropdown';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import React from 'react';
|
||||
import { getIconColor } from '@/utils/colorUtils';
|
||||
|
||||
const List: FC = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -148,7 +149,7 @@ const List: FC = () => {
|
||||
</div>
|
||||
<Refresh2
|
||||
size={18}
|
||||
color='black'
|
||||
color={getIconColor('primary')}
|
||||
className={`cursor-pointer ${isFetching ? 'animate-spin-reverse' : ''}`}
|
||||
onClick={() => refetch()}
|
||||
/>
|
||||
@@ -157,9 +158,9 @@ const List: FC = () => {
|
||||
|
||||
const selectedActions = (
|
||||
<>
|
||||
<Star size={18} color='black' onClick={() => handleFavoriteSelected()} className="cursor-pointer" />
|
||||
<Archive size={18} color='black' onClick={() => handleArchiveSelected()} className="cursor-pointer" />
|
||||
<Trash size={18} color='black' onClick={() => handleDeleteSelected()} className="cursor-pointer" />
|
||||
<Star size={18} color={getIconColor('primary')} onClick={() => handleFavoriteSelected()} className="cursor-pointer" />
|
||||
<Archive size={18} color={getIconColor('primary')} onClick={() => handleArchiveSelected()} className="cursor-pointer" />
|
||||
<Trash size={18} color={getIconColor('primary')} onClick={() => handleDeleteSelected()} className="cursor-pointer" />
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -203,12 +204,12 @@ const List: FC = () => {
|
||||
const getRowActions = (message: InboxMessage): RowActionItem[] => [
|
||||
{
|
||||
label: 'اضافه به علاقهمندیها',
|
||||
icon: <Star size={16} color="black" />,
|
||||
icon: <Star size={16} color={getIconColor('primary')} />,
|
||||
onClick: () => emailActions.favorite({ messageId: message.id, mailbox: message.mailbox }),
|
||||
},
|
||||
{
|
||||
label: 'بایگانی',
|
||||
icon: <Archive size={16} color="black" />,
|
||||
icon: <Archive size={16} color={getIconColor('primary')} />,
|
||||
onClick: () => emailActions.archive({ messageId: message.id, mailbox: message.mailbox }),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ import ChangePassword from './components/ChangePassword'
|
||||
import TwoFactor from './components/TwoFactor'
|
||||
import TitleLine from '@/components/TitleLine'
|
||||
import { SettingCategoryType, SettingItemType } from './types/SettingTypes'
|
||||
import { getIconColor } from '@/utils/colorUtils'
|
||||
|
||||
const Setting: FC = () => {
|
||||
|
||||
@@ -25,17 +26,17 @@ const Setting: FC = () => {
|
||||
<Tabs
|
||||
items={[
|
||||
{
|
||||
icon: <Notification1 color={activeTab === 'notification' ? 'black' : '#8C90A3'} size={22} />,
|
||||
icon: <Notification1 color={activeTab === 'notification' ? getIconColor('primary') : getIconColor('muted')} size={22} />,
|
||||
label: t('setting.notification'),
|
||||
value: 'notification'
|
||||
},
|
||||
{
|
||||
icon: <KeySquare color={activeTab === 'password' ? 'black' : '#8C90A3'} size={22} />,
|
||||
icon: <KeySquare color={activeTab === 'password' ? getIconColor('primary') : getIconColor('muted')} size={22} />,
|
||||
label: t('setting.password'),
|
||||
value: 'password'
|
||||
},
|
||||
{
|
||||
icon: <Key color={activeTab === '2fa' ? 'black' : '#8C90A3'} size={22} />,
|
||||
icon: <Key color={activeTab === '2fa' ? getIconColor('primary') : getIconColor('muted')} size={22} />,
|
||||
label: t('setting.2fa'),
|
||||
value: '2fa'
|
||||
},
|
||||
@@ -47,7 +48,7 @@ const Setting: FC = () => {
|
||||
|
||||
{
|
||||
activeTab === 'notification' ?
|
||||
<div className='mt-10 bg-white xl:p-8 p-4 rounded-3xl'>
|
||||
<div className='mt-10 bg-white dark:bg-[#252526] xl:p-8 p-4 rounded-3xl'>
|
||||
<div>
|
||||
{t('setting.setting_email_message')}
|
||||
</div>
|
||||
|
||||
@@ -47,7 +47,7 @@ const ChangePassword: FC = () => {
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='mt-10 bg-white p-8 rounded-3xl'>
|
||||
<div className='mt-10 bg-white dark:bg-[#252526] p-8 rounded-3xl'>
|
||||
<div>
|
||||
{t('setting.change_password')}
|
||||
</div>
|
||||
@@ -94,7 +94,7 @@ const ChangePassword: FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex-1 xl:p-8 p-4 bg-[#EEF0F7] rounded-3xl text-sm'>
|
||||
<div className='flex-1 xl:p-8 p-4 bg-[#EEF0F7] dark:bg-[#2d2d30] dark:text-gray-300 rounded-3xl text-sm'>
|
||||
<div>
|
||||
{t('setting.password_is')}
|
||||
</div>
|
||||
|
||||
@@ -35,12 +35,12 @@ const Enabled: FC = () => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='mt-10 bg-white xl:p-8 p-4 rounded-3xl'>
|
||||
<div className='mt-10 bg-white dark:bg-[#252526] xl:p-8 p-4 rounded-3xl'>
|
||||
<div className='text-center py-8'>
|
||||
<div className='inline-flex items-center justify-center w-16 h-16 bg-green-100 rounded-full mb-4'>
|
||||
<div className='inline-flex items-center justify-center w-16 h-16 bg-green-100 dark:bg-green-900/30 rounded-full mb-4'>
|
||||
<TickCircle size={32} color='#22c55e' />
|
||||
</div>
|
||||
<h2 className='text-lg font-semibold text-green-600 mb-2'>
|
||||
<h2 className='text-lg font-semibold text-green-600 dark:text-green-400 mb-2'>
|
||||
{t('setting.2fa_enabled_successfully')}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
@@ -79,7 +79,7 @@ const TwoFactor: FC = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='mt-10 bg-white xl:p-8 p-4 rounded-3xl'>
|
||||
<div className='mt-10 bg-white dark:bg-[#252526] xl:p-8 p-4 rounded-3xl'>
|
||||
<div className='mb-6 border-b border-border pb-4'>
|
||||
<h2>
|
||||
{t('setting.2fa_setup')}
|
||||
@@ -94,10 +94,10 @@ const TwoFactor: FC = () => {
|
||||
<div className='flex-1'>
|
||||
{!qrData ? (
|
||||
<div className='text-center py-8'>
|
||||
<div className='inline-flex items-center justify-center w-16 h-16 bg-blue-100 rounded-full mb-4'>
|
||||
<div className='inline-flex items-center justify-center w-16 h-16 bg-blue-100 dark:bg-blue-900/30 rounded-full mb-4'>
|
||||
<Key size={32} color='#3b82f6' />
|
||||
</div>
|
||||
<p className='text-sm text-gray-600 mb-6'>
|
||||
<p className='text-sm text-gray-600 dark:text-gray-400 mb-6'>
|
||||
{t('setting.2fa_setup_description')}
|
||||
</p>
|
||||
<Button
|
||||
@@ -119,7 +119,7 @@ const TwoFactor: FC = () => {
|
||||
{t('setting.scan_qr_code')}
|
||||
</h3>
|
||||
<div className='flex justify-center mb-4'>
|
||||
<div className='p-4 bg-white border-2 border-gray-200 rounded-xl shadow-sm'>
|
||||
<div className='p-4 bg-white dark:bg-[#2d2d30] border-2 border-gray-200 dark:border-[#3e3e42] rounded-xl shadow-sm'>
|
||||
<img
|
||||
src={qrData.qr}
|
||||
alt="QR Code"
|
||||
@@ -127,9 +127,9 @@ const TwoFactor: FC = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='text-xs text-gray-500 bg-gray-50 p-3 rounded-lg'>
|
||||
<div className='text-xs text-gray-500 dark:text-gray-400 bg-gray-50 dark:bg-[#2d2d30] p-3 rounded-lg'>
|
||||
<div className='font-medium mb-1'>{t('setting.manual_entry')}:</div>
|
||||
<div onClick={() => handleCopySeed(qrData.seed)} className='font-mono break-all text-gray-700 cursor-pointer'>{qrData.seed}</div>
|
||||
<div onClick={() => handleCopySeed(qrData.seed)} className='font-mono break-all text-gray-700 dark:text-gray-300 cursor-pointer'>{qrData.seed}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -162,39 +162,39 @@ const TwoFactor: FC = () => {
|
||||
</div>
|
||||
|
||||
{/* Information Section */}
|
||||
<div className='flex-1 xl:p-8 p-4 bg-[#EEF0F7] rounded-3xl text-sm'>
|
||||
<div className='flex-1 xl:p-8 p-4 bg-[#EEF0F7] dark:bg-[#2d2d30] rounded-3xl text-sm'>
|
||||
<div className='mb-4'>
|
||||
<h3 className='font-medium mb-2'>{t('setting.2fa_info_title')}</h3>
|
||||
<p className='text-xs text-gray-600'>
|
||||
<p className='text-xs text-gray-600 dark:text-gray-400'>
|
||||
{t('setting.2fa_info_description')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className='space-y-3'>
|
||||
<div className='flex items-start gap-3'>
|
||||
<div className='w-6 h-6 bg-blue-100 rounded-full flex items-center justify-center flex-shrink-0 mt-0.5'>
|
||||
<span className='text-xs font-medium text-blue-600'>1</span>
|
||||
<div className='w-6 h-6 bg-blue-100 dark:bg-blue-900/30 rounded-full flex items-center justify-center flex-shrink-0 mt-0.5'>
|
||||
<span className='text-xs font-medium text-blue-600 dark:text-blue-400'>1</span>
|
||||
</div>
|
||||
<p className='text-xs'>{t('setting.2fa_step_1')}</p>
|
||||
</div>
|
||||
|
||||
<div className='flex items-start gap-3'>
|
||||
<div className='w-6 h-6 bg-blue-100 rounded-full flex items-center justify-center flex-shrink-0 mt-0.5'>
|
||||
<span className='text-xs font-medium text-blue-600'>2</span>
|
||||
<div className='w-6 h-6 bg-blue-100 dark:bg-blue-900/30 rounded-full flex items-center justify-center flex-shrink-0 mt-0.5'>
|
||||
<span className='text-xs font-medium text-blue-600 dark:text-blue-400'>2</span>
|
||||
</div>
|
||||
<p className='text-xs'>{t('setting.2fa_step_2')}</p>
|
||||
</div>
|
||||
|
||||
<div className='flex items-start gap-3'>
|
||||
<div className='w-6 h-6 bg-blue-100 rounded-full flex items-center justify-center flex-shrink-0 mt-0.5'>
|
||||
<span className='text-xs font-medium text-blue-600'>3</span>
|
||||
<div className='w-6 h-6 bg-blue-100 dark:bg-blue-900/30 rounded-full flex items-center justify-center flex-shrink-0 mt-0.5'>
|
||||
<span className='text-xs font-medium text-blue-600 dark:text-blue-400'>3</span>
|
||||
</div>
|
||||
<p className='text-xs'>{t('setting.2fa_step_3')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-6 p-3 bg-yellow-50 border border-yellow-200 rounded-lg'>
|
||||
<p className='text-xs text-yellow-800'>
|
||||
<div className='mt-6 p-3 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-700 rounded-lg'>
|
||||
<p className='text-xs text-yellow-800 dark:text-yellow-300'>
|
||||
<span className='font-medium'>{t('setting.note')}:</span> {t('setting.2fa_note')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -13,6 +13,7 @@ import { ErrorType } from '@/helpers/types';
|
||||
import { toast } from '@/components/Toast';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import React from 'react';
|
||||
import { getIconColor } from '@/utils/colorUtils';
|
||||
|
||||
const List: FC = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -137,14 +138,14 @@ const List: FC = () => {
|
||||
</div>
|
||||
<Refresh2
|
||||
size={18}
|
||||
color='black'
|
||||
color={getIconColor('primary')}
|
||||
className={`cursor-pointer min-w-[18px] ${isFetching ? 'animate-spin-reverse' : ''}`}
|
||||
onClick={() => refetch()}
|
||||
/>
|
||||
<RowActionsDropdown actions={[
|
||||
{
|
||||
label: 'حذف همه',
|
||||
icon: <Trash size={18} color='black' />,
|
||||
icon: <Trash size={18} color={getIconColor('primary')} />,
|
||||
onClick: () => setShowModal(true)
|
||||
}
|
||||
]} />
|
||||
@@ -153,8 +154,8 @@ const List: FC = () => {
|
||||
|
||||
const selectedActions = (
|
||||
<>
|
||||
<RecoveryConvert size={18} color='black' onClick={() => handleNotSpamSelected()} className="cursor-pointer" />
|
||||
<Trash size={18} color='black' onClick={() => handleDeleteSelected()} className="cursor-pointer" />
|
||||
<RecoveryConvert size={18} color={getIconColor('primary')} onClick={() => handleNotSpamSelected()} className="cursor-pointer" />
|
||||
<Trash size={18} color={getIconColor('primary')} onClick={() => handleDeleteSelected()} className="cursor-pointer" />
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -198,7 +199,7 @@ const List: FC = () => {
|
||||
const getRowActions = (message: SpamMessage): RowActionItem[] => [
|
||||
{
|
||||
label: 'غیر اسپم',
|
||||
icon: <RecoveryConvert size={16} color="black" />,
|
||||
icon: <RecoveryConvert size={16} color={getIconColor('primary')} />,
|
||||
onClick: () => null,
|
||||
},
|
||||
{
|
||||
|
||||
+31
-27
@@ -11,6 +11,8 @@ import SideBarItem from './SideBarItem'
|
||||
import AvatarImage from '@/assets/images/avatar_image.png'
|
||||
import { useGetProfile } from '@/pages/profile/hooks/useProfileData'
|
||||
import { useGetInbox } from '@/pages/received/hooks/useEmailData'
|
||||
import ThemeToggle from '@/components/ThemeToggle'
|
||||
import { getIconColor } from '@/utils/colorUtils'
|
||||
|
||||
const Header: FC = () => {
|
||||
|
||||
@@ -122,7 +124,7 @@ const Header: FC = () => {
|
||||
}, [pathname])
|
||||
|
||||
return (
|
||||
<div className='fixed z-1 right-4 left-4 xl:right-[285px] top-4 xl:h-16 h-12 flex items-center px-6 bg-white justify-between rounded-[32px] xl:w-[calc(100%-305px)]'>
|
||||
<div className='fixed z-1 right-4 left-4 xl:right-[285px] top-4 xl:h-16 h-12 flex items-center px-6 bg-surface justify-between rounded-[32px] xl:w-[calc(100%-305px)] border border-surface'>
|
||||
{/* Desktop Search */}
|
||||
<div className='min-w-[270px] hidden xl:block relative'>
|
||||
<div id="search-input-container">
|
||||
@@ -138,7 +140,7 @@ const Header: FC = () => {
|
||||
{showSearchResults && search && inputFocused && (
|
||||
<div
|
||||
id="search-dropdown-container"
|
||||
className='absolute z-20 mt-1 w-full bg-white rounded-xl shadow-lg max-h-80 overflow-y-auto'
|
||||
className='absolute z-20 mt-1 w-full bg-surface border border-surface rounded-xl shadow-lg max-h-80 overflow-y-auto'
|
||||
>
|
||||
{inboxData?.data?.results && inboxData.data.results.length > 0 ? (
|
||||
<div className='py-2'>
|
||||
@@ -146,7 +148,7 @@ const Header: FC = () => {
|
||||
<Link
|
||||
key={message.id}
|
||||
to={`/mail/${message.id}/${message.mailbox}`}
|
||||
className='flex items-center gap-3 px-4 py-2 hover:bg-gray-50 cursor-pointer border-b border-gray-100 last:border-b-0'
|
||||
className='flex items-center gap-3 px-4 py-2 hover:bg-accent cursor-pointer border-b border-border last:border-b-0'
|
||||
onClick={clearSearch}
|
||||
>
|
||||
<div className='flex-1 min-w-0'>
|
||||
@@ -154,18 +156,18 @@ const Header: FC = () => {
|
||||
{!message.seen && (
|
||||
<div className="w-2 h-2 rounded-full bg-blue-500 flex-shrink-0" />
|
||||
)}
|
||||
<span className='text-sm font-medium text-gray-900 truncate'>
|
||||
<span className='text-sm font-medium text-foreground truncate'>
|
||||
{message.from.name || message.from.address}
|
||||
</span>
|
||||
</div>
|
||||
<div className='text-sm text-gray-800 font-medium truncate mb-1'>
|
||||
<div className='text-sm text-foreground font-medium truncate mb-1'>
|
||||
{message.subject || 'بدون موضوع'}
|
||||
</div>
|
||||
<div className='text-xs text-gray-500 truncate'>
|
||||
<div className='text-xs text-muted-foreground truncate'>
|
||||
{message.intro}
|
||||
</div>
|
||||
</div>
|
||||
<div className='text-xs text-gray-400 flex-shrink-0'>
|
||||
<div className='text-xs text-muted-foreground flex-shrink-0'>
|
||||
{new Date(message.date).toLocaleDateString('fa-IR')}
|
||||
</div>
|
||||
</Link>
|
||||
@@ -173,7 +175,7 @@ const Header: FC = () => {
|
||||
|
||||
</div>
|
||||
) : (
|
||||
<div className='p-4 text-center text-sm text-description'>
|
||||
<div className='p-4 text-center text-sm text-muted-foreground'>
|
||||
نتیجهای یافت نشد
|
||||
</div>
|
||||
)}
|
||||
@@ -185,7 +187,7 @@ const Header: FC = () => {
|
||||
<div className={`${showMobileSearch ? 'w-full' : ''} items-center`}>
|
||||
{!showMobileSearch && (
|
||||
<div onClick={() => setOpenSidebar(!openSidebar)} className='xl:hidden block'>
|
||||
<HambergerMenu size={24} color='black' />
|
||||
<HambergerMenu size={24} color={getIconColor('primary')} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -207,7 +209,7 @@ const Header: FC = () => {
|
||||
onClick={toggleMobileSearch}
|
||||
className='p-2 flex-shrink-0 flex items-center justify-center'
|
||||
>
|
||||
<CloseCircle size={20} color='#8C90A3' />
|
||||
<CloseCircle size={20} color={getIconColor('muted')} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -217,7 +219,7 @@ const Header: FC = () => {
|
||||
{showMobileSearch && showSearchResults && search && (
|
||||
<div
|
||||
id="mobile-search-dropdown-container"
|
||||
className='fixed right-4 left-4 top-16 z-1 bg-white rounded-xl shadow-lg max-h-[calc(100vh-80px)] overflow-y-auto'
|
||||
className='fixed right-4 left-4 top-16 z-1 bg-surface border border-surface rounded-xl shadow-lg max-h-[calc(100vh-80px)] overflow-y-auto'
|
||||
>
|
||||
{inboxData?.data?.results && inboxData.data.results.length > 0 ? (
|
||||
<div className='py-2'>
|
||||
@@ -225,7 +227,7 @@ const Header: FC = () => {
|
||||
<Link
|
||||
key={message.id}
|
||||
to={`/mail/${message.id}/${message.mailbox}`}
|
||||
className='flex items-center gap-3 px-4 py-3 hover:bg-gray-50 cursor-pointer border-b border-gray-100 last:border-b-0'
|
||||
className='flex items-center gap-3 px-4 py-3 hover:bg-accent cursor-pointer border-b border-border last:border-b-0'
|
||||
onClick={clearSearchMobile}
|
||||
>
|
||||
<div className='flex-1 min-w-0'>
|
||||
@@ -233,18 +235,18 @@ const Header: FC = () => {
|
||||
{!message.seen && (
|
||||
<div className="w-2 h-2 rounded-full bg-blue-500 flex-shrink-0" />
|
||||
)}
|
||||
<span className='text-sm font-medium text-gray-900 truncate'>
|
||||
<span className='text-sm font-medium text-foreground truncate'>
|
||||
{message.from.name || message.from.address}
|
||||
</span>
|
||||
</div>
|
||||
<div className='text-sm text-gray-800 font-medium truncate mb-1'>
|
||||
<div className='text-sm text-foreground font-medium truncate mb-1'>
|
||||
{message.subject || 'بدون موضوع'}
|
||||
</div>
|
||||
<div className='text-xs text-gray-500 truncate'>
|
||||
<div className='text-xs text-muted-foreground truncate'>
|
||||
{message.intro}
|
||||
</div>
|
||||
</div>
|
||||
<div className='text-xs text-gray-400 flex-shrink-0'>
|
||||
<div className='text-xs text-muted-foreground flex-shrink-0'>
|
||||
{new Date(message.date).toLocaleDateString('fa-IR')}
|
||||
</div>
|
||||
</Link>
|
||||
@@ -252,7 +254,7 @@ const Header: FC = () => {
|
||||
|
||||
</div>
|
||||
) : (
|
||||
<div className='p-4 text-center text-sm text-description'>
|
||||
<div className='p-4 text-center text-sm text-muted-foreground'>
|
||||
نتیجهای یافت نشد
|
||||
</div>
|
||||
)}
|
||||
@@ -263,12 +265,14 @@ const Header: FC = () => {
|
||||
{/* Mobile search icon */}
|
||||
{!showMobileSearch && (
|
||||
<div onClick={toggleMobileSearch} className='xl:hidden block'>
|
||||
<SearchNormal size={20} color='black' />
|
||||
<SearchNormal size={20} color={getIconColor('primary')} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Notifications />
|
||||
|
||||
<ThemeToggle />
|
||||
|
||||
{
|
||||
data && (
|
||||
<Popover className="relative" key={popoverKey}>
|
||||
@@ -281,16 +285,16 @@ const Header: FC = () => {
|
||||
<div className='text-xs'>
|
||||
{data?.data?.user?.emailAddress}
|
||||
</div>
|
||||
<ArrowDown2 size={14} color='#8C90A3' />
|
||||
<ArrowDown2 size={14} color={getIconColor('muted')} />
|
||||
</div>
|
||||
</div>
|
||||
</PopoverButton>
|
||||
|
||||
<PopoverPanel style={{ minHeight: window.innerWidth < 1140 ? window.innerHeight : undefined }} anchor="bottom" className="flex xl:ml-6 overflow-auto flex-col gap-3 bg-white boxShadow xl:mt-7 z-30 py-4 text-xs rounded-2.5 xl:w-[300px] -mt-[50px] w-full fixed xl:h-fit h-full shadow-md">
|
||||
<PopoverPanel style={{ minHeight: window.innerWidth < 1140 ? window.innerHeight : undefined }} anchor="bottom" className="flex xl:ml-6 overflow-auto flex-col gap-3 bg-surface border border-surface boxShadow xl:mt-7 z-30 py-4 text-xs rounded-2.5 xl:w-[300px] -mt-[50px] w-full fixed xl:h-fit h-full shadow-md">
|
||||
<div className='absolute xl:hidden top-6 left-6'>
|
||||
<CloseCircle onClick={() => setPopoverKey((prevKey) => prevKey + 1)} size={20} color='black' />
|
||||
<CloseCircle onClick={() => setPopoverKey((prevKey) => prevKey + 1)} size={20} color={getIconColor('primary')} />
|
||||
</div>
|
||||
<Link to={Paths.profile} className='flex flex-col gap-2 items-center justify-center border-b border-[#EAEDF5] pb-4'>
|
||||
<Link to={Paths.profile} className='flex flex-col gap-2 items-center justify-center border-b border-border pb-4'>
|
||||
<div className='size-14 rounded-full overflow-hidden'>
|
||||
<img src={data?.data?.user?.profilePic ? data?.data?.user?.profilePic : AvatarImage} className='size-full object-cover' />
|
||||
</div>
|
||||
@@ -299,15 +303,15 @@ const Header: FC = () => {
|
||||
{data?.data?.user?.emailAddress}
|
||||
</div>
|
||||
|
||||
<div className='text-description'>
|
||||
<div className='text-muted-foreground'>
|
||||
{data?.data?.user?.email}
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<div className='pb-6 px-6 border-b border-[#EAEDF5]'>
|
||||
<div className='pb-6 px-6 border-b border-border'>
|
||||
|
||||
<SideBarItem
|
||||
icon={<ProfileCircle color={'black'} size={20} />}
|
||||
icon={<ProfileCircle color={getIconColor('primary')} size={20} />}
|
||||
title={t('header.profile')}
|
||||
isActive
|
||||
link={Paths.profile}
|
||||
@@ -315,7 +319,7 @@ const Header: FC = () => {
|
||||
/>
|
||||
|
||||
<SideBarItem
|
||||
icon={<Setting2 color={'black'} size={20} />}
|
||||
icon={<Setting2 color={getIconColor('primary')} size={20} />}
|
||||
title={t('header.setting')}
|
||||
isActive
|
||||
link={Paths.setting}
|
||||
@@ -325,7 +329,7 @@ const Header: FC = () => {
|
||||
|
||||
<div className='px-6 -mt-[2px]'>
|
||||
<SideBarItem
|
||||
icon={<Logout color={'black'} size={20} />}
|
||||
icon={<Logout color={getIconColor('primary')} size={20} />}
|
||||
title={t('sidebar.logout')}
|
||||
isActive={false}
|
||||
link={'#'}
|
||||
|
||||
+18
-17
@@ -11,6 +11,7 @@ import Button from '@/components/Button'
|
||||
import { useMailboxCount } from './hooks/useShareData'
|
||||
import { MailboxEnum } from '@/pages/received/enum/Enum'
|
||||
import { MailboxCount } from './types/SharedTypes'
|
||||
import { getIconColor } from '@/utils/colorUtils'
|
||||
|
||||
const SideBar: FC = () => {
|
||||
|
||||
@@ -38,12 +39,12 @@ const SideBar: FC = () => {
|
||||
}
|
||||
<div
|
||||
className={clx(
|
||||
'fixed xl:flex flex translate-x-[400px] xl:translate-x-0 transition-all ease-in-out opacity-0 invisible xl:visible xl:opacity-100 xl:right-2 xl:md:right-4 right-0 xl:top-2 xl:md:top-4 top-0 xl:bottom-2 xl:md:bottom-4 bottom-0 xl:rounded-[20px] xl:md:rounded-[32px] w-[250px] sm:w-[280px] xl:w-[250px] bg-white flex-col py-8 md:py-10 xl:py-12',
|
||||
'fixed xl:flex flex translate-x-[400px] xl:translate-x-0 transition-all ease-in-out opacity-0 invisible xl:visible xl:opacity-100 xl:right-2 xl:md:right-4 right-0 xl:top-2 xl:md:top-4 top-0 xl:bottom-2 xl:md:bottom-4 bottom-0 xl:rounded-[20px] xl:md:rounded-[32px] w-[250px] sm:w-[280px] xl:w-[250px] bg-surface border border-surface flex-col py-8 md:py-10 xl:py-12',
|
||||
openSidebar && 'opacity-100 visible -translate-x-[0px] flex z-40'
|
||||
)}
|
||||
>
|
||||
<div className='flex justify-center px-4'>
|
||||
<img src={LogoImage} className='w-[120px] md:w-[140px]' />
|
||||
<img src={LogoImage} className='w-[120px] md:w-[140px] filterWhite' />
|
||||
</div>
|
||||
|
||||
<div className='flex justify-center mt-10 md:mt-14 px-4'>
|
||||
@@ -55,20 +56,20 @@ const SideBar: FC = () => {
|
||||
}}
|
||||
>
|
||||
<div className='flex gap-2 items-center'>
|
||||
<Add size={18} color='black' />
|
||||
<Add size={18} color={getIconColor('primary')} />
|
||||
<div>{t('sidebar.new_message')}</div>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className='flex-1 flex flex-col h-full overflow-y-auto no-scrollbar min-h-0'>
|
||||
<div className='mt-8 md:mt-10 px-8 md:px-12 text-[#C3C7DD] text-sm font-bold'>
|
||||
<div className='mt-8 md:mt-10 px-8 md:px-12 text-secondary-content text-sm font-bold'>
|
||||
{t('sidebar.menu')}
|
||||
</div>
|
||||
|
||||
<div className='text-xs text-[#8C90A3]'>
|
||||
<div className='text-xs text-secondary-content'>
|
||||
<SideBarItem
|
||||
icon={<DirectInbox variant={isActive(Paths.received) ? 'Bold' : 'Outline'} color={isActive(Paths.received) ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
|
||||
icon={<DirectInbox variant={isActive(Paths.received) ? 'Bold' : 'Outline'} color={isActive(Paths.received) ? getIconColor('primary') : getIconColor('muted')} size={iconSizeSideBar} />}
|
||||
title={t('sidebar.received')}
|
||||
isActive={isActive(Paths.received)}
|
||||
link={Paths.received}
|
||||
@@ -76,14 +77,14 @@ const SideBar: FC = () => {
|
||||
/>
|
||||
|
||||
<SideBarItem
|
||||
icon={<DirectSend variant={isActive(Paths.sent) ? 'Bold' : 'Outline'} color={isActive(Paths.sent) ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
|
||||
icon={<DirectSend variant={isActive(Paths.sent) ? 'Bold' : 'Outline'} color={isActive(Paths.sent) ? getIconColor('primary') : getIconColor('muted')} size={iconSizeSideBar} />}
|
||||
title={t('sidebar.sent')}
|
||||
isActive={isActive(Paths.sent)}
|
||||
link={Paths.sent}
|
||||
/>
|
||||
|
||||
<SideBarItem
|
||||
icon={<Edit variant={isActive(Paths.draft) ? 'Bold' : 'Outline'} color={isActive(Paths.draft) ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
|
||||
icon={<Edit variant={isActive(Paths.draft) ? 'Bold' : 'Outline'} color={isActive(Paths.draft) ? getIconColor('primary') : getIconColor('muted')} size={iconSizeSideBar} />}
|
||||
title={t('sidebar.draft')}
|
||||
isActive={isActive(Paths.draft)}
|
||||
link={Paths.draft}
|
||||
@@ -91,34 +92,34 @@ const SideBar: FC = () => {
|
||||
/>
|
||||
|
||||
<SideBarItem
|
||||
icon={<ArchiveTick variant={isActive(Paths.archive) ? 'Bold' : 'Outline'} color={isActive(Paths.archive) ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
|
||||
icon={<ArchiveTick variant={isActive(Paths.archive) ? 'Bold' : 'Outline'} color={isActive(Paths.archive) ? getIconColor('primary') : getIconColor('muted')} size={iconSizeSideBar} />}
|
||||
title={t('sidebar.archive')}
|
||||
isActive={isActive(Paths.archive)}
|
||||
link={Paths.archive}
|
||||
/>
|
||||
|
||||
<SideBarItem
|
||||
icon={<Trash variant={isActive(Paths.trash) ? 'Bold' : 'Outline'} color={isActive(Paths.trash) ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
|
||||
icon={<Trash variant={isActive(Paths.trash) ? 'Bold' : 'Outline'} color={isActive(Paths.trash) ? getIconColor('primary') : getIconColor('muted')} size={iconSizeSideBar} />}
|
||||
title={t('sidebar.trash')}
|
||||
isActive={isActive(Paths.trash)}
|
||||
link={Paths.trash}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-8 md:mt-10 px-8 md:px-12 text-[#C3C7DD] text-sm font-bold'>
|
||||
<div className='mt-8 md:mt-10 px-8 md:px-12 text-secondary-content text-sm font-bold'>
|
||||
{t('sidebar.other')}
|
||||
</div>
|
||||
|
||||
<div className='text-xs text-[#8C90A3]'>
|
||||
<div className='text-xs text-secondary-content'>
|
||||
<SideBarItem
|
||||
icon={<Star1 variant={isActive(Paths.favorite) ? 'Bold' : 'Outline'} color={isActive(Paths.favorite) ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
|
||||
icon={<Star1 variant={isActive(Paths.favorite) ? 'Bold' : 'Outline'} color={isActive(Paths.favorite) ? getIconColor('primary') : getIconColor('muted')} size={iconSizeSideBar} />}
|
||||
title={t('sidebar.favorite')}
|
||||
isActive={isActive(Paths.favorite)}
|
||||
link={Paths.favorite}
|
||||
/>
|
||||
|
||||
<SideBarItem
|
||||
icon={<InfoCircle variant={isActive(Paths.spam) ? 'Bold' : 'Outline'} color={isActive(Paths.spam) ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
|
||||
icon={<InfoCircle variant={isActive(Paths.spam) ? 'Bold' : 'Outline'} color={isActive(Paths.spam) ? getIconColor('primary') : getIconColor('muted')} size={iconSizeSideBar} />}
|
||||
title={t('sidebar.spam')}
|
||||
isActive={isActive(Paths.spam)}
|
||||
link={Paths.spam}
|
||||
@@ -127,15 +128,15 @@ const SideBar: FC = () => {
|
||||
</div>
|
||||
|
||||
<div className='flex-1 flex flex-col justify-end mt-10 md:mt-14 pb-6 md:pb-8'>
|
||||
<div className='text-xs text-[#8C90A3]'>
|
||||
<div className='text-xs text-secondary-content'>
|
||||
<SideBarItem
|
||||
icon={<Setting2 variant={isActive(Paths.setting) ? 'Bold' : 'Outline'} color={isActive(Paths.setting) ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
|
||||
icon={<Setting2 variant={isActive(Paths.setting) ? 'Bold' : 'Outline'} color={isActive(Paths.setting) ? getIconColor('primary') : getIconColor('muted')} size={iconSizeSideBar} />}
|
||||
title={t('sidebar.settings')}
|
||||
isActive={isActive(Paths.setting)}
|
||||
link={Paths.setting}
|
||||
/>
|
||||
<SideBarItem
|
||||
icon={<Logout variant={isActive('logout') ? 'Bold' : 'Outline'} color={isActive('logout') ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
|
||||
icon={<Logout variant={isActive('logout') ? 'Bold' : 'Outline'} color={isActive('logout') ? getIconColor('primary') : getIconColor('muted')} size={iconSizeSideBar} />}
|
||||
title={t('sidebar.logout')}
|
||||
isActive={isActive('logout')}
|
||||
link={`#`}
|
||||
|
||||
@@ -28,17 +28,17 @@ const SideBarItem: FC<Props> = (props: Props) => {
|
||||
{
|
||||
!props.isWithoutLine &&
|
||||
<div className={clx(
|
||||
'w-1 bg-black h-6',
|
||||
'w-1 bg-foreground h-6',
|
||||
!props.isActive && 'invisible'
|
||||
)}></div>
|
||||
}
|
||||
<div className='flex flex-1 gap-3 items-center'>
|
||||
{props.icon}
|
||||
<div className={props.isActive || !!props.count ? 'text-black' : ''}>
|
||||
<div className={props.isActive || !!props.count ? 'text-foreground' : 'text-foreground'}>
|
||||
{props.title}
|
||||
</div>
|
||||
<div className='flex flex-1 justify-end pl-6'>
|
||||
{!!props.count && <div className='h-[18px] w-fit rounded-[6px] bg-black text-white text-xs flex items-center justify-center px-2.5 pt-0.5'>
|
||||
{!!props.count && <div className='h-[18px] w-fit rounded-[6px] bg-foreground text-background text-xs flex items-center justify-center px-2.5 pt-0.5'>
|
||||
{props.count > 100 ? '+100' : props.count}
|
||||
</div>}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* تابع برای دریافت رنگ مناسب آیکونها بر اساس theme
|
||||
* @param variant - نوع رنگ: 'primary' یا 'muted'
|
||||
* @returns رنگ مناسب برای theme فعلی
|
||||
*/
|
||||
export const getIconColor = (
|
||||
variant: "primary" | "muted" = "primary"
|
||||
): string => {
|
||||
// تشخیص theme از document
|
||||
const isDark = document.documentElement.classList.contains("dark");
|
||||
|
||||
if (variant === "muted") {
|
||||
return isDark ? "#9ca3af" : "#6b7280"; // خاکستری متوسط
|
||||
}
|
||||
|
||||
// primary color
|
||||
return isDark ? "#ffffff" : "#000000"; // سفید برای dark، سیاه برای light
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook برای استفاده در React components
|
||||
*/
|
||||
export const useIconColor = () => {
|
||||
return {
|
||||
primary: getIconColor("primary"),
|
||||
muted: getIconColor("muted"),
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user