fix build

This commit is contained in:
hamid zarghami
2025-06-11 01:17:22 +03:30
parent f66f1a768e
commit 7db9ec7f2f
14 changed files with 1685 additions and 38 deletions
+98
View File
@@ -0,0 +1,98 @@
import Filters, { FilterValues } from '../../components/Filters';
import { FC, ReactNode, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Table from '../../components/Table';
import { ColumnType } from '../../components/types/TableTypes';
import { InfoCircle, More, Refresh2 } from 'iconsax-react';
interface Message extends Record<string, unknown> {
id: string;
title: string | ReactNode;
sender: string;
date: string;
read: boolean;
}
const TrashList: FC = () => {
const { t } = useTranslation();
const [isLoading] = useState(false);
const messageData: Message[] = [
{ id: '1', title: <div className='text-blue-500'>پیش نویس</div>, sender: 'علی محمدی', date: '1402/05/12', read: true },
{ id: '2', title: <div className='text-blue-500'>ارسالی</div>, sender: 'زهرا احمدی', date: '1402/05/13', read: true },
{ id: '3', title: <div className='text-blue-500'>دریافت شده</div>, sender: 'مدیریت', date: '1402/05/14', read: true },
];
const columns: ColumnType<Message>[] = [
{
key: 'title',
title: 'عنوان پیام',
render: (item) => (
<div className="flex items-center">
{!item.read && <div className="w-2 h-2 rounded-full bg-blue-500 mr-2" />}
<span>{item.title}</span>
</div>
)
},
{ key: 'status', title: 'وضعیت' },
{ key: 'sender', title: 'فرستنده' },
{ key: 'date', title: 'تاریخ', align: 'center' },
];
const tableActions = (
<div className='flex items-center gap-4'>
<Refresh2 size={20} color='black' />
<More size={20} color='black' className='rotate-90' />
</div>
);
const handleFilterChange = (newFilters: FilterValues) => {
console.log('Applied filters:', newFilters);
};
return (
<div className='mt-4'>
<h1>{t('draft.title')}</h1>
<Filters
fields={[
{ type: 'date', name: 'from_date', placeholder: t('received.from_date') },
{ type: 'date', name: 'to_date', placeholder: t('received.to_date') },
{
type: 'select',
name: 'status',
placeholder: t('received.all'),
options: [
{ value: 'all', label: t('received.all') },
{ value: 'read', label: t('received.read') },
{ value: 'unread', label: t('received.unread') }
]
},
{ type: 'input', name: 'search', placeholder: t('received.search') }
]}
onChange={handleFilterChange}
className="mt-8 flex justify-between items-center"
searchField="search"
/>
<Table<Message>
columns={columns}
data={messageData}
isLoading={isLoading}
showHeader={false}
actions={tableActions}
onRowClick={(id) => console.log(`کلیک روی پیام با شناسه ${id}`)}
noDataMessage={
<div className="flex flex-col items-center py-6">
<InfoCircle size={40} className="text-gray-300 mb-2" />
<div>هیچ پیامی یافت نشد</div>
</div>
}
selectable={true}
/>
</div>
)
}
export default TrashList
+99
View File
@@ -0,0 +1,99 @@
import Filters, { FilterValues } from '../../components/Filters';
import { FC, ReactNode, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Table from '../../components/Table';
import { ColumnType } from '../../components/types/TableTypes';
import { InfoCircle, More, Refresh2 } from 'iconsax-react';
interface Message extends Record<string, unknown> {
id: string;
title: string | ReactNode;
status: ReactNode;
sender: string;
date: string;
read: boolean;
}
const ArchiveList: FC = () => {
const { t } = useTranslation();
const [isLoading] = useState(false);
const messageData: Message[] = [
{ id: '1', title: <div className='text-blue-500'>پیش نویس</div>, status: <div className='text-yellow-500'>آرشیو شده</div>, sender: 'علی محمدی', date: '1402/05/12', read: true },
{ id: '2', title: <div className='text-blue-500'>ارسالی</div>, status: <div className='text-yellow-500'>آرشیو شده</div>, sender: 'زهرا احمدی', date: '1402/05/13', read: true },
{ id: '3', title: <div className='text-blue-500'>دریافت شده</div>, status: <div className='text-yellow-500'>آرشیو شده</div>, sender: 'مدیریت', date: '1402/05/14', read: true },
];
const columns: ColumnType<Message>[] = [
{
key: 'title',
title: 'عنوان پیام',
render: (item) => (
<div className="flex items-center">
{!item.read && <div className="w-2 h-2 rounded-full bg-blue-500 mr-2" />}
<span>{item.title}</span>
</div>
)
},
{ key: 'status', title: 'وضعیت' },
{ key: 'sender', title: 'فرستنده' },
{ key: 'date', title: 'تاریخ', align: 'center' },
];
const tableActions = (
<div className='flex items-center gap-4'>
<Refresh2 size={20} color='black' />
<More size={20} color='black' className='rotate-90' />
</div>
);
const handleFilterChange = (newFilters: FilterValues) => {
console.log('Applied filters:', newFilters);
};
return (
<div className='mt-4'>
<h1>{t('draft.title')}</h1>
<Filters
fields={[
{ type: 'date', name: 'from_date', placeholder: t('received.from_date') },
{ type: 'date', name: 'to_date', placeholder: t('received.to_date') },
{
type: 'select',
name: 'status',
placeholder: t('received.all'),
options: [
{ value: 'all', label: t('received.all') },
{ value: 'read', label: t('received.read') },
{ value: 'unread', label: t('received.unread') }
]
},
{ type: 'input', name: 'search', placeholder: t('received.search') }
]}
onChange={handleFilterChange}
className="mt-8 flex justify-between items-center"
searchField="search"
/>
<Table<Message>
columns={columns}
data={messageData}
isLoading={isLoading}
showHeader={false}
actions={tableActions}
onRowClick={(id) => console.log(`کلیک روی پیام با شناسه ${id}`)}
noDataMessage={
<div className="flex flex-col items-center py-6">
<InfoCircle size={40} className="text-gray-300 mb-2" />
<div>هیچ پیامی یافت نشد</div>
</div>
}
selectable={true}
/>
</div>
)
}
export default ArchiveList
+97
View File
@@ -0,0 +1,97 @@
import Filters, { FilterValues } from '../../components/Filters';
import { FC, ReactNode, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Table from '../../components/Table';
import { ColumnType } from '../../components/types/TableTypes';
import { InfoCircle, More, Refresh2 } from 'iconsax-react';
interface Message extends Record<string, unknown> {
id: string;
title: string | ReactNode;
sender: string;
date: string;
read: boolean;
}
const List: FC = () => {
const { t } = useTranslation();
const [isLoading] = useState(false);
const messageData: Message[] = [
{ id: '1', title: <div className='text-blue-500'>پیش نویس</div>, sender: 'علی محمدی', date: '1402/05/12', read: true },
{ id: '2', title: <div className='text-blue-500'>پیش نویس</div>, sender: 'زهرا احمدی', date: '1402/05/13', read: true },
{ id: '3', title: <div className='text-blue-500'>پیش نویس</div>, sender: 'مدیریت', date: '1402/05/14', read: true },
];
const columns: ColumnType<Message>[] = [
{
key: 'title',
title: 'عنوان پیام',
render: (item) => (
<div className="flex items-center">
{!item.read && <div className="w-2 h-2 rounded-full bg-blue-500 mr-2" />}
<span>{item.title}</span>
</div>
)
},
{ key: 'sender', title: 'فرستنده' },
{ key: 'date', title: 'تاریخ', align: 'center' },
];
const tableActions = (
<div className='flex items-center gap-4'>
<Refresh2 size={20} color='black' />
<More size={20} color='black' className='rotate-90' />
</div>
);
const handleFilterChange = (newFilters: FilterValues) => {
console.log('Applied filters:', newFilters);
};
return (
<div className='mt-4'>
<h1>{t('draft.title')}</h1>
<Filters
fields={[
{ type: 'date', name: 'from_date', placeholder: t('received.from_date') },
{ type: 'date', name: 'to_date', placeholder: t('received.to_date') },
{
type: 'select',
name: 'status',
placeholder: t('received.all'),
options: [
{ value: 'all', label: t('received.all') },
{ value: 'read', label: t('received.read') },
{ value: 'unread', label: t('received.unread') }
]
},
{ type: 'input', name: 'search', placeholder: t('received.search') }
]}
onChange={handleFilterChange}
className="mt-8 flex justify-between items-center"
searchField="search"
/>
<Table<Message>
columns={columns}
data={messageData}
isLoading={isLoading}
showHeader={false}
actions={tableActions}
onRowClick={(id) => console.log(`کلیک روی پیام با شناسه ${id}`)}
noDataMessage={
<div className="flex flex-col items-center py-6">
<InfoCircle size={40} className="text-gray-300 mb-2" />
<div>هیچ پیامی یافت نشد</div>
</div>
}
selectable={true}
/>
</div>
)
}
export default List
+31 -10
View File
@@ -4,30 +4,55 @@ import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { SettingTabEnum } from './enum/SettingEnum'
import Personality from './personality/Personality'
import Domain from './domain/Domain'
import MailServer from './mail-server/MailServer'
import Address from './address/Address'
import PersonalitySidebar from './personality/Setting'
const Setting: FC = () => {
const { t } = useTranslation()
const [activeTab, setActiveTab] = useState<SettingTabEnum>(SettingTabEnum.SETTING_PERSONALITY)
const renderActiveTab = () => {
switch (activeTab) {
case SettingTabEnum.SETTING_MAIL_SERVER:
return <MailServer />;
case SettingTabEnum.SETTING_DOMAIN:
return <Domain />;
case SettingTabEnum.SETTING_ADDRESS:
return <Address />;
case SettingTabEnum.SETTING_PERSONALITY:
return (
<div className='flex flex-row-reverse gap-6 mt-8'>
<PersonalitySidebar />
<Personality />
</div>
);
default:
return null;
}
};
return (
<div className='mt-4'>
<h1>{t('setting.title')}</h1>
<h1 className='text-2xl font-bold text-right'>{t('setting.title')}</h1>
<div className='mt-7'>
<Tabs
items={[
{
icon: <Setting3 size={22} color={activeTab === SettingTabEnum.SETTING_MAIL_SERVER ? 'black' : '#8C90A3'} />,
label: t('setting.address'),
label: t('setting.mail_server'),
value: SettingTabEnum.SETTING_MAIL_SERVER
},
{
icon: <Global size={22} color={activeTab === SettingTabEnum.SETTING_DOMAIN ? 'black' : '#8C90A3'} />,
label: t('setting.mail_server'),
label: t('setting.domain'),
value: SettingTabEnum.SETTING_DOMAIN
},
{
icon: <People size={22} color={activeTab === SettingTabEnum.SETTING_ADDRESS ? 'black' : '#8C90A3'} />,
label: t('setting.domain'),
label: t('setting.address'),
value: SettingTabEnum.SETTING_ADDRESS
},
{
@@ -41,12 +66,8 @@ const Setting: FC = () => {
/>
</div>
<div className='mt-10'>
{
activeTab === SettingTabEnum.SETTING_PERSONALITY ?
<Personality />
: null
}
<div>
{renderActiveTab()}
</div>
</div>
)
+244
View File
@@ -0,0 +1,244 @@
import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Add, Edit2, Trash } from 'iconsax-react'
interface EmailAddress {
id: number
email: string
name: string
isPrimary: boolean
}
const Address: FC = () => {
const { t } = useTranslation()
const [addresses, setAddresses] = useState<EmailAddress[]>([
{ id: 1, email: 'user@example.com', name: 'Main Account', isPrimary: true }
])
const [showAddForm, setShowAddForm] = useState(false)
const [newEmail, setNewEmail] = useState('')
const [newName, setNewName] = useState('')
const [editingId, setEditingId] = useState<number | null>(null)
const handleAddAddress = () => {
if (newEmail && newName) {
const newId = addresses.length > 0 ? Math.max(...addresses.map(a => a.id)) + 1 : 1
setAddresses([...addresses, {
id: newId,
email: newEmail,
name: newName,
isPrimary: addresses.length === 0
}])
setNewEmail('')
setNewName('')
setShowAddForm(false)
}
}
const handleEditStart = (address: EmailAddress) => {
setEditingId(address.id)
setNewEmail(address.email)
setNewName(address.name)
}
const handleEditSave = () => {
if (editingId && newEmail && newName) {
setAddresses(addresses.map(addr =>
addr.id === editingId
? { ...addr, email: newEmail, name: newName }
: addr
))
setEditingId(null)
setNewEmail('')
setNewName('')
}
}
const handleSetPrimary = (id: number) => {
setAddresses(addresses.map(addr => ({
...addr,
isPrimary: addr.id === id
})))
}
const handleDelete = (id: number) => {
const filteredAddresses = addresses.filter(addr => addr.id !== id)
// If we're deleting the primary address, make the first one primary
if (addresses.find(addr => addr.id === id)?.isPrimary && filteredAddresses.length > 0) {
filteredAddresses[0].isPrimary = true
}
setAddresses(filteredAddresses)
}
return (
<div className='bg-white rounded-4xl p-8'>
<div className='flex justify-between mb-8'>
<h2 className='text-xl font-semibold'>{t('setting.address')}</h2>
<button className='bg-primary text-white px-6 py-2 rounded-lg'>{t('common.save')}</button>
</div>
<div className='mb-6'>
<div className='flex justify-between items-center mb-4'>
<h3 className='text-md font-medium'>{t('setting.email_addresses')}</h3>
{!showAddForm && (
<button
onClick={() => setShowAddForm(true)}
className='flex items-center gap-1 text-primary'
>
<Add size={18} />
<span>{t('setting.add_address')}</span>
</button>
)}
</div>
{showAddForm && (
<div className='p-4 bg-gray-50 rounded-lg mb-4'>
<h4 className='font-medium mb-3'>{t('setting.new_address')}</h4>
<div className='grid grid-cols-1 gap-4 mb-4'>
<div>
<label className='block text-sm font-medium text-gray-700 mb-1'>{t('setting.email')}</label>
<input
type="email"
value={newEmail}
onChange={(e) => setNewEmail(e.target.value)}
className='w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent'
placeholder='email@domain.com'
/>
</div>
<div>
<label className='block text-sm font-medium text-gray-700 mb-1'>{t('setting.display_name')}</label>
<input
type="text"
value={newName}
onChange={(e) => setNewName(e.target.value)}
className='w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent'
placeholder={t('setting.display_name_placeholder')}
/>
</div>
</div>
<div className='flex justify-end gap-2'>
<button
onClick={() => {
setShowAddForm(false)
setNewEmail('')
setNewName('')
}}
className='px-4 py-2 border border-gray-300 rounded-lg'
>
{t('common.cancel')}
</button>
<button
onClick={handleAddAddress}
disabled={!newEmail || !newName}
className={`px-4 py-2 rounded-lg ${!newEmail || !newName ? 'bg-gray-300 text-gray-500' : 'bg-primary text-white'}`}
>
{t('common.save')}
</button>
</div>
</div>
)}
<div className='space-y-3'>
{addresses.map((address) => (
<div key={address.id} className='p-4 bg-gray-50 rounded-lg'>
{editingId === address.id ? (
<div className='grid grid-cols-1 gap-4 mb-4'>
<div>
<label className='block text-sm font-medium text-gray-700 mb-1'>{t('setting.email')}</label>
<input
type="email"
value={newEmail}
onChange={(e) => setNewEmail(e.target.value)}
className='w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent'
/>
</div>
<div>
<label className='block text-sm font-medium text-gray-700 mb-1'>{t('setting.display_name')}</label>
<input
type="text"
value={newName}
onChange={(e) => setNewName(e.target.value)}
className='w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent'
/>
</div>
<div className='flex justify-end gap-2'>
<button
onClick={() => {
setEditingId(null)
setNewEmail('')
setNewName('')
}}
className='px-4 py-2 border border-gray-300 rounded-lg'
>
{t('common.cancel')}
</button>
<button
onClick={handleEditSave}
disabled={!newEmail || !newName}
className={`px-4 py-2 rounded-lg ${!newEmail || !newName ? 'bg-gray-300 text-gray-500' : 'bg-primary text-white'}`}
>
{t('common.save')}
</button>
</div>
</div>
) : (
<div className='flex items-center justify-between'>
<div>
<div className='flex items-center gap-2'>
<p className='font-medium'>{address.name}</p>
{address.isPrimary && (
<span className='px-2 py-0.5 bg-green-100 text-green-600 text-xs rounded-full'>
{t('setting.primary')}
</span>
)}
</div>
<p className='text-gray-500'>{address.email}</p>
</div>
<div className='flex items-center gap-2'>
{!address.isPrimary && (
<>
<button
onClick={() => handleSetPrimary(address.id)}
className='text-blue-600 px-3 py-1 text-sm'
>
{t('setting.set_primary')}
</button>
<button
onClick={() => handleEditStart(address)}
className='text-gray-500'
>
<Edit2 size={18} />
</button>
<button
onClick={() => handleDelete(address.id)}
className='text-gray-500'
>
<Trash size={18} />
</button>
</>
)}
</div>
</div>
)}
</div>
))}
</div>
</div>
<div className='p-4 bg-yellow-50 rounded-lg border border-yellow-100'>
<div className='flex items-start gap-3'>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 22C17.5 22 22 17.5 22 12C22 6.5 17.5 2 12 2C6.5 2 2 6.5 2 12C2 17.5 6.5 22 12 22Z" stroke="#F59E0B" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M12 8V13" stroke="#F59E0B" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M11.9941 16H12.0031" stroke="#F59E0B" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
<div>
<h4 className='font-medium text-yellow-800'>{t('setting.verification_required')}</h4>
<p className='text-sm text-yellow-700 mt-1'>{t('setting.verification_note')}</p>
</div>
</div>
</div>
</div>
)
}
export default Address
+128
View File
@@ -0,0 +1,128 @@
import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Add, CloseCircle } from 'iconsax-react'
const Domain: FC = () => {
const { t } = useTranslation()
const [domains, setDomains] = useState<string[]>(['example.com'])
const [newDomain, setNewDomain] = useState('')
const [error, setError] = useState('')
const handleAddDomain = () => {
if (!newDomain) {
setError(t('setting.domain_required'))
return
}
// Simple domain validation
const domainRegex = /^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9]\.[a-zA-Z]{2,}$/
if (!domainRegex.test(newDomain)) {
setError(t('setting.invalid_domain'))
return
}
if (domains.includes(newDomain)) {
setError(t('setting.domain_exists'))
return
}
setDomains([...domains, newDomain])
setNewDomain('')
setError('')
}
const handleRemoveDomain = (domain: string) => {
setDomains(domains.filter(d => d !== domain))
}
return (
<div className='bg-white rounded-4xl p-8'>
<div className='flex justify-between mb-8'>
<h2 className='text-xl font-semibold'>{t('setting.domain')}</h2>
<button className='bg-primary text-white px-6 py-2 rounded-lg'>{t('common.save')}</button>
</div>
<div className='mb-6'>
<label className='block text-sm font-medium text-gray-700 mb-1'>{t('setting.add_domain')}</label>
<div className='flex gap-2'>
<input
type="text"
value={newDomain}
onChange={(e) => {
setNewDomain(e.target.value)
setError('')
}}
className={`flex-1 px-4 py-2 border ${error ? 'border-red-500' : 'border-gray-300'} rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent`}
placeholder='domain.com'
/>
<button
onClick={handleAddDomain}
className='bg-primary text-white px-4 py-2 rounded-lg'
>
<Add size={20} />
</button>
</div>
{error && <p className='text-red-500 text-sm mt-1'>{error}</p>}
</div>
<div>
<h3 className='text-md font-medium mb-4'>{t('setting.your_domains')}</h3>
<div className='space-y-3'>
{domains.map((domain, index) => (
<div key={index} className='flex items-center justify-between p-4 bg-gray-50 rounded-lg'>
<div>
<p className='font-medium'>{domain}</p>
<p className='text-sm text-gray-500'>
{index === 0 ? t('setting.primary_domain') : t('setting.additional_domain')}
</p>
</div>
<div className='flex items-center gap-3'>
<div className={`px-2 py-1 rounded-full text-xs ${index === 0 ? 'bg-green-100 text-green-600' : 'bg-blue-100 text-blue-600'}`}>
{index === 0 ? t('setting.verified') : t('setting.pending')}
</div>
{index !== 0 && (
<button
onClick={() => handleRemoveDomain(domain)}
className='text-gray-400 hover:text-red-500'
>
<CloseCircle size={20} />
</button>
)}
</div>
</div>
))}
</div>
</div>
<div className='mt-8 p-4 bg-blue-50 rounded-lg'>
<h3 className='text-md font-medium text-blue-700 mb-2'>{t('setting.dns_records')}</h3>
<p className='text-sm text-blue-600 mb-4'>{t('setting.dns_instructions')}</p>
<div className='bg-white p-4 rounded-lg border border-blue-100'>
<div className='grid grid-cols-3 gap-4 text-sm font-medium text-gray-500 pb-2 border-b'>
<div>{t('setting.type')}</div>
<div>{t('setting.hostname')}</div>
<div>{t('setting.value')}</div>
</div>
<div className='grid grid-cols-3 gap-4 text-sm py-4 border-b'>
<div>MX</div>
<div>@</div>
<div>mail.example.com</div>
</div>
<div className='grid grid-cols-3 gap-4 text-sm py-4 border-b'>
<div>TXT</div>
<div>@</div>
<div className='break-all'>v=spf1 include:spf.example.com ~all</div>
</div>
<div className='grid grid-cols-3 gap-4 text-sm py-4'>
<div>CNAME</div>
<div>mail</div>
<div>mailserver.example.com</div>
</div>
</div>
</div>
</div>
)
}
export default Domain
@@ -0,0 +1,134 @@
import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
const MailServer: FC = () => {
const { t } = useTranslation()
const [serverType, setServerType] = useState('smtp')
const [host, setHost] = useState('')
const [port, setPort] = useState('')
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [encryption, setEncryption] = useState('tls')
return (
<div className='bg-white rounded-4xl p-8'>
<div className='flex justify-between mb-8'>
<h2 className='text-xl font-semibold'>{t('setting.mail_server')}</h2>
<button className='bg-primary text-white px-6 py-2 rounded-lg'>{t('common.save')}</button>
</div>
<div className='grid grid-cols-1 gap-6'>
<div>
<label className='block text-sm font-medium text-gray-700 mb-1'>{t('setting.server_type')}</label>
<div className='flex gap-4 mb-4'>
<label className='flex items-center gap-2 cursor-pointer'>
<input
type="radio"
checked={serverType === 'smtp'}
onChange={() => setServerType('smtp')}
className='h-4 w-4 accent-primary'
/>
<span>SMTP</span>
</label>
<label className='flex items-center gap-2 cursor-pointer'>
<input
type="radio"
checked={serverType === 'api'}
onChange={() => setServerType('api')}
className='h-4 w-4 accent-primary'
/>
<span>API</span>
</label>
</div>
</div>
{serverType === 'smtp' && (
<>
<div className='grid grid-cols-2 gap-4'>
<div>
<label className='block text-sm font-medium text-gray-700 mb-1'>{t('setting.host')}</label>
<input
type="text"
value={host}
onChange={(e) => setHost(e.target.value)}
className='w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent'
placeholder='mail.example.com'
/>
</div>
<div>
<label className='block text-sm font-medium text-gray-700 mb-1'>{t('setting.port')}</label>
<input
type="text"
value={port}
onChange={(e) => setPort(e.target.value)}
className='w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent'
placeholder='587'
/>
</div>
</div>
<div>
<label className='block text-sm font-medium text-gray-700 mb-1'>{t('setting.username')}</label>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
className='w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent'
placeholder='username@example.com'
/>
</div>
<div>
<label className='block text-sm font-medium text-gray-700 mb-1'>{t('setting.password')}</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className='w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent'
/>
</div>
<div>
<label className='block text-sm font-medium text-gray-700 mb-1'>{t('setting.encryption')}</label>
<select
value={encryption}
onChange={(e) => setEncryption(e.target.value)}
className='w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent'
>
<option value="none">{t('setting.none')}</option>
<option value="ssl">SSL</option>
<option value="tls">TLS</option>
</select>
</div>
<div>
<button className='bg-blue-50 text-blue-600 px-4 py-2 rounded-lg mt-4'>{t('setting.test_connection')}</button>
</div>
</>
)}
{serverType === 'api' && (
<>
<div>
<label className='block text-sm font-medium text-gray-700 mb-1'>{t('setting.api_key')}</label>
<input
type="password"
className='w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent'
/>
</div>
<div>
<label className='block text-sm font-medium text-gray-700 mb-1'>{t('setting.api_url')}</label>
<input
type="text"
className='w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent'
placeholder='https://api.example.com/send'
/>
</div>
</>
)}
</div>
</div>
)
}
export default MailServer
+180 -7
View File
@@ -1,13 +1,186 @@
import { FC } from 'react'
import Setting from './Setting'
const Personality: FC = () => {
return (
<div className='flex gap-8'>
<div className='flex-1 bg-white rounded-4xl p-8'>
import { FC, useState } from 'react'
import { Add, Edit } from 'iconsax-react'
const Personality: FC = () => {
const [backgroundImage, setBackgroundImage] = useState<string | null>(null)
const [emailContent, setEmailContent] = useState<string>('')
const [emailFooter, setEmailFooter] = useState<string>('')
const [showContentEditor, setShowContentEditor] = useState<boolean>(false)
const [showFooterEditor, setShowFooterEditor] = useState<boolean>(false)
const handleImageUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0]
if (file) {
const reader = new FileReader()
reader.onload = (e) => {
setBackgroundImage(e.target?.result as string)
}
reader.readAsDataURL(file)
}
}
const handleAddEmailContent = () => {
if (!emailContent) {
setEmailContent('متن ایمیل خود را اینجا وارد کنید')
}
setShowContentEditor(true)
}
const handleAddEmailFooter = () => {
if (!emailFooter) {
setEmailFooter('پاورقی ایمیل خود را اینجا وارد کنید')
}
setShowFooterEditor(true)
}
return (
<div className='bg-white rounded-4xl p-6 flex-1'>
{/* Header buttons */}
<div className='flex justify-between mb-8'>
<button className='bg-black text-white px-5 py-2 rounded-xl text-sm'>ذخیره</button>
{/* <div className='flex gap-3'>
<button className='w-10 h-10 flex items-center justify-center bg-white border border-gray-200 rounded-full'>
<Edit size={20} variant="Bold" color="#FF008A" />
</button>
<button className='w-10 h-10 flex items-center justify-center bg-white border border-gray-200 rounded-full'>
<ArrowRight size={20} />
</button>
<button className='w-10 h-10 flex items-center justify-center bg-white border border-gray-200 rounded-full'>
<ArrowLeft size={20} />
</button>
<button className='w-10 h-10 flex items-center justify-center bg-white border border-gray-200 rounded-full'>
<Eye size={20} />
</button>
</div> */}
</div>
<Setting />
{/* Background image section */}
<div className='border border-dashed border-gray-300 rounded-2xl p-8 mb-6'>
{backgroundImage ? (
<div className='relative w-full'>
<img src={backgroundImage} alt="Background" className='w-full h-48 object-cover rounded-lg' />
<button
className='absolute top-2 right-2 bg-white p-1 rounded-full shadow-sm'
onClick={() => setBackgroundImage(null)}
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 22C17.5 22 22 17.5 22 12C22 6.5 17.5 2 12 2C6.5 2 2 6.5 2 12C2 17.5 6.5 22 12 22Z" stroke="#292D32" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M9.17 14.83L14.83 9.17" stroke="#292D32" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M14.83 14.83L9.17 9.17" stroke="#292D32" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</button>
</div>
) : (
<div className='flex flex-col items-center justify-center py-10'>
<div className='text-gray-500 mb-4'>تصویر پس زمینه مورد نظر را آپلود کنید</div>
<label className='flex items-center gap-2 bg-white border border-gray-200 rounded-lg px-4 py-2 text-sm cursor-pointer'>
<input
type="file"
accept="image/*"
className='hidden'
onChange={handleImageUpload}
/>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 17V11L7 13" stroke="#292D32" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M9 11L11 13" stroke="#292D32" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M22 10V15C22 20 20 22 15 22H9C4 22 2 20 2 15V9C2 4 4 2 9 2H14" stroke="#292D32" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M22 10H18C15 10 14 9 14 6V2L22 10Z" stroke="#292D32" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
آپلود
</label>
</div>
)}
</div>
{/* Email content section */}
<div className='border border-dashed border-gray-300 rounded-2xl p-8 mb-6'>
{emailContent && showContentEditor ? (
<div className='w-full'>
<textarea
className='w-full p-4 border border-gray-200 rounded-lg min-h-[150px] text-right'
value={emailContent}
onChange={(e) => setEmailContent(e.target.value)}
/>
<div className='flex justify-end mt-3'>
<button
className='px-4 py-2 bg-gray-200 rounded-lg text-sm'
onClick={() => setShowContentEditor(false)}
>
تایید
</button>
</div>
</div>
) : emailContent ? (
<div className='w-full'>
<div className='p-4 border border-gray-200 rounded-lg min-h-[150px] text-right'>
{emailContent}
</div>
<div className='flex justify-end mt-3'>
<button
className='p-2 bg-gray-100 rounded-lg'
onClick={() => setShowContentEditor(true)}
>
<Edit size={16} />
</button>
</div>
</div>
) : (
<div className='flex flex-col items-center justify-center py-14'>
<div className='text-gray-500 mb-3'>محتوای ایمیل شما</div>
<button
className='w-10 h-10 flex items-center justify-center rounded-full bg-gray-100'
onClick={handleAddEmailContent}
>
<Add size={24} />
</button>
</div>
)}
</div>
{/* Email footer section */}
<div className='border border-dashed border-gray-300 rounded-2xl p-8 mb-6'>
{emailFooter && showFooterEditor ? (
<div className='w-full'>
<textarea
className='w-full p-4 border border-gray-200 rounded-lg min-h-[100px] text-right'
value={emailFooter}
onChange={(e) => setEmailFooter(e.target.value)}
/>
<div className='flex justify-end mt-3'>
<button
className='px-4 py-2 bg-gray-200 rounded-lg text-sm'
onClick={() => setShowFooterEditor(false)}
>
تایید
</button>
</div>
</div>
) : emailFooter ? (
<div className='w-full'>
<div className='p-4 border border-gray-200 rounded-lg min-h-[100px] text-right'>
{emailFooter}
</div>
<div className='flex justify-end mt-3'>
<button
className='p-2 bg-gray-100 rounded-lg'
onClick={() => setShowFooterEditor(true)}
>
<Edit size={16} />
</button>
</div>
</div>
) : (
<div className='flex flex-col items-center justify-center py-10'>
<div className='text-gray-500 mb-3'>پاورقی ایمیل شما</div>
<button
className='w-10 h-10 flex items-center justify-center rounded-full bg-gray-100'
onClick={handleAddEmailFooter}
>
<Add size={24} />
</button>
</div>
)}
</div>
</div>
)
}
+149 -18
View File
@@ -1,31 +1,162 @@
import { FC } from 'react'
import { ArrowDown2, Gallery, LinkSquare, Setting4, Shapes, Text } from 'iconsax-react'
import Logo from '@/assets/images/logo-small.svg'
import { Gallery, LinkSquare, Setting4, Shapes, Text } from 'iconsax-react'
import { useTranslation } from 'react-i18next'
const Setting: FC = () => {
const { t } = useTranslation()
const PersonalitySidebar: FC = () => {
return (
<div className='flex-1 bg-white rounded-4xl p-8 max-w-[400px] flex'>
<div className='flex-1'>
<div className='bg-white rounded-3xl w-[360px] flex'>
<div className='flex-1 p-6'>
<div className='text-xl font-semibold text-right mb-6'>تنظیمات</div>
{/* Layout options */}
<div className='mb-6'>
<div className='grid grid-cols-2 gap-3'>
<div className='border border-gray-200 rounded-xl p-2'>
<div className='flex h-full'>
<div className='w-full h-12 bg-gray-100 rounded-lg'></div>
</div>
</div>
<div className='border border-gray-200 rounded-xl p-2'>
<div className='flex h-full'>
<div className='w-full h-12 bg-gray-100 rounded-lg'></div>
</div>
</div>
<div className='border border-gray-200 rounded-xl p-2'>
<div className='grid grid-cols-2 gap-1 w-full h-full'>
<div className='bg-gray-100 rounded-lg'></div>
<div className='bg-gray-100 rounded-lg'></div>
</div>
</div>
<div className='border border-gray-200 rounded-xl p-2'>
<div className='grid grid-cols-3 gap-1 w-full h-full'>
<div className='bg-gray-100 rounded-lg'></div>
<div className='bg-gray-100 rounded-lg'></div>
<div className='bg-gray-100 rounded-lg'></div>
</div>
</div>
</div>
</div>
{/* Color picker section */}
<div className='mb-6'>
<div className='flex justify-end mb-2'>
<span className='text-sm text-gray-600'>رنگ زمینه</span>
</div>
<div className='flex flex-col items-center'>
<div className='w-full h-36 bg-[#FF008A] rounded-lg mb-3'></div>
<div className='flex items-center w-full'>
<div className='flex'>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="10" fill="url(#paint0_angular_121_3242)" stroke="#E5E7EB" strokeWidth="1.5" />
<circle cx="12" cy="12" r="3" fill="#FF008A" stroke="white" strokeWidth="1.5" />
<defs>
<radialGradient id="paint0_angular_121_3242" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(12 12) rotate(90) scale(10)">
<stop offset="0" stopColor="#FF0000" />
<stop offset="0.17" stopColor="#FFFF00" />
<stop offset="0.33" stopColor="#00FF00" />
<stop offset="0.5" stopColor="#00FFFF" />
<stop offset="0.67" stopColor="#0000FF" />
<stop offset="0.83" stopColor="#FF00FF" />
<stop offset="1" stopColor="#FF0000" />
</radialGradient>
</defs>
</svg>
</div>
<div className='text-sm text-gray-500 ml-auto text-right'>#FF008A</div>
</div>
</div>
</div>
{/* Upload image section */}
<div className='border border-dashed border-gray-300 rounded-lg p-5 mb-6'>
<div className='flex flex-col items-center'>
<div className='mb-3'>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="24" height="24" rx="4" fill="#F3F4F6" />
<path d="M17 9.5C17 11.7091 15.2091 13.5 13 13.5C10.7909 13.5 9 11.7091 9 9.5C9 7.29086 10.7909 5.5 13 5.5C15.2091 5.5 17 7.29086 17 9.5Z" stroke="#9CA3AF" strokeWidth="1" />
<path d="M3 20.5L8 15.5L10.5 17L14.5 13L21 19.5" stroke="#9CA3AF" strokeWidth="1" />
</svg>
</div>
<div className='text-center text-xs text-gray-500 mb-3'>تصویر پس زمینه مورد نظر را آپلود کنید</div>
<button className='flex items-center gap-1 bg-white border border-gray-200 rounded-lg px-3 py-1 text-xs'>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 17V11L7 13" stroke="#292D32" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M9 11L11 13" stroke="#292D32" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M22 10V15C22 20 20 22 15 22H9C4 22 2 20 2 15V9C2 4 4 2 9 2H14" stroke="#292D32" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M22 10H18C15 10 14 9 14 6V2L22 10Z" stroke="#292D32" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
آپلود
</button>
</div>
</div>
{/* Other settings */}
<div className='mb-4'>
<div className='flex justify-end mb-2'>
<span className='text-sm text-gray-600'>سایر</span>
</div>
<div className='flex items-center justify-between border border-gray-200 rounded-lg p-2'>
<ArrowDown2 size={16} />
<span className='text-sm'>پوشش</span>
</div>
</div>
{/* Positioning options */}
<div>
{t('setting.title')}
<div className='flex justify-between mb-2'>
<div className='text-sm text-gray-600'>موقعیت افقی</div>
<div className='text-sm text-gray-600'>موقعیت عمودی</div>
</div>
<div className='flex justify-between'>
<div className='flex gap-1'>
<button className='w-7 h-7 border border-gray-200 rounded flex items-center justify-center'>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.5 7H19.5M7.5 12H16.5M10.5 17H13.5" stroke="#292D32" strokeWidth="1.5" strokeLinecap="round" />
</svg>
</button>
<button className='w-7 h-7 border border-gray-200 rounded flex items-center justify-center'>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.5 7H16.5M4.5 12H19.5M10.5 17H13.5" stroke="#292D32" strokeWidth="1.5" strokeLinecap="round" />
</svg>
</button>
<button className='w-7 h-7 border border-gray-200 rounded flex items-center justify-center'>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.5 7H13.5M7.5 12H16.5M4.5 17H19.5" stroke="#292D32" strokeWidth="1.5" strokeLinecap="round" />
</svg>
</button>
</div>
<div className='flex gap-1'>
<button className='w-7 h-7 border border-gray-200 rounded flex items-center justify-center'>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7 4.5V19.5M12 7.5V16.5M17 10.5V13.5" stroke="#292D32" strokeWidth="1.5" strokeLinecap="round" />
</svg>
</button>
<button className='w-7 h-7 border border-gray-200 rounded flex items-center justify-center'>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7 7.5V16.5M12 4.5V19.5M17 10.5V13.5" stroke="#292D32" strokeWidth="1.5" strokeLinecap="round" />
</svg>
</button>
<button className='w-7 h-7 border border-gray-200 rounded flex items-center justify-center'>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7 10.5V13.5M12 7.5V16.5M17 4.5V19.5" stroke="#292D32" strokeWidth="1.5" strokeLinecap="round" />
</svg>
</button>
</div>
</div>
</div>
</div>
<div className='w-fit pr-6 flex flex-col items-end border-r border-border'>
<img src={Logo} className='w-8' />
<div className='flex flex-col gap-6 mt-14'>
<Setting4 size={20} color='black' variant='Bold' />
<Text size={20} color='black' />
<LinkSquare size={20} color='black' />
<Gallery size={20} color='black' />
<Shapes size={20} color='black' />
<div className='w-20 border-r border-gray-200 flex flex-col items-center pt-6 pb-6'>
<img src={Logo} className='w-8' alt="Logo" />
<div className='flex flex-col gap-10 mt-16'>
<Setting4 size={22} color='black' variant='Bold' />
<Text size={22} color='black' />
<LinkSquare size={22} color='black' />
<Gallery size={22} color='black' />
<Shapes size={22} color='black' />
</div>
</div>
</div>
)
}
export default Setting
export default PersonalitySidebar