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